npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@pythonluvr/squire

v1.3.0

Published

General-purpose runtime for spawning CLI AI agents (Claude Code, Codex, Gemini CLI) as subprocesses with structured event streaming, MCP tool forwarding, and permission auto-setup.

Readme

If you've spawned claude, codex, or gemini from a Node app, you know what you signed up for. Windows .cmd shim quirks. Hand-parsing stdout. Writing MCP config files to disk. Getting Claude Code to actually let your MCP tools through its permission gate. Then doing it all again the next time you add a CLI to the mix.

Squire is the runtime layer that handles that. Give it a binary and a prompt; you get back a typed event stream, MCP forwarding that works, and permission setup that doesn't need babysitting. Spawn one agent or bridge several from the same Node process. Cross-platform, zero runtime dependencies, MIT.

It's a tool, not a framework. Squire doesn't have opinions about how you structure your agent loop, what you log, or which CLI is "best." It hands you the primitives and stays out of the way.

Bonus most people miss: the CLIs Squire wraps auth through your subscription, not your API key. Claude Code uses your Claude Code OAuth. Gemini CLI uses your Google AI account. Squire lets your Node app inherit that subscription auth instead of burning API tokens at retail. The $20/mo seat your team already pays for can now power the apps you're building.

npm install @pythonluvr/squire
import { Squire } from '@pythonluvr/squire'

const squire = new Squire({
  binary: 'claude',
  args: ['--permission-mode', 'bypassPermissions'],
  cwd: process.cwd(),
})

squire.on('stdout', (chunk) => process.stdout.write(chunk))
squire.on('event', (event) => {
  if (event.type === 'message_stop') console.log('\nexit code:', event.code)
})

await squire.start('Hello, agent.')

What it does

  • Subprocess spawn. Cross-platform child_process wrapper with Windows .cmd / .bat / extensionless-binary handling baked in. Works the same way on macOS and Linux.
  • Structured event streaming. A typed SquireEvent union (stdout, stderr, text_delta, tool_call, tool_result, thinking_delta, usage, message_start, message_stop, error) replaces ad-hoc stdio parsing. v1.1 ships dedicated parsers for Claude Code (adapter: 'claude-code') and Gemini CLI (adapter: 'gemini-cli') that emit semantic events; the built-in text-stream adapter remains the default fallback for any other CLI.
  • MCP forwarding. Pass mcp.servers or a pre-built mcp.configPath and Squire wires the child's --mcp-config flag for you. The temp config file is cleaned up on stop().
  • Permission auto-setup. For Claude Code, autoSetup.claudeCode merges allowedTools patterns into ~/.claude/settings.json atomically (preserving everything else in the file). Idempotent.

Supported CLIs

v1.0 is binary-agnostic. Any CLI that accepts a prompt on stdin and emits output on stdout will work with the built-in text-stream adapter.

| CLI | Notes | | --- | --- | | Claude Code | binary: 'claude', adapter: 'claude-code'. Pair with autoSetup.claudeCode for MCP-tool permissions. Emits text_delta, thinking_delta, tool_call, tool_result, usage. | | Gemini CLI | binary: 'gemini', adapter: 'gemini-cli'. Honors --mcp-config. Emits text_delta, tool_call, tool_result, usage. | | OpenAI Codex CLI | binary: 'codex'. Honors --mcp-config. Use the default text-stream adapter; a dedicated codex adapter is planned for a follow-up release. | | Custom | Any binary on PATH or absolute path. Use text-stream or register your own SquireAdapter. |

The dedicated adapters parse each vendor's stream-json output line-by-line and fall back to raw stdout events for any line they cannot interpret, so a vendor format tweak degrades gracefully instead of crashing. The SquireAdapter interface is exported for callers who want to ship custom parsers.

Cross-platform

| Platform | Status | | --- | --- | | Linux | First-class. | | macOS | First-class. | | Windows | First-class. Handles .cmd / .bat shims (needed for npm-installed CLIs) and PATHEXT walk for extensionless binaries via shell: true auto-detection. |

The Windows logic is in src/spawn.ts. It's deliberately scoped: shell: true only when the binary needs it, so paths containing spaces (the C:\Program Files\... case) keep working.

API reference

new Squire(options: SquireOptions)

| Option | Type | Default | Notes | | --- | --- | --- | --- | | binary | string | required | Path or PATH-resolvable binary name. | | args | string[] | [] | Default args prepended before per-call args. | | cwd | string | process.cwd() | Working directory for the child. | | env | Record<string,string> | {} | Merged on top of process.env. | | timeoutMs | number | 600000 | Hard timeout per start(). 0 = unlimited. | | shell | boolean | auto | Force shell mode; otherwise auto-detected per platform. | | mcp | SquireMcpOptions | off | See "MCP forwarding" below. | | autoSetup | SquireAutoSetupOptions | off | See "Permission auto-setup" below. | | adapter | string | 'text-stream' | Name of a registered SquireAdapter. |

