@dungle-scrubs/harness-cli-normalizer

中文文档
0.9.20发布于 2026/11/10MITES Module构建来源已验证近 30 天下载 2552

A unified interface across multiple coding agents.

原始使用文档
文档目录21 节
  1. Install
  2. Usage
  3. Run a turn
  4. Limit tools and access
  5. Read results as JSON
  6. Run a route from model-router
  7. Keep a session open
  8. Read saved sessions
  9. Answer a question from the model
  10. Concepts
  11. How the three packages fit
  12. Refusal and divergence
  13. Reference
  14. Commands
  15. Exit codes
  16. Config
  17. Security
  18. Contributing
  19. Status
  20. Thanks
  21. License

harness-cli-normalizer

CI npm license: MIT

A unified interface across multiple coding agents.

hcn is a command-line tool for scripts and agents that hand work to coding-agent CLIs. hcn calls each of these CLIs a harness. It currently supports Claude Code, Codex, pi, Muse, Cursor CLI, Antigravity CLI, popeye, and OpenCode CLI. You choose the harness and model for each task and pass the same options to any of them. hcn translates the options into that harness's flags and reports the result as the same JSON events and exit codes. If a harness has no equivalent for an option, hcn exits without starting it and lists the harnesses that do.

$ hcn inspect claude --argv --access read --context-window 200000 --prompt "find bugs in src/"
...
argv: claude -p --effort medium --autocompact 200000 --permission-mode dontAsk --disallowedTools mcp__* --tools Read,Grep,Glob,WebFetch,WebSearch ...

$ hcn inspect codex --argv --access read --context-window 200000 --prompt "find bugs in src/"
...
argv: codex exec --json ... -c model_context_window=200000 --disable memories --sandbox read-only ...

$ hcn inspect pi --argv --access read --context-window 200000 --prompt "find bugs in src/"
pi cannot express option "contextWindow" (200000); supported: effort, provider, ... - drop the option or route this work to a harness that supports it
supported on: claude (--autocompact), codex (-c), popeye (--context-window)

hcn inspect --argv prints the command hcn would run. hcn run takes the same flags and runs it. To have the harness and model chosen for you, put model-router in front of hcn, as described in How the three packages fit.

Install

hcn needs Node 24 or newer and at least one harness CLI, installed and signed in. The CLIs are linked under Thanks. hcn uses each CLI's own login.

npm install -g @dungle-scrubs/harness-cli-normalizer

hcn ls lists the harnesses and the CLI version hcn was verified against for each:

$ hcn ls
[email protected] (npm:@anthropic-ai/claude-code)
[email protected] (npm:@openai/codex)
[email protected] (npm:@earendil-works/pi-coding-agent)
[email protected] (installed:muse)
[email protected] (installed:agent)
[email protected] (installed:agy)
[email protected] (npm:@dungle-scrubs/popeye)
[email protected] (npm:opencode-ai)

To teach your coding agents hcn's flags, events and refusals, install its agent skill:

npx skills add dungle-scrubs/harness-cli-normalizer --skill hcn -g

Usage

Run a turn

$ hcn run claude "name three primes, nothing else" --model haiku
provenance: effort = "medium" (profile)
...
divergence: profile "sandbox" not expressible on claude; harness default applies
env: CLAUDE_CODE_DISABLE_AUTO_MEMORY=1
spawn: claude -p --effort medium [prompt:969ch] --output-format stream-json --verbose --include-partial-messages --model claude-haiku-5-5
provenance: questions = ask (default)
  ● session d8b139a5-14d4-40dc-852e-3ae87d23b221 (harness-minted)
2, 3, 5
  ○ clean (exit 0)

To use another harness, replace claude with codex, pi, muse, cursor, antigravity, popeye, or opencode and keep the other flags. hcn checks --model and --effort against the harness first. On claude, --setting-sources <list> (user, project, local) picks which settings scopes the child loads; runs pass nothing by default and sessions pin project. For example, --model haiku on codex exits 2 and lists the codex models. The stderr lines show where each setting came from (provenance:), what the harness cannot express (divergence:), and the command hcn ran (spawn:). The prompt is longer than what you typed because hcn adds a preamble that tells the model how to ask you a question. See Answer a question from the model.

Limit tools and access

hcn run codex "find bugs in src/" --access read
hcn run claude "update the parser" --access write --no-autonomy
hcn run claude "find bugs in src/" --tools read,grep,glob
hcn run claude "find bugs in src/" --context-window 200000

--access read becomes a closed --tools Read,Grep,Glob,WebFetch,WebSearch set plus --permission-mode dontAsk and an explicit mcp__* deny on Claude Code, --sandbox read-only on Codex, and --tools on pi. --tools and --exclude-tools use the same tool names on every harness: read, write, edit, shell, grep, glob, list, web-fetch, web-search, subagent, and skill. --skills limits skills in the same way.

