CLI reference
tama provider config
Prints the hook block an install writes into a provider's configuration: the wiring that makes an event of Claude Code, Codex or another coding-agent harness reach the installed runtime.
#Invocation
tama provider config --provider <id> --dispatcher <command> [--json]
The providers are claude, codex and every harness Tama declares a hook adapter for, under the runtime id Oko and Transcript Lake use for it:
| Id | Harness | Hook file an install writes | How a block reaches it |
|---|---|---|---|
gemini | Gemini CLI | ~/.gemini/settings.json, hooks key | exit code 2, reason on standard error |
qwen | Qwen Code | ~/.qwen/settings.json, hooks key | exit code 2, reason on standard error |
droid | Factory Droid | ~/.factory/hooks.json, keyed by event | exit code 2, reason on standard error |
codebuddy | CodeBuddy Code | ~/.codebuddy/settings.json, hooks key | Tama's own document, as Claude Code reads it |
cursor | Cursor and Cursor CLI | ~/.cursor/hooks.json, hooks key, only when that file exists (its version is kept) | {"permission":"deny","user_message","agent_message"}; a pass answers {"permission":"allow"} |
copilot | GitHub Copilot CLI | ~/.copilot/settings.json, hooks key (PreToolUse uses Claude tool-name matchers; PostToolUse shell matching uses native bash|powershell) | {"permissionDecision":"deny","permissionDecisionReason"} |
windsurf | Windsurf Cascade | ~/.codeium/windsurf/hooks.json, hooks key | exit code 2, reason on standard error |
kimi | Kimi Code | ~/.kimi-code/config.toml, [[hooks]] tables between # BEGIN tama hooks and # END tama hooks | exit code 2, reason on standard error |
devin | Devin CLI | ~/.config/devin/config.json, hooks key (macOS/Linux; JSONC) | exit code 2, reason on standard error |
Passing Kimi hooks send hookSpecificOutput.additionalContext as plain standard-output text, which Kimi may append to its context. Passing Copilot hooks send it as top-level additionalContext JSON, including after a tool finishes. When a passing hook supplies no context, these adapters emit no output. Blocking responses remain separate from passing context.
Passing Qwen Code hooks preserve hookSpecificOutput.additionalContext and its native hookEventName in JSON, including Stop. This matters for tool events: Qwen treats plain successful stdout as a system message there, not as additional model context. A Qwen block still exits with code 2 and sends the reason on standard error, as its hook contract requires.
Gemini CLI receives passing context as JSON hookSpecificOutput.additionalContext, without Claude-specific event names. Gemini consumes this field on AfterTool and BeforeAgent; emitting it does not make an event support context when Gemini's contract does not. Its blocking protocol remains exit code 2 with the reason on standard error.
Devin uses its native PreToolUse, PostToolUse, UserPromptSubmit and Stop events; the registry's compact-session event maps to PostCompaction, not SessionStart. Matchers are anchored regular expressions for native tool names. Shell exec, file read/write/edit, notebook, search and subagent calls retain their arguments while receiving canonical tool names. A freeform apply_patch string or its native raw_patch field becomes the canonical patch field. Devin's DEVIN_PROJECT_DIR supplies the project directory; an explicit shell workdir supplies that command's directory. Missing or empty project-directory input, malformed workdir and missing or non-string patch text refuse with the native field and observed cause.
Passing Devin context preserves hookSpecificOutput.additionalContext and uses the declared native event for hookEventName, including PostCompaction rather than the dispatcher's canonical SessionStart. Devin documents context consumption for prompt, session-start and post-tool events; emitting context on another event is not proof of consumption. Inspect Devin's /hooks list and source paths, then exercise the native event before relying on installed wiring. Devin may also import Claude configuration; those imported hooks are separate from the native Devin configuration Tama manages. See the native hook contract and lifecycle and matcher reference.
Each harness's event names, tool matchers and payload come from its own hook documentation, recorded beside the declaration in tama-core/src/harnesses/harnesses.json. The runtime translates each harness's payload into the Claude Code payload every hook reads before any hook runs: Gemini CLI's run_shell_command, Qwen Code's run_shell_command, Droid's Execute and Cursor's Shell arrive as Bash, and Windsurf's pre_run_command, pre_read_code and pre_write_code arrive as Bash, Read and MultiEdit calls built from their tool_info. Only a hook's command and its group's matcher reach another harness; the other fields of a Claude Code hook mean other things there.
The registry arrives on stdin, so the caller decides which registry is being described — an install pipes the one it is about to write, and a person can pipe ~/.shared-hooks/registry.json or a source tree's copy. The dispatcher is the command that runs the installed hook dispatcher, pinned Node included; every printed command runs it for exactly one hook id through TAMA_ONLY_HOOK_ID, with TAMA_PROVIDER naming the harness.
#Output
Without --json the summary names the provider, the dispatcher, the total number of commands, and each provider event with its matchers:
provider: claude
dispatcher: /opt/homebrew/bin/node --require '…/node-runtime-preflight.cjs' '…/.shared-hooks/run-hook.mjs'
commands: 100
PostToolUse: Bash
PreToolUse: Bash, apply_patch|Edit|Write, MultiEdit, Read, …
SessionStart: compact
Stop: *
UserPromptSubmit: *
--json prints the block itself in the shape the provider's file holds it — for Claude Code, Codex, Gemini CLI, Qwen Code, Droid, CodeBuddy Code and Devin CLI an event key to groups of { matcher, hooks }; for Cursor, Copilot CLI and Windsurf an event key to flat entries; for Kimi Code { "hooks": [{ event, matcher, command }] } — which is what tama hooks install-release merges into each provider's file (hook-releases). The event and matcher table is the one tama_hook_tools::registry owns for Claude Code and Codex and the harness declaration owns for the others, so a config can never disagree with the registry installed beside it.
For these additional harnesses, installation replaces Tama-generated commands for that provider while retaining unrelated hooks, including commands in mixed matcher groups. Invalid event or group shapes refuse installation with the configuration path and failed field. Kimi installation preserves text outside its managed region and refuses nested, unmatched or unterminated region markers rather than discarding user configuration.
Devin inspection and installation read JSONC, including comments, through the same native configuration reader. Installation preserves configuration values and unrelated hook groups, but writes ordinary formatted JSON without comments. The installation transaction backs up the original for rollback; that temporary backup is removed when the transaction finishes, not retained as a configuration archive. Invalid JSONC or a non-object document refuses with the provider, configuration path and parse or shape cause. This does not relax registry, package-manifest or other providers' JSON parsing.
Droid passing decisions preserve context through its native JSON fields: top-level additionalContext for UserPromptSubmit, and hookSpecificOutput.additionalContext for post-tool and session-start context. Blocks continue to send corrective feedback on stderr. Droid snapshots hooks at startup; inspect its /hooks Effective tab and any external-change warning before relying on newly installed wiring. Managed-only policy can exclude user hooks even when configuration inspection finds the commands.
The real context-delivery journey is cargo test --manifest-path rust/Cargo.toml -p tama-cli --test harness_context -- --ignored --nocapture. Set TAMA_TEST_CONTEXT_EXPECTATIONS to a JSON array of independently captured { "provider", "event", "payload", "native_env", "expected_context", "expected_response" } records. native_env is a required object containing the native event's environment, including Devin's actual DEVIN_PROJECT_DIR; use an empty object when no native variables are required. It cannot override TAMA_PROVIDER. Supply the complete expected context. For plain stdout, use "expected_response": {"format":"plain_text"}; for JSON, use {"format":"json","context_pointer":"/additionalContext","hook_event_name":null}, or the native nested pointer /hookSpecificOutput/additionalContext and required native event name. Derive these expectations from the harness contract and independent native observations, not Tama's adapter registry. The journey compares complete context and the presence or absence of hookSpecificOutput.hookEventName; plain stdout ignores trailing whitespace only. Installed hooks must actually produce the expected context; the journey does not synthesize hook replies. It requires prompt, compact-session-start and post-tool cases for every declared hook-event-context or Droid-context protocol, and can also exercise other native context protocols. Devin's compact-session case must expect its native PostCompaction event name. Each result retains its expectation, source identity, binary digest, native input, explicit environment and process output. These observations are made at the dispatcher boundary; whether a running harness consumes the reply is read by that harness's own journey.
The provider coverage response (tama request coverage) and desktop policy inspection include every declared harness, supported registry mappings and the configuration path. Native JSON configurations and Kimi's complete TOML document are read to count installed mappings. Matching checks the native event, matcher and generated dispatch event; a hook id in the wrong event is not coverage. CodeBuddy uses the existing Claude-shaped reader. installedMappingCount: null means inspection failed, and the accompanying note names the configuration path and read, parse or shape error. An inspected configuration without matching commands reports zero. Configuration inspection is not live execution evidence and does not establish runtime trust, enablement or enforcement.
For a real installed-configuration journey, set TAMA_TEST_REGISTRY_ROOT to the installed registry release and TAMA_TEST_COVERAGE_EXPECTATIONS to a JSON array of independently observed { "provider", "installed_mapping_count", "diagnostic" } records, then run cargo test --manifest-path rust/Cargo.toml -p tama-cli --test harness_coverage -- --ignored --nocapture. Include both an inspected native configuration and an actual inspection failure; a failure expects a null count and a nonempty cause fragment. The test reads the real configurations through the same request coverage API as desktop, changes none of them, and retains expectations, source revision, patch, binary digest, command, exit status and response under the Cargo build directory.
For Claude Code, Codex and every harness that takes Claude Code's events, the matcher a written group carries and the registry events the coverage reader takes that matcher to stand for come from one table, so a block this command wrote reads back as running every hook it carries. A PreToolUse matcher stands for each registry event whose own matcher names one of the same tools, compared without case; a tool name Tama never writes in such a matcher stands for no event. The journey cargo test --manifest-path rust/Cargo.toml -p tama-cli --test provider_config_readback pipes the repository registry into tama provider config --provider claude --json, writes the block to ~/.claude/settings.json of a home under the Cargo build directory, and requires tama request coverage to report installedMappingCount equal to mappingCount with no note; it also requires an unknown provider to be refused with the providers this command does configure.
The real native installation journey is cargo test --manifest-path rust/Cargo.toml -p tama-cli --test harness_install -- --ignored --nocapture. Set TAMA_TEST_HOOK_RELEASE to a genuine sealed release from the tested clean source revision and TAMA_TEST_NATIVE_INSTALL_CASE to independently recorded JSON containing provider, configuration and invalid_configuration file paths, the invalid input's diagnostic, preserved JSON-pointer/value expectations, unrelated_commands, and a positive installed_mapping_count. Settings and unrelated commands must exist in the genuine native input; the journey does not manufacture a provider configuration or hook response. For Devin, provide actual JSONC captures.
This journey copies native input into a fresh Cargo-build-directory home and invokes hooks install-release --session-control-only. That mode still merges native provider configurations, connects no Git entrypoint that was not connected, and rewrites only the global Git dispatchers already Tama's; the fresh home holds none, so none is written there. Child commands receive only the isolated HOME and the dependency PATH, with their working directory in the isolated home. The journey checks persisted settings, unrelated-command multiplicities, release identity and symlink, independently expected installed coverage, and repeat-install idempotence. It then places the captured invalid configuration only in that home and checks the refusal cause, unchanged input, installed identity and release link. The operator's original configuration must remain byte-identical. Inputs, installed configuration, release identity, expectations and command evidence are retained.
The payload, context and coverage journeys share one evidence recorder. Before invoking Tama it retains the source revision and patch, binary digest, arguments, environment and exact standard input; context and coverage expectations are also saved before launch. Native stdout and stderr go directly to retained files while the child runs. The result record distinguishes starting, running, exited and spawn/wait failure, includes the child PID, and saves terminal status before reading output back. An interrupted running record is not a pass.
#Refusals and failures
Each of these exits 1 and writes nothing to stdout:
provider config produced no wiring for <provider>: the registry declares no event this provider supports— the block would have been empty, which is a config that enforces nothing.provider config does not configure the provider <name>; it configures claude, codex, gemini, qwen, droid, codebuddy, cursor, copilot, windsurf, kimi, devinprovider config could not parse the registry on stdin: <detail>, orprovider config requires a registry object on stdinprovider config requires a non-empty --dispatcher commandprovider config could not encode the hook block for <provider>: <detail>
A missing --provider or --dispatcher prints the usage text and exits 2.
At invocation time, a declared harness must send an object payload. Native pre-tool and post-tool payloads must name a nonempty string tool_name and an object tool_input, except Devin's freeform apply_patch string, which is translated before shape validation. Missing or mistyped fields report the provider, registry event, expected field shape and observed value. A synthesized tool input must contain every source field its adapter declares. Missing fields produce <provider> hook <event>: missing required payload field <path> for <field>; non-object inputs name the observed value. Pre-tool failures use the harness's native denial protocol, not an ordinary process failure that a fail-open harness could treat as permission to proceed. Post-tool feedback cannot undo a tool that already ran.
For Copilot post-tool decisions, a refusal or malformed payload becomes native additionalContext with a successful hook-process exit. This is required for Copilot to read the corrective feedback; its non-success post-tool exits are warnings and do not deliver permission decisions. Tama retains the policy refusal internally while reporting its reason through the field Copilot actually consumes. Pre-tool Copilot decisions still use native permissionDecision: "deny".
A required synthesized source field whose value is null is also refused, with required payload field <path> for <field> is null. This distinguishes an absent field from a present but unusable value; neither is treated as a valid native command or file operation.
The repository's real dispatcher boundary regression target is cargo test --manifest-path rust/Cargo.toml -p tama-cli --test harness_payload -- --ignored --nocapture. It requires an installed Tama registry and checks Cursor and Copilot pre-tool denial JSON, Copilot post-tool corrective context, Windsurf's stderr refusal for malformed tool payloads, and Devin's blocking exit for invalid project-directory, workdir and patch inputs. Each invocation retains the source revision, source patch, binary digest, payload, arguments, provider, exit status and output in the Cargo build directory.
Return to the complete CLI command tree.