Lifecycle

  • start(prompt: string, opts?: { signal?: AbortSignal, extraArgs?: string[] }): Promise<void>: spawn the child, pipe the prompt to stdin, stream events until exit. Resolves when the child closes (cleanly or otherwise).
  • send(followup: string): Promise<void>: write more input to the child's stdin. Throws if the child is dead or stdin was closed.
  • stop({ graceful?: boolean }): Promise<void>: SIGTERM then SIGKILL after 3 seconds (default); { graceful: false } sends SIGKILL immediately.
  • pid: number | null: child PID once spawned.

Events

squire.on('stdout', (chunk: string) => { /* raw */ })
squire.on('stderr', (chunk: string) => { /* raw */ })
squire.on('event', (event: SquireEvent) => { /* discriminated union, see events.ts */ })
squire.on('exit', (code: number | null) => { /* terminal */ })

SquireEvent is a discriminated union; narrow on event.type. The v1.0 type union is documented in src/events.ts.

Errors

All thrown errors are SquireError (or SquireAutoSetupError for permission-file failures). Narrow on err.code:

| Code | Meaning | | --- | --- | | INVALID_OPTIONS | Constructor or registerSquireAdapter rejected the input. | | ALREADY_STARTED | start() called twice on the same instance. | | NOT_STARTED | send() called before start(). | | SPAWN_FAILED | Child failed to spawn (ENOENT, EACCES, etc). | | TIMEOUT | timeoutMs exceeded. | | NON_ZERO_EXIT | Child exited with a non-zero status code. | | ADAPTER_UNSUPPORTED_FEATURE | Adapter or transport doesn't support the requested operation. | | ADAPTER_PARSE | Adapter failed to parse the child's output (reserved for v1.x adapters). | | AUTOSETUP_READ / AUTOSETUP_PARSE / AUTOSETUP_WRITE | Claude Code settings merge failed; see .path. | | MCP_CONFIG_WRITE | Could not write the temp MCP config file. |

MCP forwarding

new Squire({
  binary: 'claude',
  mcp: {
    servers: {
      myserver: { command: 'node', args: ['./mcp-server.js'] },
    },
    allowList: ['mcp__myserver__*'],
  },
})

If you already have an MCP config file, pass mcp.configPath instead and Squire will skip the temp-file write.

The flag defaults to --mcp-config (matches Claude Code, Codex, Gemini CLI). Override with mcp.configFlag for custom CLIs.

Permission auto-setup

new Squire({
  binary: 'claude',
  mcp: { /* ... */ allowList: ['mcp__myserver__*'] },
  autoSetup: {
    claudeCode: { writeSettings: true },
  },
})

Claude Code treats external MCP tools as separate-trust by design. Without this step, the child halts at its own permission gate on the first MCP tool call. Squire merges the allowList patterns into ~/.claude/settings.json atomically, preserving everything else in the file.

Falls back to ~/.claude/settings.json by default; override with autoSetup.claudeCode.settingsPath.

Custom adapters

import { registerSquireAdapter, type SquireAdapter } from '@pythonluvr/squire'

const myAdapter: SquireAdapter = {
  name: 'codex-json',
  create(ctx) {
    return {
      onStdout(chunk) { /* parse, return SquireEvent[] */ return [] },
      onStderr(chunk) { return [{ type: 'stderr', chunk }] },
    }
  },
}
registerSquireAdapter(myAdapter)

new Squire({ binary: 'codex', adapter: 'codex-json' })

Examples

Two runnable example apps live in examples/:

  • examples/minimal: the smallest possible Squire app. Spawns Claude Code, sends one prompt, prints streamed text deltas. Start here.
  • examples/bridge-multi-cli: spawns Claude Code and Gemini CLI sequentially in the same Node process and pipes the first reply into the second prompt. Shows the multi-CLI bridging differentiator end to end.

Each example is standalone with its own package.json; cd into the directory and run npm install && npm start.

Example consumers

  • OpenWar is a phase-gated agent runtime. It uses Squire under the hood for its cli-bridge adapter, layering a phase machine, deterministic detectors, and brief/trace persistence on top.

If you're using Squire in a project, send a PR adding a line here.

License

MIT. See LICENSE.

Contributing

PRs welcome. See docs/contributing.md for the basics. The public API is frozen at v1.0.0: additive changes only on the v1.x line; breaking changes wait for v2.0.