On Claude Code, --access write selects --permission-mode acceptEdits and requests a strict native sandbox that auto-approves sandboxed Bash. hcn keeps filesystem isolation on, disables unsandboxed retries, and requests that Claude stop if the sandbox cannot start. The sandbox covers Bash, not native file or web tools. Native permission checks and managed policy still apply.

Strict mode also limits network access. Unless managed policy overrides strict mode, Claude ignores project-level domain allowlists. Dependency installs and tests may need trusted user or managed network policy and writable paths. hcn adds neither domains nor extra writable roots. With --access write --autonomy, Claude keeps the explicit Bash sandbox while its permission mode becomes bypassPermissions. That mode can approve hosts outside the network allowlist unless strictAllowlist, allowManagedDomainsOnly, or native denied-domain policy blocks them. Claude documents that matrix for interactive terminal sessions, and hcn has not verified it headless. A native httpProxyPort or socksProxyPort moves filtering to that custom proxy, outside Claude's own domain controls. Keeping the filesystem sandbox does not promise network confinement. Autonomy alone does not request this sandbox. See the access mappings and native limits.

On Cursor, --access read selects native ask mode. Cursor write access is unconfined: --access write renders --force, allowing commands and bypassing workspace trust. It does not provide a workspace sandbox; native explicit denials still apply.

Read results as JSON

$ hcn run claude "name three primes, nothing else" --model haiku --json
{"kind":"identity","sessionId":"7e3712eb-76cf-4c00-a72a-ba08ab1f78db","authority":"harness-minted","capabilities":{...}}
...
{"kind":"message","role":"assistant","text":"2, 3, 5"}
{"kind":"done","exitCode":0,"cause":"clean","escalation":{"mode":"ask","detection":"none"}}

--json prints one JSON event per line. Native tool outputs arrive as tool-result events, including structured extension results and error flags when reported. Native token counts and cost estimates arrive as scoped usage events. A clean done does not mean every tool succeeded; check the tool results. Native tool-denial reports can also produce error.denial observations. hcn reports each valid entry in Claude's final permission_denials with the tool name, optional call ID, and a bounded input summary. A turn with denial observations adds a positive done.denialCount; human output adds [denials: N]. The count reports observations, not unique calls or task correctness. A missing count does not establish success: a failed command can produce only tool-result.isError. See the event contract. The format is the same for every harness, and the last line is always done. On a nonzero run exit, done.completion preserves output evidence: native-terminal means the harness reported successful turn completion; message-delivered means assistant text arrived without confirmed turn completion. The process failure remains in cause/failure, and exitCode keeps the harness's actual status. If the run failed, done.failure.retryable says whether a retry can help, and done.failure.class gives the reason, such as rate-limit, auth, transport, or rejected. A refusal also lists the harnesses that support the option:

$ hcn run pi "find bugs in src/" --context-window 200000 --json
{"kind":"failure","class":"rejected","retryable":false,...,"supportedBy":[{"harness":"claude","spelling":"--autocompact"},{"harness":"codex","spelling":"-c"},{"harness":"popeye","spelling":"--context-window"}]}
{"kind":"done","exitCode":null,"cause":"failed","failure":{...}}

Run a route from model-router

$ model-router '{"task":"code-review","stakes":"normal","privacy":"normal"}' \
    | jq -c '.routes[0] | {harness, modelId, effort}'
{"harness":"pi","modelId":"zai/glm-5.3","effort":"xhigh"}
$ hcn run pi --model zai/glm-5.3 --effort xhigh --access read "review the diff on this branch"

A route has a harness, a model id, an effort, and for some routes a provider. Pass them to hcn run as the harness argument, --model, --effort, and --provider. If the run fails with retryable: true, run the next route in .routes. The routes come from your registry, so yours will differ.

Keep a session open

$ printf '%s\n' '{"op":"send","id":"in-1","text":"name three primes, nothing else"}' \
    | hcn session claude --model haiku --json
{"kind":"session","sessionId":"230f0d30-47d8-49bd-bcfc-ba2c5008776c","harness":"claude","hcn":"0.9.8","questions":"ask","origin":"fresh"}
{"kind":"disposition","id":"in-1","disposition":"started"}
{"kind":"turn","turnId":"230f0d30-47d8-49bd-bcfc-ba2c5008776c:turn-1","id":"in-1"}
...
{"kind":"message","role":"assistant","text":"2, 3, 5"}
{"kind":"done","exitCode":null,"cause":"clean",...}
{"kind":"closed","exitCode":0,"cause":"clean"}

