CLI reference
tama justify
Records, reads and retires the justifications the write guards require, so a registry that a hook demands is written by a command instead of by hand.
#Invocation
tama justify <record|show|remove|move|list> [--file <path>] [--to <path>] [--justification <text>] [--trajectory-evidence <text>]
[--kind file|cua|ssh] [--quote <text>] [--quote-match <text>] [--expires-at <iso>] [--home <path>] [--json]
tama justify move --file <old path> --to <new path> [--kind file]
--kind file selects ~/.shared-hooks/file_justifications.json, keyed by absolute target path. --kind cua selects ~/.shared-hooks/cua_justifications.json and records application/action grants rather than file justifications. --kind ssh selects ~/.shared-hooks/ssh_justification.json and records the target, operation and exact path or command grants block-unauthorized-ssh reads. When TAMA_CUA_JUSTIFICATIONS or TAMA_SSH_JUSTIFICATION names another file — the variable block-unauthorized-cua or block-unauthorized-ssh reads its registry from — the command, and tama attestation list|verify|revoke, use that file, so they write, list and withdraw the very grants the gate admits through. --home changes the registry destination only; it does not select a different consent transcript. Whether test code may be written is not a justification: it is the operator's own approval, recorded with tama tests approve, and --kind test is refused as an unknown kind.
#Why it exists
The rules that demand a justification are enforced on write, and the registries they read are long — the file registry passed seven thousand lines. A guard that refuses any edit leaving a source file over three hundred lines therefore refused every edit to the registry that another guard demanded an entry in, so the two rules together made the required entry unwritable. The command owns those files instead: it is not an exemption from either rule, it is the supported way to satisfy one of them.
#Output and state
For file entries, record writes or replaces the absolute path, show prints one entry, list lists paths, remove retires one entry, and move --file <old> --to <new> carries one entry to the path the file has after a rename, byte for byte, leaving every other entry where it was. The registries are keyed by absolute path, so git mv leaves the entry behind and the next write to the moved file is refused for want of one; move is the supported way to carry it. For CUA, list returns authorizations.
A trajectory, keeper or routine file needs measured evidence beside its reason: record --kind file --file <path> --justification <why> --trajectory-evidence <evidence> writes the evidence as trajectory_evidence_justification in the same entry of ~/.shared-hooks/file_justifications.json, the registry pre-edit-trajectory-evidence reads for that file (there is no second registry under ~/.claude). The evidence names a passing or reference source, a non-passing source and a concrete artifact, as the gate's refusal lists them; empty evidence is refused as the trajectory evidence holds no text and nothing is written.
Without --json every answer is text: a sentence for record, remove and move, one path per line for a file list, and key: value lines for show and every CUA answer, each list item on its own - line. With --json the same answer is one JSON document.
#Record consent already given in a session
tama justify record --kind cua \
--session <current-OMP-session-id> --app Figma \
--actions list_windows,get_window_state \
--quote-match "Zezwalam CUA" --json
tama justify list --kind cua --json
tama justify show --kind cua --quote "<exact recorded user message>" --json
tama justify remove --kind cua --quote-match "Figma: list_windows" --json
The example requires a real message that directly grants CUA access to Figma and names both actions. It does not grant access by itself. Supply exactly one of --quote, an exact quotation, or --quote-match, text selecting one distinct original user message in the named session. A match preserves the complete message, including whitespace; an assistant quotation or tool result is not selected. Multiple distinct matching user messages are refused.
Tama reads the installed application's bundle identifier and executable from its Info.plist in /Applications or ~/Applications. There is no arbitrary executable, bundle-id or transcript-root CLI override. The shared hook validator checks the quotation, application, every requested action and its presence in the named session. The transcript root is the runtime's TAMA_OMP_SESSION_ROOT, or ~/.omp/agent/sessions when unset.
The successful response contains kind, registry, sessionId and authorization. The entry preserves the existing vTwo schema, records the exact quote and app/action scope, and sets allowUntargeted: false and lifetime: "current OMP session". Recording the same quote for the same application replaces it without adding another entry. Other entries and registry metadata remain intact.
Recording does not disable a hook, create a runtime capability, start Cua Driver or prove that a GUI operation ran. The installed hook checks the current session, action and actual target process at use. Verify the requested action through Cua Driver and inspect its result. A driver message such as Cua Driver daemon is not running is an execution failure, not a missing user quote. Do not rewrite the registry or broaden the recorded grant to hide it.
Starting the driver is a separate, target-neutral action. If the operator's message names serve for the application, record it alongside the application actions, then run cua-driver serve before launch_app. The grant still stores allowUntargeted: false: only the explicitly named serve, status, stop and check_permissions management actions need no app PID. A missing action is refused; launch_app must name the granted bundle and window actions must target its live process. A stopped daemon is not repaired by recording a grant alone.
Every grant recorded for the session keeps its own scope. A call passes when any grant the session's transcript backs covers its action and target, so recording a later grant for the same application, for example one naming hotkey and type_text, does not withdraw an earlier one naming get_window_state. A call no backed grant covers is refused with the CUA operation is outside the app/action scope of every user quote recorded for this session.
show and remove take exactly one of --quote, the whole recorded quote, or --quote-match, a fragment only one recorded quote contains. block-unauthorized-cua reads a driver call only from the command words a shell line runs, so a consent sentence passed whole as a quoted argument (to tama justify, in a commit message) is data and is not refused as a driver call; a driver started by a shell's -c line is still read. A fragment no recorded quote contains is refused with no recorded CUA quote contains "<fragment>"; a fragment several quotes contain is refused with their count and the quotes themselves.
For a window action through the CLI, pass the live PID returned by launch_app in the action's JSON argument, for example cua-driver get_window_state '{"pid":<live-pid>,"window_id":<window-id>}' with the PID and window ID from that run. Tama checks the PID in the quoted JSON argument against the granted application's executable. An absent PID, a process belonging to another application or an action omitted from the grant is refused; the command does not gain access merely because the daemon is running.
#CUA refusals
These refuse recording with exit 1 and leave consent unchanged:
sessionId must be the exact OMP session identifiersupply exactly one nonempty quote or quoteMatchthe quote must directly authorize CUA, the application and every requested actionthe quote is not a verbatim user message in OMP session <id> under <root>application <app> is not installed in /Applications or ~/Applications<registry> must contain an authorizations array; nothing was changed
Missing or ambiguous message matches identify the match, session and observed count. Unreadable application metadata and registry files include the failing path and underlying error. Missing consent on show/remove returns no CUA grant is recorded for that quote.
#Record SSH consent already given in a session
tama justify record --kind ssh --session <current-OMP-session-id> \
--quote-match "zgadzam sie na ssh" \
--target <user@host the quote names> --operation exec \
--command "<the exact full command>" --command "<another>" --json
tama justify list --kind ssh --json
tama justify show --kind ssh --quote-match "zgadzam sie na ssh" --json
tama justify remove --kind ssh --quote-match "zgadzam sie na ssh" --json
block-unauthorized-ssh admits an SSH call only when the operator's consent is the Tama session capability {"tool": "ssh"}, the operator's own words name every target and are a verbatim user message of the session, and the registry covers the exact target, operation and path or command. --target repeats for every destination, exactly as the call spells it. read and write use exact ssh:// or sftp:// URLs, one per --path; exec, transfer, tunnel and git use exact full commands, one per --command. Before writing, the recorder asks the hook's matcher whether each target and path or command is covered. Recording the same quote, targets and operation replaces the earlier entry while preserving other entries and metadata. Recording alone grants no access: the hook checks the capability and session transcript at use.
The numeric gate reads .shared-hooks/ssh_justification.json by field. String arrays in allowedTargets, allowedOperations, allowedPaths and allowedCommands describe the grant, so an IP destination or a process identifier inside an exact authorized command is not an invented quantity. The original quote and string-valued tool, lifetime and session identifiers are also grant text. Unknown fields, numeric values, nested objects and malformed JSON retain numeric checks. The same key names in an unrelated configuration file receive no exemption. This does not bypass consent or hook-edit protection; tama justify record --kind ssh remains the supported writer.
#SSH refusals
These refuse recording with exit 1 and leave the registry unchanged:
sessionId must be the exact OMP session identifiersupply exactly one nonempty quote or quoteMatchtargets must name at least one exact SSH destinationoperation must name what the call does, in the gate's wordsgive exact paths (--path, for read and write) or exact full commands (--command, for exec, transfer, tunnel and git), not both and not neitherthe quote must name every target; consent itself is the Tama session capability {"tool": "ssh"}the quote is not a verbatim user message in OMP session <id> under <transcript root>the gate would not admit <operation> to <target> from this entry (an operation the gate does not know, or paths where it reads commands, or commands where it reads paths); nothing was recorded
Missing consent on show/remove returns no SSH grant is recorded for that quote; a --quote-match no recorded quote or several recorded quotes contain is refused naming the count. A usage error — an unknown option, a missing --session or --operation, or both or neither of --quote and --quote-match on show and remove — exits 2.
#Desktop requests
Justifications → CUA consent records, lists and removes these same grants through tama request, one process per operation. The native view displays the original quote, application, executable, actions, session and lifetime, and shows the product's refusal. No JSON has to be pasted into a protected directory.
#Refusals and failures
A justification shorter than the rule's own word count is refused with the count it needs. A usage error prints the refusal and the usage and exits 2: a missing --file, --to or --justification, a relative path, an unknown subcommand, an unknown kind, and for --kind cua an unknown option, a missing --session, --app, --actions or --quote. A refusal of a well-formed request, an unreadable registry, or an unset HOME exits 1. move refuses a source with no entry (no justification is recorded for <old>), a destination that already has one (a justification is already recorded for <new>; remove one of the two first) and a relative path.
Return to the complete CLI command tree.