host integration playbook
September 14, 2026 ยท View on GitHub
this document is the implementation checklist for adding a new host integration (like codex, claude-code, codebuddy, cursor, pi).
when to use this
use this before opening a PR that adds tokenjuice install <host> or any new hook/adapter path.
design decisions first
define the host hook model before writing code:
- can the host rewrite shell input before execution?
- can the host replace shell output after execution?
- are hooks file-based, extension-based, or api-based?
- does the host return plain text, structured json, or both?
pick one integration mode:
- post-tool compaction (preferred when shell output can be replaced safely)
- pre-tool command wrapping (use
tokenjuice wrapwhen post replacement is unavailable)
for pre-tool wrapping, preserve shell semantics (for example bash -lc '<cmd>') and ensure classification normalization can recover the nested command.
implementation checklist
for a new host adapter in src/hosts/<host>/index.ts:
- install flow
- write/update host config atomically
- preserve unrelated keys
- keep installation idempotent (replace prior tokenjuice entry, keep non-tokenjuice entries)
- doctor flow
- detect disabled / warn / broken / ok states
- validate expected command against installed command
- report missing executable paths
- return a single repair command
- runtime hook flow
- parse hook payload defensively
- skip non-target tools/events early
- preserve explicit raw bypass behavior
- never throw hard on hook input parse failures
then wire CLI + exports:
src/cli/main.tsinstall <host>doctor <host>- usage text
- runtime hook entry command if needed
src/index.ts- runtime/install/doctor exports
- result/report type exports
src/hosts/shared/hook-doctor.ts- add the host to aggregate doctor report
symlinked settings
Claude Code, CodeBuddy, and Droid update the resolved settings file while preserving its symlink and permissions. Broken links must have their target restored before installing. The writer checks for changed contents or a retargeted link before replacing the file and asks you to retry when it detects another edit. Avoid editing settings concurrently with install or uninstall: portable filesystem rename cannot make that check and replacement atomic against another writer.
test strategy (required)
add host-specific tests and aggregate tests:
test/hosts/<host>.test.ts- install idempotency
- preserve unrelated config fields
- doctor status matrix (
disabled,warn,broken,ok) - runtime behavior (rewrite/skip/bypass paths)
- aggregate coverage
- update tests for
doctorInstalledHooksif the new host is included there
- update tests for
critical test isolation rules
new adapters often fail CI/local due to leaked machine config. isolate host homes explicitly:
- set and reset host env vars in each suite (
CODEX_HOME,CLAUDE_CONFIG_DIR,CLAUDE_HOME,CURSOR_HOME,PI_CODING_AGENT_DIR,OPENCODE_CONFIG_DIR,XDG_CONFIG_HOME,COPILOT_HOME,TOKENJUICE_CLAUDE_CODE_SHELL,TOKENJUICE_CURSOR_SHELL,SHELL, etc.) COPILOT_HOMEaffects copilot-cli only; VS Code Copilot Chat ignores it and always resolves under$HOME/.copilot/hooks/. If your adapter should not depend onCOPILOT_HOME, add an explicit test that sets it to a decoy path and asserts the install does not land there.- avoid reading real
~/.<host>in tests - use temp dirs for all config paths
- restore
PATHafter each test
shared hook dirs (hazard)
copilot-cli and vscode-copilot both read every *.json under ~/.copilot/hooks/. install each host under a per-host filename (tokenjuice-cli.json and tokenjuice-vscode.json) so neither host's install overwrites the other. doctor for both hosts scans sibling files and reports stray tokenjuice entries.
if you add a new host to aggregate doctor logic, existing aggregate tests may start failing unless they set that host's env home to temp storage.
shared Codex hooks renderer
when codex-hooks is executable on PATH, the Codex installer registers the
top-level {"hooks": ...} fragment as tokenjuice.post-tool-use and leaves the
effective hooks.json write to that renderer. uninstall unregisters the same
integration id. install and uninstall both provide the detected legacy
Tokenjuice group as an owned-source manifest so the renderer can adopt or
remove pre-fragment standalone state. without the renderer, Tokenjuice can update a regular
standalone hooks file with a source-content check; it refuses to replace an
externally owned symlink.
standalone install and uninstall remove only Tokenjuice commands from a matcher group and preserve nested custom commands. the shared renderer currently owns whole groups, so Tokenjuice rejects a mixed Tokenjuice/custom group before invoking it; split those commands into distinct matcher groups and retry.
regression gates before merge
minimum gate for a new host adapter:
pnpm typecheck
pnpm vitest run test/hosts/<host>.test.ts
pnpm vitest run test/hosts/codex.test.ts test/hosts/claude-code.test.ts test/hosts/codebuddy.test.ts test/hosts/cursor.test.ts test/hosts/pi.test.ts
if you changed normalization/classification paths, also run:
pnpm vitest run test/core/command.test.ts test/core/classify.test.ts test/core/trace.test.ts
manual verification flow
run from repo root:
pnpm build
node dist/cli/main.js install <host>
node dist/cli/main.js doctor <host>
for command-path diagnostics, use trace:
node dist/cli/main.js wrap --format json --trace -- bash -lc "git status --short"
verify:
- normalized command/argv match expected command intent
- matched reducer is specific (not generic fallback for common commands)
- raw mode preserves full output:
node dist/cli/main.js wrap --format json --trace --raw -- <command>
for truncation-related debugging, verify both boundaries explicitly:
- reducer truncation: if output includes
... omitted .../... lines omitted ..., rerun with--rawfirst. - capture truncation: if output includes
[tokenjuice: output truncated], rerun with a larger capture ceiling (for example--max-capture-bytes 52428800). - do not treat these as the same failure mode; reducer bypass and capture-size tuning solve different problems.
docs updates required in same PR
when adding a host integration, update:
README.mdcommand examples and support tabledocs/spec.mdsupported host hooks table- dedicated design doc if host behavior differs materially (like cursor pre-tool wrapping)