With --json, you write commands to stdin, one JSON object per line (send, answer, interrupt, settings, approval, close), and read events from stdout. Each send gets one disposition; turns opened by a send carry its id. Check the session packet's controls before choosing delivery queue or steer, or interrupting and sending a replacement after confirmation. On a Claude session, --native-approvals forwards each permission request, AskUserQuestion and plan approval to you as approval-request events; see the reference. On Claude and pi sessions, the settings op changes model and effort mid-session, and send takes "verbatim": true to type the harness's own slash command untouched (Claude slash commands; on pi only extension commands such as /skill:x run); see the reference. Sessions work with Claude Code, Codex, pi, Antigravity CLI, and popeye. Without --json, hcn session claude opens an interactive prompt.

Read saved sessions

$ hcn transcript ls --limit 1
{"schemaVersion":1,"kind":"session-list-source","hcnVersion":"0.9.8","harnesses":["claude","codex","pi","muse","cursor","antigravity","popeye"],...}
{"schemaVersion":1,"kind":"session","harness":"claude","id":"1daeb0ec-1d9c-4461-8e5c-84ca8c6ee3f6",...,"mode":"interactive","readable":true,"blocked":null}
{"schemaVersion":1,"kind":"session-list-result","exitCode":0,"status":"complete","rowsReturned":1,"more":true,...}
$ hcn transcript read claude --id 1daeb0ec-1d9c-4461-8e5c-84ca8c6ee3f6 > transcript.jsonl

hcn transcript ls lists the saved sessions of every harness for the current directory, newest first, in one format. hcn transcript read exports the messages and tool results of one session without starting the harness. Support differs by harness; see docs/transcripts.md.

Answer a question from the model

$ hcn run claude "Add a database to the new service. Postgres or SQLite are both fine and the choice is mine, not yours." --model haiku --access read --json
...
{"kind":"question","question":"Which database should the new service use, ...","options":["SQLite","Postgres"],"recommended":"SQLite"}
{"kind":"done","exitCode":0,"cause":"awaiting-input","escalation":{"mode":"ask","detection":"block"}}
$ hcn run claude --resume 98ba94a4-f51b-49ef-b515-2e2202a4fc0e --prompt "Postgres"

By default, hcn's preamble tells the model how to ask a question when it cannot make a decision itself. The turn then ends with a question event and a done event with cause awaiting-input (exit 0). Ask your user, then resume the session with the answer, using the sessionId from the identity event. With --questions assume, the model states its assumption and continues instead.

Concepts

How the three packages fit

task ──▶ model-router ──▶ routes, best first ──▶ hcn run <harness> --model <id> --effort <level>
              ▲                                              │
              │                                              └─ retryable failure: next route
       model-registry

Together, the three packages route each job to a model and harness automatically, following your preferences. model-registry defines the registry file: the models you can use, the harnesses that reach them, and ratings for each. model-router reads the registry, takes a task, and returns routes ranked best first. Each route is a harness, a model, and an effort. hcn runs the route. If the run fails with retryable: true, run the next route. popeye is a harness built for hcn to drive, and a registry can list routes through it like any other harness. Each package also works on its own. hcn runs any harness and model you give it, and model-router ranks routes without running anything.

Refusal and divergence

If you pass an option the harness cannot express, hcn exits 2 without starting the harness and lists the harnesses that support the option. If the option comes from hcn's defaults instead of from you, hcn prints a divergence: line to stderr, and the harness uses its own default.

Reference

Commands

hcn run <harness> [prompt]     Run one headless turn
hcn session <harness>          Keep one process open across turns (--json for programs)
hcn interactive <harness>      Resume a native session in your terminal, with a control pipe
hcn inspect <harness>          Preview argv, capabilities, runtime, context, or saved settings
hcn transcript ls              List saved native sessions, newest first
hcn transcript read <harness>  Export one saved session as JSONL
hcn ls                         List harnesses and the CLI version each was verified against
hcn check                      Compare those versions with the latest releases

<harness> is one of claude, codex, pi, muse, cursor (binary agent), antigravity (binary agy), popeye, or opencode. hcn <command> --help lists every flag.

Session send and answer commands accept delivery: "queue" or "steer" where supported. The session controls reference covers per-harness support and the interrupt request and confirmation receipts.

Exit codes

CodeMeaning
0The turn finished, including a turn that ends with a question
1The harness or the transport failed; done.failure says why
2Usage error or refusal; the harness did not start
4A bug in hcn; a --json stream still ends with failure and done

Config

hcn takes each setting from the first source that sets it:

flags > .hcn/config.json (git root) > ~/.config/hcn/config.json > hcn defaults > harness default
{ "version": 1, "effort": "high", "access": "read", "toolsets": { "review": ["read", "grep"] } }

