[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-81780":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":13,"openIssues":14,"contributorsCount":14,"subscribersCount":14,"size":14,"stars1d":14,"stars7d":15,"stars30d":16,"stars90d":14,"forks30d":14,"starsTrendScore":14,"compositeScore":17,"rankGlobal":9,"rankLanguage":9,"license":18,"archived":19,"fork":19,"defaultBranch":20,"hasWiki":21,"hasPages":19,"topics":22,"createdAt":9,"pushedAt":9,"updatedAt":23,"readmeContent":24,"aiSummary":25,"trendingCount":14,"starSnapshotCount":14,"syncStatus":26,"lastSyncTime":27,"discoverSource":28},81780,"claude-pee","sbhattap\u002Fclaude-pee","sbhattap","Use Claude Code programmatically as an interactive user",null,"Rust",55,4,50,0,1,5,43.1,"MIT License",false,"main",true,[],"2026-06-12 04:01:35","# claude-pee\n\nA drop-in front-end for the [`claude`](https:\u002F\u002Fdocs.claude.com\u002Fen\u002Fdocs\u002Fclaude-code) CLI that:\n\n- spawns `claude` in a PTY,\n- assigns a fresh `--session-id \u003CUUIDv4>`,\n- forwards every other flag verbatim,\n- optionally injects a one-shot prompt via `-p`,\n- tails the matching session transcript jsonl, and\n- prints just the assistant's reply to stdout (`text` by default, or `json`\u002F`stream-json`).\n\nTermination is driven by claude's own **Stop hook** — no flaky screen-idle heuristics. When claude finishes a turn, the hook touches a sentinel file; claude-pee sees it, sends `\u002Fexit`, and exits with the child's status code.\n\n## Install\n\n```bash\ncargo build --release\ncp target\u002Frelease\u002Fclaude-pee ~\u002F.local\u002Fbin\u002F      # or anywhere on PATH\n```\n\nRequires Rust 1.85+ (edition 2024).\n\n## Quick start\n\n```bash\nclaude-pee -p \"what is 2 + 2\"\n# → 4\n```\n\n## Drop-in for `claude`\n\nThe simplest way to make `claude` go through claude-pee everywhere you already type it:\n\n```sh\n# ~\u002F.zshrc \u002F ~\u002F.bashrc\nalias claude='claude-pee'\n```\n\nThis is safe because shell aliases are resolved only in interactive shells. When claude-pee internally does `spawn(\"claude\")`, it goes through `$PATH` and finds the real binary — no recursion.\n\nIf you need the replacement visible to scripts too, put a `claude` shim earlier in `$PATH` and point `CLAUDE_PEE_EXEC` at the real claude:\n\n```sh\nln -s \"$(which claude-pee)\" ~\u002Fbin\u002Fclaude\nexport CLAUDE_PEE_EXEC=\u002Fusr\u002Flocal\u002Fbin\u002Fclaude   # absolute path to the real one\n```\n\n## Flags\n\nOwned by claude-pee (consumed, never forwarded):\n\n| Flag | Purpose |\n| --- | --- |\n| `-p PROMPT` \u002F `-p=PROMPT` | One-shot prompt to inject. Triggers auto-`\u002Fexit` after claude responds. |\n| `--output-format text\\|json\\|stream-json` | What to print on stdout. Default `text`. |\n\nEverything else is passed through to `claude` after `--session-id \u003CUUID> --settings \u003Chook-json>`. So `claude-pee --permission-mode plan -p hi` becomes:\n\n```\nclaude --session-id \u003CUUID> --settings '\u003Cjson>' --permission-mode plan\n```\n\n## Output formats\n\n| Format | What goes to stdout |\n| --- | --- |\n| `text` (default) | The assistant's plain text reply. Thinking-only and tool-use-only turns are skipped. |\n| `json` | The assistant message transcript line, verbatim JSON (one per turn). Result lines too. |\n| `stream-json` | Every transcript line as it lands, verbatim. |\n\nDiagnostic logs go to **stderr** via [`log`](https:\u002F\u002Fdocs.rs\u002Flog) + `env_logger`. Default level `info` (silent). Set `RUST_LOG=debug` for the per-line tailer trace; `RUST_LOG=trace` also surfaces \"tailing &lt;path&gt;\" and \"injecting \u002Fexit\".\n\n## Environment variables\n\n| Variable | Default | Effect |\n| --- | --- | --- |\n| `CLAUDE_PEE_EXEC` | `claude` | Binary to spawn. Empty value behaves like unset. |\n| `CLAUDE_PEE_QUIESCE_MS` | `500` | Milliseconds the TUI must be idle before the prompt is injected. Heuristic — needed because the Stop hook can't fire before the first turn. Set higher if your machine is slow. Must be `> 0`. |\n| `CLAUDE_PEE_INJECT_CHAR_DELAY_MS` | `0` | Per-character typing delay (simulates human typing or works around input rate-limiters). |\n| `RUST_LOG` | `info` | Standard env_logger filter. |\n\n## How termination works\n\n1. At startup claude-pee computes a sentinel path `${TMPDIR}\u002Fclaude-pee-\u003CUUID>.done` and builds a `--settings` JSON containing:\n\n   ```json\n   {\n     \"hooks\": {\n       \"Stop\": [\n         { \"hooks\": [ { \"type\": \"command\", \"command\": \"touch '\u003Csentinel>'\" } ] }\n       ]\n     }\n   }\n   ```\n\n   That gets passed to `claude --settings …` so claude registers the hook for this session only — no permanent config changes.\n\n2. claude-pee waits for the PTY screen to go quiescent (so the TUI is ready to receive input), then types the prompt + `\\r`.\n\n3. When claude finishes its turn, it runs the registered `Stop` hook, which `touch`es the sentinel.\n\n4. claude-pee sees the sentinel appear, sends `\u002Fexit\\r\\n`, and waits for the child to exit.\n\n5. The sentinel is removed on exit via an RAII guard (even on error\u002Fpanic paths).\n\n## Examples\n\n```bash\n# Text answer only\nclaude-pee -p \"tell me a joke\"\n\n# Full assistant message as JSON (one line)\nclaude-pee -p \"tell me a joke\" --output-format json | jq\n\n# Stream every transcript event live\nclaude-pee -p \"do a long task\" --output-format stream-json | jq -c .\n\n# Override the executable (e.g. point at a build)\nCLAUDE_PEE_EXEC=~\u002Fcode\u002Fclaude\u002Fdist\u002Fclaude claude-pee -p hi\n\n# Slow down injection (simulate typing)\nCLAUDE_PEE_INJECT_CHAR_DELAY_MS=50 claude-pee -p hello\n\n# Bigger pre-prompt grace window on a slow machine\nCLAUDE_PEE_QUIESCE_MS=2000 claude-pee -p hi\n\n# Diagnose what the tailer is doing\nRUST_LOG=debug claude-pee -p hi\n```\n\n## Caveats\n\n- **Without `-p` it's a passthrough** — no auto-`\u002Fexit`, so you'll need to exit claude yourself (interactive use). Useful if you just want the session-id assignment and the transcript path printed (set `RUST_LOG=trace`).\n- The pre-prompt **quiescence wait is heuristic** — the Stop hook can't help here because no turn has happened yet. If you see the prompt being typed before claude is ready, raise `CLAUDE_PEE_QUIESCE_MS`.\n- Depends on claude honouring `--session-id`, `--settings`, and `\u002Fexit`. Tested against current claude code; if the schema changes upstream, this could break.\n- Stdout is for the parsed output only. There is no \"echo the raw PTY to my stdout\" mode (it would interleave with the parsed output).\n\n## Source layout\n\n```\nsrc\u002Fmain.rs        entry + run() orchestration\nsrc\u002Fargs.rs        CLI parsing\nsrc\u002Fconfig.rs      Config: args + env → resolved runtime config\nsrc\u002Fchild.rs       PTY spawn, drain, vt100 screen hashing\nsrc\u002Fhook.rs        Stop-hook sentinel + --settings JSON + RAII cleanup\nsrc\u002Finject.rs      auto-inject: TUI wait → prompt → sentinel → \u002Fexit\nsrc\u002Ftranscript.rs  jsonl discovery + tail + per-format output\n```\n\nLints are strict (`clippy::all`\u002F`pedantic`\u002F`nursery`\u002F`cargo` denied, `unsafe_code` forbidden, no `unwrap`\u002F`expect`\u002F`panic` outside tests). `pre-commit` runs `cargo fmt`, `check`, `clippy`, `test`, and `doc` on every commit.\n","claude-pee 是一个用于以编程方式与 Claude 代码进行交互的前端工具。它通过在伪终端中启动 Claude，并为其分配一个新的会话 ID，同时支持直接注入一次性提示、转发其他标志参数，并可选择输出格式（文本或 JSON）。该项目利用 Rust 编程语言构建，确保了高效稳定的运行性能。适用于需要自动化或脚本化调用 Claude API 的场景，如集成到 CI\u002FCD 流水线、开发测试环境或是任何希望简化与 Claude 服务交互过程的地方。安装简便，只需基本的 Rust 环境即可快速上手使用。",2,"2026-06-11 04:06:40","CREATED_QUERY"]