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 wrap when 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.ts
    • install <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 doctorInstalledHooks if the new host is included there

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_HOME affects copilot-cli only; VS Code Copilot Chat ignores it and always resolves under $HOME/.copilot/hooks/. If your adapter should not depend on COPILOT_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 PATH after 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 --raw first.
  • 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.md command examples and support table
  • docs/spec.md supported host hooks table
  • dedicated design doc if host behavior differs materially (like cursor pre-tool wrapping)