Keys: effort, model, provider, sandbox, contextWindow, tools, excludeTools, autonomy, write, shell, memory, maxSteps, toolsets, timeout, questions, systemPrompt, appendSystemPrompt, toolMap, and access.

The user config also accepts claudeConfigDir, an absolute path for Claude's launch context. Inherited CLAUDE_CONFIG_DIR and per-run --env choices take precedence. See configuration and account context.

hcn's defaults are effort medium, autonomy off, memory off, and discovery on. Codex and Antigravity CLI also get sandbox workspace-write, and Codex and popeye get a 272,000-token context window. In a project config, tools also caps what --tools can grant, and a request for more exits 2. An unknown key or invalid JSON exits 2 with the problem in the message.

Security

  • hcn stores no credentials. Each harness signs in with your own account.
  • hcn masks secret-like strings from harness stderr in failure messages: sk- keys and token=, key=, secret=, and password= pairs.
  • Each invocation appends a start line and an end line to hcn/commands.jsonl under $HCN_STATE_DIR, $XDG_STATE_HOME, or ~/.local/state.
  • If HCN_OBSERVER is set to an executable, hcn run sends its events to that program's stdin. See docs/observer.md. If it is not set, hcn sends nothing.

docs/reference.md has the full flag table, the tool names for each harness, the event and failure fields, the session protocol, and the native-session commands (terminal resume, Codex approvals, and saved settings).

Contributing

You need Node 24 or newer, pnpm, and Bun.

pnpm install
pnpm setup:hooks           # once, from the primary checkout root: installs the Git hooks
pnpm check                 # full verification, including hook readiness and placement
bun run demo claude "hi"   # run a harness and print its event stream

Do not break the layer rules that test/interpretation/purity.test.ts and test/no-chat-imports.test.ts enforce, and do not edit test/fixtures/. Commits follow Conventional Commits. See CONTRIBUTING.md for the rest, and docs/harness-updates.md for how to verify a harness against a new CLI version.

Status

hcn is pre-1.0. It is not a harness and does not run a model itself. It adds a layer between your program and the harness, so if you use only one CLI, you may prefer to call that CLI directly. ROADMAP.md lists planned work, including new harnesses. Support differs by harness:

  • Sessions work with Claude Code, Codex, pi, Antigravity CLI, and popeye. Muse, Cursor CLI, and OpenCode CLI run single turns only.
  • hcn interactive supports Codex only.
  • --native-approvals supports Codex one-shot responses and Claude sessions. Saved-settings inspection supports Codex and pi.
  • The weekly version check in CI covers the harnesses published to npm. Muse, Cursor CLI, and Antigravity CLI are checked locally with their own version commands.

Thanks

hcn runs these CLIs: Claude Code, Codex, pi, Muse (installed from source, not on a registry), Cursor CLI (installed via script, binary agent, not on a registry), Antigravity CLI, popeye, and OpenCode CLI.

License

MIT. See LICENSE.

版本与发布记录

@dungle-scrubs/harness-cli-normalizer 共发布 77 个版本,首次发布于 2026/08/13;过去 12 个月发布了 77 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.9.20latest2026/11/103 天前
0.9.192026/11/103 天前
0.9.182026/11/076 天前
0.9.172026/11/049 天前
0.9.162026/11/049 天前
0.9.152026/11/0310 天前

关于 @dungle-scrubs/harness-cli-normalizer 的常见问题

@dungle-scrubs/harness-cli-normalizer 是什么?

A unified interface across multiple coding agents.

如何安装 @dungle-scrubs/harness-cli-normalizer?

在项目目录运行 npm install @dungle-scrubs/harness-cli-normalizer,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 hcn,可以用 npx hcn 直接运行。

@dungle-scrubs/harness-cli-normalizer 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/dungle-scrubs__harness-cli-normalizer 是否存在。

@dungle-scrubs/harness-cli-normalizer 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@dungle-scrubs/harness-cli-normalizer 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=24。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@dungle-scrubs/harness-cli-normalizer 可以商用吗?

@dungle-scrubs/harness-cli-normalizer 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@dungle-scrubs/harness-cli-normalizer 的下载量有多少?

根据 npm 官方下载统计,@dungle-scrubs/harness-cli-normalizer 在 2026/10/13 – 2026/11/11 这 30 天内被下载 2,552 次,日均约 85 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@dungle-scrubs/harness-cli-normalizer 还在维护吗?

最新版本 0.9.20 发布于 2026/11/10(3 天前);至今共发布 77 个版本,其中过去 12 个月发布了 77 个;首次发布于 2026/08/13。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。