@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

Packages

Package details

extension

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

npm downloads

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 writer
  • session-resume.test.ts — resume id resolution
  • cost.test.ts — usage + cost synthesis
  • permission-gate.test.ts — when the permission gate is open/closed
  • translator.test.ts — stdin → pi calls
  • translator-out.test.ts — pi events → Claude NDJSON (result fields, control_request)
  • compact.test.ts — /compact parsing and interception
  • skills.test.ts — skill discovery + expansion
  • structured-output.test.ts — --json-schema value 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

  • --model is parsed and surfaced in system/init but does not select a model; pi picks from ~/.pi/agent/settings.json#defaultModel. Honoring arbitrary provider IDs requires plumbing through pi's ModelRuntime registry.
  • --max-turns is parsed but not enforced; the session runs as many turns as the agent decides.
  • Hook forwarder commands (the hapi hook-forwarder invocation Claude Code runs on SessionStart) are not triggered. Hosts tolerate this when system/init and 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 — --settings SessionStart hook forwarding is not invoked.
  • No --model selection — pi's default model is used.
  • No --max-turns enforcement.
  • 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/skills scan — only the user and project .claude/skills roots Claude Code verifies are scanned.
  • No --output-format stream-json partial streaming by default — set --include-partial-messages to emit partial assistant updates.