@ladbabynpm/picc-claude-shim
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
Package details
Install @ladbabynpm/picc-claude-shim from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@ladbabynpm/picc-claude-shim- Package
@ladbabynpm/picc-claude-shim- Version
0.1.6- Published
- Sep 30, 2026
- Downloads
- 120/mo · 18/wk
- Author
- ladbabynpm
- License
- MIT
- Types
- extension
- Size
- 277.1 KB
- Dependencies
- 1 dependency · 3 peers
Pi manifest JSON
{
"extensions": [
"./src/index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
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-shim
pi install runs npm install, which fires the package's postinstall hook (install.js). The
hook creates a dedicated, deterministic launcher directory that does not collide with other
software and never changes PATH:
~/.pi/agent/extensions/picc-claude-shim/bin/
On Windows, configure third-party applications to execute:
~/.pi/agent/extensions/picc-claude-shim/bin/claude.exe
The 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.
pi also records npm:@ladbabynpm/picc-claude-shim in ~/.pi/agent/settings.json#packages and
loads the pi.extensions manifest itself, so no manual registration is needed.
Flags
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> |
Allow/deny tools, mapped through the tool-name table below. |
--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.mjs
Tests 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.mjs
Files
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.