@ladbabynpm/picc-claude-shim
v0.1.10
Published
Drop-in replacement for Claude Code CLI. Speaks Claude Code's NDJSON protocol (--output-format stream-json / --input-format stream-json / --permission-prompt-tool stdio) so that third-party tools like hapi can drive pi instead of Claude Code. Internally c
Maintainers
Readme
picc-claude-shim
Drop-in replacement for the Claude Code CLI, by disguising pi as Claude Code. Part of picc, a pi agent setup mirroring Claude Code's harness. Softwares built upon Claude Code, like hapi and T3 Code, can directly replace Claude Code with pi.
The shim parses the Claude-flavored CLI flags, creates a pi AgentSession, and translates bidirectionally between pi session events and Claude Code's stream-json wire protocol (system/assistant/result/control_request/control_response).
It also mirrors each turn into a Claude/hapi-compatible session JSONL and reports a Claude-style version, so hosts that probe claude --version or scan ~/.claude/projects/**/<session-id>.jsonl see exactly what they would see against a real Claude Code install.
Modes
| Mode | Invocation | Purpose |
|------|------------|---------|
| stream-json | --output-format stream-json --input-format stream-json | The primary host target. Bidirectional NDJSON over stdin/stdout; speaks the Claude Code SDK protocol and bridges a live pi session. |
| json | --output-format json | One-shot single-JSON-object output. With --json-schema <schema> emits {"structured_output": <value>} (t3code's commit/PR/title text-generation parser); without, emits a single Claude result message. Prompt arrives inline or on stdin. |
| print | --print <prompt> (or bare -p) | One-shot text output. Streams the assistant's final text to stdout and exits 0. Used for quick smoke tests. |
| local (TTY) | (default, no JSON flags) | Not supported. Emits an explicit error and exits 1 — pi's interactive TUI uses Ink and would conflict with a host's terminal handling. |
--version and --help are answered by a fast JS path (bin/claude.js) without loading the pi
runtime. At package installation, the shim fetches the latest official Claude Code GitHub release
once and caches its version. That cached version is used by --version, the startup banner,
system/init, and session JSONL metadata. Normal shim invocations never refresh it; a failed
install-time fetch preserves an existing cache or falls back to the bundled version.
T3 Code's no-prompt capability check uses a lightweight protocol path, so account metadata is available without loading pi or waiting for the full runtime to initialize.
Install
Install as a pi package — one command does the whole job:
pi install npm:@ladbabynpm/picc-claude-shimpi records npm:@ladbabynpm/picc-claude-shim in ~/.pi/agent/settings.json#packages and installs
the package itself into its managed npm directory:
~/.pi/agent/npm/node_modules/@ladbabynpm/picc-claude-shim/pi loads the package's pi.extensions manifest directly from there, so no manual registration is
needed. The package's postinstall hook (install.js) additionally materializes a
dedicated, deterministic launcher directory that does not collide with other software and never
changes PATH:
~/.pi/agent/extensions/picc-claude-shim/bin/That directory is the recommended thing to point third-party applications at, because it is
guaranteed to exist: if a package manager skips the postinstall hook, the shim recreates it on
the first real (non---version) invocation. Both locations are valid entry points.
On Windows, configure third-party applications to execute:
~/.pi/agent/extensions/picc-claude-shim/bin/claude.exeThe installer writes picc-claude-shim-root.txt beside the executable so the copied native
launcher can resolve its npm-managed runtime after package upgrades. On POSIX systems, configure
~/.pi/agent/extensions/picc-claude-shim/bin/claude instead.
Resolving the pi runtime
The drop-in binary runs outside pi, so it locates the @earendil-works/pi-coding-agent install
itself, in this order (first match wins):
PICC_CLAUDE_SHIM_PI_DIR— explicit override to the pi-coding-agent directory (highest priority).- the nearest
node_modules/@earendil-works/pi-coding-agentwalking up from the package. - a global Bun root (
$BUN_INSTALL/install/global/node_modules,~/.bun/install/global/node_modules) — bun-compiled hapi runners commonly live here. - the global npm root (
npm root -g). - pi's managed install, resolved through
~/.pi/agent/install/current-versionto~/.pi/agent/install/releases/<version>/node_modules, then legacy pi-managed npm roots (~/.pi/agent/npm/node_modules,~/.pi/agent/node_modules,~/.pi/node_modules).
If none are found, the shim prints a clear error showing the PICC_CLAUDE_SHIM_PI_DIR override.
Set that variable in the environment of whatever process spawns claude (e.g. the hapi runner) to
point at your pi install:
PICC_CLAUDE_SHIM_PI_DIR=/path/to/node_modules/@earendil-works/pi-coding-agentFlags
Parsed by parseClaudeArgs (src/args.ts). Unrecognized flags are collected and reported on the
startup banner but do not fail the run.
| Flag | Effect |
|------|--------|
| --output-format stream-json / --input-format stream-json | Enable stream-json mode. |
| --output-format json | Enable json (one-shot) mode. |
| --print <prompt> / -p | Enable print mode. Bare -p reads the prompt from stdin. |
| --permission-prompt-tool stdio | Accepted. The permission gate is in practice open for any non-bypassPermissions mode. |
| --permission-mode <mode> / --dangerously-skip-permissions | bypassPermissions closes the gate entirely; other modes are forwarded to PICC_PERMISSION_MODE for @ladbabynpm/picc-permission-modes. |
| --system-prompt <text> | Replace the pi system prompt. |
| --append-system-prompt <text> | Append to the pi system prompt. |
| --resume <id> | Open the existing pi session with that id (falls back to a fresh session if not found). |
| --continue | Continue the most recent pi session in the cwd. |
| --allowed-tools <list> / --disallowed-tools <list> | Permission rules, not tool activation: --allowed-tools pre-approves matching tool names; --disallowed-tools removes matching pi tools. |
| --model <id> | Parsed, surfaced in system/init until the real pi model is known. See Known limitations. |
| --max-turns <n> | Parsed but not enforced. |
| --include-partial-messages | When set, the translator emits partial assistant message updates. |
| --json-schema <schema> | (json mode only) Emit {"structured_output": <value>} decoded against the schema. |
Tool name mapping
Mappings in src/tool-names.ts are case-insensitive in both directions to match picc's
registered tool names, which may be lowercase (read, bash, grep) or PascalCase (Read,
Edit, Glob). pi find maps to Claude Glob (picc-glob accepts both Glob and find); the
other built-ins map identity-modulo-case. Unknown tools (TodoWrite, WebSearch, etc.) have no pi
equivalent — if a tool-call gate asks permission for one, the shim replies with
control_response{behavior:"deny"}.
| pi (canonical) | Claude wire name |
|----------------|------------------|
| read | Read |
| write | Write |
| edit | Edit |
| bash | Bash |
| grep | Grep |
| find | Glob |
| ls | LS |
Testing
Run the unit tests:
node test/run-all.mjsTests cover:
args.test.ts— flag parsing (--output-format,--permission-prompt-tool, etc.)tool-names.test.ts— pi ↔ Claude tool name mapping (case-insensitive)session-jsonl.test.ts— hapi JSONL writersession-resume.test.ts— resume id resolutioncost.test.ts— usage + cost synthesispermission-gate.test.ts— when the permission gate is open/closedtranslator.test.ts— stdin → pi callstranslator-out.test.ts— pi events → Claude NDJSON (result fields, control_request)compact.test.ts—/compactparsing and interceptionskills.test.ts— skill discovery + expansionstructured-output.test.ts—--json-schemavalue extraction
entry.e2e.test.ts is a separate live-backend test (needs a configured pi model + API key) and is
not part of run-all.mjs. Run it with:
node test/run-e2e.mjsFiles
picc-claude-shim/
├── package.json # @ladbabynpm/picc-claude-shim; jiti dep, pi peer deps
├── tsconfig.json
├── install.js # postinstall entry point
├── installer-lib.mjs # copies explicit launchers under the pi extension directory
├── README.md
├── scripts/
│ ├── build-exe.mjs # build native claude.exe (Claude Agent SDK spawn path on Windows)
│ └── claude_launcher.c
├── bin/
│ ├── claude.js # Node entry; --version/--help fast path, else imports src/entry.ts via jiti
│ ├── claude.cmd # Windows entry template (install.js fills in the path)
│ ├── claude # POSIX entry
│ ├── claude.exe # native entry (built by scripts/build-exe.mjs)
│ └── entry-slow.mjs # jiti loader; aliases pi-coding-agent to pi's install
├── src/
│ ├── index.ts # no-op extension factory; registers --claude-shim-install flag
│ ├── entry.ts # orchestrator: args → pi session → protocol loop (all modes)
│ ├── args.ts # parseClaudeArgs — Claude-flavored argv parser
│ ├── translator.ts # bi-directional event/message translation
│ ├── session-jsonl.ts # hapi-compatible session JSONL writer
│ ├── tool-names.ts # case-insensitive pi ↔ Claude tool name mapping
│ ├── cost.ts # synthesizeUsageAndCost from pi's SessionStats
│ ├── skills.ts # Claude skill discovery + expansion
│ ├── structured-output.ts # --json-schema value extraction
│ └── version.js # version string the shim impersonates
└── test/ # unit + e2e tests (see Testing)Known limitations
--modelis parsed and surfaced insystem/initbut does not select a model; pi picks from~/.pi/agent/settings.json#defaultModel. Honoring arbitrary provider IDs requires plumbing through pi'sModelRuntimeregistry.--max-turnsis parsed but not enforced; the session runs as many turns as the agent decides.- Hook forwarder commands (the
hapi hook-forwarderinvocation Claude Code runs on SessionStart) are not triggered. Hosts tolerate this whensystem/initand the JSONL file are both present.
Differences from Claude Code
These are intentionally out of scope for picc-claude-shim.
- No local (TTY) mode — stream-json, json, and print only.
- No hooks —
--settingsSessionStart hook forwarding is not invoked. - No
--modelselection — pi's default model is used. - No
--max-turnsenforcement. - Skill inline bash — Claude Code executes inline
!`…`injections in a skill body before sending; the shim forwards the substituted body as-is and lets pi's agent run such commands via its own tools. - No
.agents/skillsscan — only the user and project.claude/skillsroots Claude Code verifies are scanned. - No
--output-format stream-jsonpartial streaming by default — set--include-partial-messagesto emit partial assistant updates.
