This is the canonical reference for the v1.0 public surface. Everything in src/index.ts is exported; anything outside that file is internal and may change between patch versions.
The main class. One instance manages one child-process lifecycle.
import { Squire } from '@pythonluvr/squire'
const squire = new Squire({
binary: 'claude',
args: ['--permission-mode', 'bypassPermissions'],
})Spawns the child, runs optional auto-setup and MCP preflight, pipes prompt to stdin, and streams events until exit. Resolves when the child closes (cleanly or otherwise).
Throws synchronously only on INVALID_OPTIONS or ALREADY_STARTED. All other failures (spawn, timeout, non-zero exit) surface as error events on the stream and resolve the promise normally.
SquireStartOptions:
signal?: AbortSignal: forward an abort signal to the child (SIGTERM on abort).extraArgs?: string[]: per-call argv tail appended afteroptions.argsand any Squire-injected args (e.g.--mcp-config).
Writes followup to the child's stdin. Throws NOT_STARTED if called before start(), or ADAPTER_UNSUPPORTED_FEATURE if the child's stdin is closed (most CLIs do this after consuming the initial prompt).
Terminates the child.
{ graceful: true }(default): SIGTERM, then SIGKILL after 3 seconds if still running.{ graceful: false }: immediate SIGKILL.
Safe to call multiple times.
The child PID once spawned, or null.
Squire extends EventEmitter. Listeners:
squire.on('stdout', (chunk: string) => { /* raw */ })
squire.on('stderr', (chunk: string) => { /* raw */ })
squire.on('event', (event: SquireEvent) => { /* semantic union */ })
squire.on('exit', (code: number | null) => { /* lifecycle terminal */ })Discriminated union. Narrow on type:
type SquireEvent =
| { type: 'stdout'; chunk: string }
| { type: 'stderr'; chunk: string }
| { type: 'text_delta'; delta: string }
| { type: 'message_start'; pid: number }
| { type: 'message_stop'; code: number | null; signal: NodeJS.Signals | null; assembled: string }
| { type: 'error'; error: Error; reason?: 'spawn'|'timeout'|'exit'|'adapter'|'aborted'; stderrTail?: string }
// Added in v1.1 (emitted by per-CLI adapters; never by `text-stream`):
| { type: 'tool_call'; id: string; name: string; input: unknown }
| { type: 'tool_result'; id: string; output: unknown; isError?: boolean }
| { type: 'thinking_delta'; delta: string }
| { type: 'usage'; inputTokens?: number; outputTokens?: number; cacheReadTokens?: number; cacheWriteTokens?: number }text_delta is emitted by the configured adapter. For the built-in text-stream adapter it duplicates stdout. The v1.1 per-CLI adapters (claude-code, gemini-cli) emit text_delta only for assistant-visible text and route tool I/O through tool_call / tool_result, internal reasoning through thinking_delta, and token counters through usage. Custom adapters may emit any subset; consumers should narrow on event.type and ignore variants they don't care about.
| Option | Type | Default | Notes |
|---|---|---|---|
binary |
string |
required | Path or PATH-resolvable binary. |
args |
string[] |
[] |
Default args. |
cwd |
string |
process.cwd() |
Working directory. |
env |
Record<string,string> |
{} |
Merged on top of process.env. |
timeoutMs |
number |
600000 |
0 = unlimited. |
shell |
boolean |
auto | Force shell mode; otherwise per-platform auto-detect. |
mcp |
SquireMcpOptions |
off | MCP forwarding. |
autoSetup |
SquireAutoSetupOptions |
off | Permission preflight. |
adapter |
string |
'text-stream' |
Registered adapter name. |
{
configPath?: string; // pre-built config file
servers?: Record<string, SquireMcpServerConfig>; // inline (Squire writes temp file)
configFlag?: string; // default '--mcp-config'
allowList?: string[]; // for autoSetup.claudeCode
cleanupOnStop?: boolean; // default true for temp files
}{
claudeCode?: {
writeSettings?: boolean; // default true (when claudeCode is set)
allowedTools?: string[]; // falls back to mcp.allowList
settingsPath?: string; // default ~/.claude/settings.json
}
}All thrown errors are instances of SquireError. Narrow on err.code:
| Code | Trigger |
|---|---|
INVALID_OPTIONS |
Bad constructor input or adapter name. |
ALREADY_STARTED |
Second start() on the same instance. |
NOT_STARTED |
send() before start(). |
SPAWN_FAILED |
ENOENT / EACCES, signaled via error event. |
TIMEOUT |
timeoutMs exceeded. |
NON_ZERO_EXIT |
Child exited non-zero (also error event). |
ADAPTER_UNSUPPORTED_FEATURE |
send() after stdin closed, etc. |
ADAPTER_PARSE |
Reserved for v1.x adapters. |
AUTOSETUP_READ / AUTOSETUP_PARSE / AUTOSETUP_WRITE |
Claude Code settings merge failed. SquireAutoSetupError carries .path. |
MCP_CONFIG_WRITE |
Could not write temp MCP config. |
import { registerSquireAdapter, type SquireAdapter } from '@pythonluvr/squire'
const codexJson: SquireAdapter = {
name: 'codex-json',
create(ctx) {
let buf = ''
return {
onStdout(chunk) {
buf += chunk
const out = []
// parse buf, push SquireEvent values, drop consumed bytes
return out
},
onStderr(chunk) { return [{ type: 'stderr', chunk }] },
onClose() { return [] },
}
},
}
registerSquireAdapter(codexJson)
const squire = new Squire({ binary: 'codex', adapter: 'codex-json' })Adapter registration is process-global and last-write-wins by name. The built-in text-stream adapter is always registered.
needsShell(binary, platform?) is exported for callers that want to share Squire's auto-detection logic. The current rules:
- POSIX (
linux,darwin, etc.): alwaysfalse. - Windows (
win32):.cmd/.bat->true(Node cannot spawn these without a shell).- No file extension ->
true(PATHEXT walk viacmd.exe). .exe/.com/ other ->false(CreateProcess handles them; preserves path-with-spaces).