A small code-agent CLI with file and shell tools. Built with Node.js 22, TypeScript, and ESM.
Product improvements enter the autonomous queue from three sources: promoted
user reports, findings from the bounded community-source registry, and
reproducible self-discoveries. Each becomes a normalized Issue authored by the
repository Bot before execution. The autonomy contract, its policy files,
GitHub workflows, and CODEOWNERS form a protected governance plane maintained
by qqqys; the Bot may propose governance changes but cannot apply them.
npm install
npm run buildNew here? Follow docs/FIRST-RUN.md for a verified path from
install through your first successful task, including a setup --doctor check
and troubleshooting.
Model configuration is resolved from environment variables and an optional user settings file. Environment variables always take precedence, so existing export-based setups work unchanged.
| Variable | Required | Default | Description |
|---|---|---|---|
OPENAI_API_KEY |
Yes¹ | — | API key for the provider |
OPENAI_BASE_URL |
No | https://api.openai.com/v1 |
OpenAI-compatible base URL |
OPENAI_MODEL |
Yes¹ | — | Model name |
¹ Not required in the environment when supplied through the user settings file below.
To avoid exporting variables in every shell, store the non-secret model
configuration in the user-owned file ~/.oh-my-cli/settings.json, or select an
alternative with --settings <path>. The credential itself is never stored in
the file — only the name of the environment variable that holds it:
{
"model": {
"baseUrl": "https://dashscope.aliyuncs.com/compatible-mode/v1",
"name": "qwen-latest-series-invite-beta-v77",
"apiKeyEnv": "DASHSCOPE_API_KEY"
},
"mcpServers": {},
"extensions": {}
}This is the same file that backs the --health MCP/extension inventory. Each
field is resolved with the following precedence (highest first):
| Field | 1 (highest) | 2 | 3 (lowest) |
|---|---|---|---|
| Base URL | OPENAI_BASE_URL |
model.baseUrl |
built-in default |
| Model name | OPENAI_MODEL |
model.name |
(required) |
| Credential | OPENAI_API_KEY |
env var named by model.apiKeyEnv |
(required) |
Security: the settings file is only ever the user-owned default or a path you
pass explicitly — a settings file inside a project is never auto-discovered, so
an untrusted repository cannot redirect your endpoint or credential. Raw
credential fields such as model.apiKey are rejected; reference an environment
variable through apiKeyEnv instead. oh-my-cli --preflight prints a redacted
summary of the resolved model, endpoint host, settings source, and credential
variable name (never the credential value).
To keep several named configurations (for example a hosted Qwen endpoint and a
local model) and switch between them, declare a profiles map in the same
user-owned settings file. Each profile has the same shape as the model section
(plus an optional description and a disabled flag); no raw credential is ever
stored — only the environment-variable name that holds it:
{
"defaultProfile": "qwen",
"profiles": {
"qwen": {
"baseUrl": "https://dashscope.aliyuncs.com/compatible-mode/v1",
"name": "qwen-latest-series-invite-beta-v77",
"apiKeyEnv": "DASHSCOPE_API_KEY",
"description": "Hosted Qwen"
},
"local": { "name": "llama3", "baseUrl": "http://127.0.0.1:11434/v1" }
}
}Selection precedence (highest first): an explicit --profile <name>, then
settings.defaultProfile, then the legacy single model section. The chosen
profile reuses the exact resolution above, so OPENAI_* still override it and
the same credential rules apply.
oh-my-cli --list-profiles # inventory (read-only, redacted)
oh-my-cli --list-profiles --output json # machine-readable
oh-my-cli --profile qwen -p "Summarize README" # select a profile for this run
oh-my-cli --preflight --profile local # verify a profile's endpoint/credentialProfiles are read only from the user-owned scope: a project-local settings file
can never set profiles or defaultProfile, so an untrusted repository cannot
silently replace your selected endpoint or credential. Unknown profile names,
disabled profiles, and missing credentials all fail before any request, and the
errors (like the listing) are redacted.
The selected profile is recorded in the session's metadata, so resuming a session under a different profile (or model) prints a redacted warning that explains the change while the conversation, tool, and approval history are preserved.
oh-my-cli -p "List the files in this directory"oh-my-clioh-my-cli --resume <session-id>Prefer to browse instead of copying an id? Run oh-my-cli --browse-sessions in
a terminal to search and arrow through your sessions, then resume the selected
one. It resumes the exact session and restores its declared workspace, and it
fails closed with an actionable message rather than resuming something else when
the chosen session is missing, corrupt, or its workspace no longer exists.
Sessions are persisted as JSONL under ~/.oh-my-cli/sessions/. Each
non-interactive run seals the session with an atomic checkpoint (a temp file
renamed over the canonical one), so an interrupted write leaves either the
previous or the new complete checkpoint — never a half-written file. On
--resume, the checkpoint is recovered automatically: a complete one left by an
interrupted write is promoted, a partial one is discarded, and a corrupt one is
quarantined alongside the session (preserved, never deleted) with a warning —
without touching other sessions.
A long session can be compacted into a bounded, versioned summary so it can
continue past a provider context limit without losing task or execution state.
Compaction never edits the transcript: the full JSONL stays on disk and the
summary is written to a sibling <session-id>.compact.json sidecar. On the next
--resume, the sidecar is validated against the transcript (schema, version, and
a digest of the summarized messages) and only then applied; a missing, corrupt,
or mismatched sidecar is ignored and the full transcript is used instead (fail
closed). Completed tool actions are kept as bounded, redacted receipts that the
resumed model is told not to repeat, so removing detailed turns never re-runs
a completed mutation.
# Write a compaction sidecar for a session (original transcript untouched) and
# print a redacted report of what will be retained.
oh-my-cli --compact <session-id>
# Resume: the validated summary is applied automatically.
oh-my-cli --resume <session-id>To compact automatically during a run, set a context-pressure threshold in
tokens; when the latest provider prompt reaches it, the in-memory transcript is
compacted before the next call (the on-disk transcript is still untouched, and
the event is observable in --output json as a compaction record):
oh-my-cli -p "Long task" --compact-threshold 100000
# or: OMC_COMPACT_THRESHOLD=100000 oh-my-cli -p "Long task"Export a single session to a portable, local record for debugging, review, or handoff — without copying raw terminal output that loses structure or leaks credentials. The export reads the canonical session record and writes two files next to each other: a readable Markdown transcript and a machine-readable JSON manifest. Secrets, auth tokens, sensitive environment values, and the host home directory are redacted before any bytes are written. The manifest records the session id, redacted workspace and model, timestamps, per-tool call/result tallies, attachment references (by name/type/size — never embedded), and a sha256 of the source session file as its evidence reference.
# Write <session-id>.session-export.md and <session-id>.session-export.manifest.json
oh-my-cli --export-session <session-id> --out ./exports
# JSON output prints the manifest and the two stable output paths.
oh-my-cli --export-session <session-id> --out ./exports --output jsonExports are deterministic: repeated exports of an unchanged session produce
byte-identical files (no export-time wall clock). Writes are atomic (a temp file
renamed over the canonical one, temps cleaned up on failure), and an existing
output file is never overwritten without --force. The export is purely local —
it performs no network or external-state action. A missing session exits non-zero;
a corrupt or partial session still exports the recoverable content with its
integrity flagged.
An agent turn changes both the conversation and the workspace, so a wrong turn
needs a fast recovery path — but a generic git reset would destroy unrelated,
pre-existing work. Every non-interactive (-p) turn captures an explicit,
content-based checkpoint around exactly the files its mutating tools touched
(their pre-image is recorded before the tool runs) plus the turn's transcript
entries. Undo reverses only that turn — restoring each owned file to its
pre-image (or deleting a file the turn created) and trimming its messages —
while leaving user-owned and pre-existing changes in place. Redo re-applies the
turn when the undo checkpoint is still valid.
# Preview what undoing the latest turn would change (no writes).
oh-my-cli --undo-turn <session-id> --dry-run
# Undo the latest turn, then redo it.
oh-my-cli --undo-turn <session-id>
oh-my-cli --redo-turn <session-id>
# JSON output prints the structured plan/receipt.
oh-my-cli --undo-turn <session-id> --dry-run --output jsonUndo and redo are safe by construction: no force, hard reset, branch rewrite, or
implicit stash is ever used. The operation fails closed (exit 2, nothing changed)
when a turn-owned file has diverged since the checkpoint, is in a conflicted
state, or the turn is already applied — so a failed undo/redo leaves both the
workspace and the transcript exactly as they were. Operations are idempotent
(re-undoing an undone turn is a no-op) and record a durable receipt tied to the
exact session and checkpoint digest. Checkpoints live in a <session-id>.turn.json
sidecar, so they survive a restart. The mechanism needs no Git repository and
never bypasses approval, lease, or evidence policies.
While a longer task is in flight, sending a quick clarification through the main conversation can alter task state, trigger context compaction, change tool plans, or shift the active goal. A side question answers that clarification against a bounded, read-only snapshot of the active session — with tool execution and workspace mutation disabled — and returns the answer inline without appending to the main transcript, goal, workflow, or retry chain.
In the interactive shell, type /ask <question> to open a distinct overlay. The
pane states the context boundary and whether a provider request is active; while
it is open the main task is untouched underneath. From a settled answer you can
Enter to promote it into the composer (to edit or send deliberately), c to
copy it to the terminal clipboard, or Esc/d to dismiss. While the answer
streams, Esc cancels. The same command works in the plain readline fallback
(streaming the answer inline).
# Headless: ask against a session's read-only context (nothing is persisted).
oh-my-cli --side-question "which test runner does this project use?" --session <session-id>
# JSON output prints a versioned result and the context scope.
oh-my-cli --side-question "why is the build failing?" --session <session-id> --output jsonIsolation is structural, not best-effort: the side-question runner is handed only a provider config, a bounded context snapshot, and the question — never a session store, goal, approval, or workspace handle — so it cannot mutate any of them. The provider call carries no tool schemas and any tool-call event is ignored, so a side turn can never run a tool or touch the workspace. A headless side question leaves the source session byte-identical (transcript and every sidecar).
To see where a session's time, context, model requests, and tool activity went
without reading raw logs, open the read-only stats view. It is derived from the
canonical session record (the persisted messages) plus an optional live-runtime
enrichment captured during the current session, and is grouped into session
activity, context usage, model activity, tool outcomes, and elapsed/waiting time.
Every value states its provenance: a measurement is bare, an estimate (the
chars/4 context size, the bundled-price cost) is tagged (est.), and a value the
provider or runtime never reported reads n/a — never a fabricated zero. Tool
names and failure summaries are secret-safe before they reach the view.
In the interactive shell, type /stats to open the overlay above the composer
(the transcript underneath is untouched); Esc/q/d dismiss it. The same
engine backs the headless form, so a session's numbers read identically in both.
# Headless: a read-only stats view for a session (no provider call, no mutation).
oh-my-cli --session-stats <session-id>
# Stable JSON for automation (no ANSI); add --output json.
oh-my-cli --session-stats <session-id> --output jsonAggregation is deterministic: counts come from the message log, so resuming a
session recomputes the same totals without double-counting restored events. A
headless read (or a freshly resumed session) has no live runtime, so model
activity, tool failures, and timing report n/a rather than an invented value.
Hidden language-server startup or stale diagnostics can mislead both users and agents. The read-only language-server view surfaces, for the active trusted workspace, which configured servers are available, which binaries are missing, and which present languages have no registered server — without ever installing software. When servers are running it shows their readiness (starting, ready, indexing, degraded, stopped, error) and their diagnostics, each bound to the exact workspace, file, document version, and server instance that produced it. A diagnostic from another workspace, a superseded document version, or a previous server instance is rejected rather than presented as current. Unsupported languages and missing binaries are explicit and quiet, and never block normal CLI use; all server-supplied text is secret-safe and length-bounded.
In the interactive shell, type /lsp to open the overlay above the composer (the
transcript underneath is untouched, and no edits are performed); Esc/q/d
dismiss it. The same engine backs the headless form, so the view reads identically
in both.
# Headless: the read-only language-server view for the current workspace
# (discovery only — no install, no mutation, no edits).
oh-my-cli --lsp-status
# Stable JSON for automation (no ANSI); add --output json.
oh-my-cli --lsp-status --output jsonDiscovery is read-only and trust-gated: an untrusted workspace surfaces no running servers. The CLI does not itself spawn language servers, so the live list is empty in normal use; the deterministic runtime engine that produces live, workspace-bound state (and rejects stale events) is covered by focused tests and the end-to-end receipt.
Long-running work — verification, evidence collection, recovery — can outlive the
turn that started it. The read-only task center monitors that background work
without ever performing it: it shows each task's lifecycle state (queued,
running, waiting on approval, succeeded, failed, cancelled, orphaned,
recovered) and the durable receipt a terminal task leaves behind (an opaque
digest plus an optional evidence link). Concurrency limits, leases, approval
gates, and workspace ownership stay authoritative — the center reflects them and
never grants a slot, releases a lease, or bypasses an approval. Cancellation is
idempotent and scoped to the one selected task, and a cancelled task keeps its
receipt rather than being rewritten as something else.
On restart the center reconciles recorded state against real process state: a
running task whose process is gone with no receipt is marked orphaned (never
succeeded), and one whose durable receipt has surfaced is marked recovered.
Work is never inferred complete from UI state alone. All task text is secret-safe
and length-bounded, and the home path is redacted.
In the interactive shell, type /tasks to open the overlay above the composer
(the transcript underneath is untouched, and no edits are performed); Esc/q/d
dismiss it. The same engine backs the headless form, so the view reads identically
in both.
# Headless: the read-only task-center view for a session
# (monitor only — no start, no cancel, no mutation, no edits).
oh-my-cli --tasks <session-id>
# Stable JSON for automation (no ANSI); add --output json.
oh-my-cli --tasks <session-id> --output jsonThe durable task snapshot lives in a per-session sidecar and is parsed fail-closed: a malformed, truncated, or incompatible snapshot is rejected rather than presented as live. The deterministic runtime engine that drives the state machine, concurrency, cancellation, and restart reconciliation is covered by focused tests and the end-to-end receipt.
| Option | Description |
|---|---|
-p, --prompt <prompt> |
Run a single non-interactive request |
--image <paths...> |
Attach image file(s) by path for vision-capable analysis (PNG, JPEG, GIF, or WebP); also /attach in interactive mode |
--resume <session-id> |
Resume a persisted session |
--browse-sessions |
Interactively browse, search, and resume a previous session (requires a terminal) |
--list-sessions |
List resumable sessions with a redacted usage summary and exit |
--session-stats <session-id> |
Show a read-only, deterministic activity/efficiency stats view for a session (add --output json for automation) and exit; also /stats in interactive mode |
--lsp-status |
Show the read-only, workspace-bound language-server discovery and readiness view for the current workspace (add --output json for automation) and exit; also /lsp in interactive mode |
--tasks <session-id> |
Show the read-only background-task center for a session — lifecycle states, durable receipts, and restart reconciliation (add --output json for automation) and exit; also /tasks in interactive mode |
--export-session <session-id> |
Export a session locally as redacted Markdown + a deterministic JSON manifest and exit |
--out <dir> |
Output directory for --export-session (default: current directory) |
--force |
Overwrite existing --export-session output files |
--compact <session-id> |
Compact a session into a bounded summary sidecar (original transcript preserved) and exit |
--compact-threshold <tokens> |
Auto-compact the in-memory transcript when the latest prompt size reaches this (also honors OMC_COMPACT_THRESHOLD) |
--undo-turn <session-id> |
Safely undo the most recent completed agent turn of a session (restores its files + transcript) and exit |
--redo-turn <session-id> |
Redo the most recent undone agent turn of a session and exit |
--dry-run |
Preview an --undo-turn/--redo-turn plan without changing the workspace or transcript |
--side-question <text> |
Ask a side question against a session's bounded, read-only context (no tools, no mutation, nothing persisted) and exit; also /ask in interactive mode |
--session <session-id> |
Source session whose read-only context seeds --side-question |
--approval-mode <mode> |
default, auto-edit, or yolo |
--workspace <dir> |
Workspace directory (default: cwd) |
--doctor |
Run read-only installation/platform readiness checks and exit |
--readiness |
Inspect repository readiness for a blocked task (read-only) and exit |
--expected-branch <name> |
Expected branch for the --readiness branch check |
--remote <name> |
Git remote to probe for --readiness (default origin) |
--repo-context |
Inspect a bounded, redacted repository context snapshot (read-only) and exit |
--repo-map |
Inspect a bounded, ranked repository map of key files and top-level symbols (read-only) and exit |
--map-tokens <n> |
Token budget for --repo-map (default 1024; ~4 chars per token) |
--instruction-context |
Inspect the effective, redacted repository instruction context a fresh session is seeded with (read-only) and exit |
--plan <task> |
Produce a bounded, deterministic execution plan for a task (read-only) and exit |
--verify-task |
Run the repository's canonical verify commands and report a bounded, head-bound pass/fail verdict and exit |
--review-change |
Review the current change against a base ref and emit a bounded, redacted, head-bound review brief and exit |
--base <ref> |
Base ref for --review-change, --ci-handoff, and --delivery-brief (default origin/main, then HEAD) |
--ci-handoff |
Compose verify and review into a bounded, redacted, head-bound CI handoff brief and exit |
--delivery-brief |
Compose plan, verify, review, and CI handoff into a bounded, redacted, head-bound completion verdict and exit |
--ci-result <state> |
CI outcome for --delivery-brief: pass, fail, or pending (default pending) |
--provider-contract |
Inspect the resolved provider extension contract from settings (read-only, redacted) and exit |
--provider <id> |
Provider id to select for --provider-contract / --invoke-provider (defaults to settings.providers.default or the sole entry) |
--invoke-provider |
Issue one bounded model request to the resolved-ready provider from settings once, gated by approval mode, bounded and redacted, and exit |
--provider-prompt <text> |
Prompt to send for --invoke-provider (defaults to a minimal safe ping) |
--mcp-contract |
Inspect the resolved MCP server extension contract from settings (read-only, redacted) and exit |
--server <id> |
MCP server id to select for --mcp-contract / --invoke-mcp (defaults to settings.mcp.default or the sole entry) |
--invoke-mcp |
Connect to the resolved-ready MCP server from settings once and call one of its tools, gated by approval mode and command policy, confined and redacted, and exit |
--mcp-tool <name> |
Tool name to call for --invoke-mcp (defaults to the sole exposed tool) |
--mcp-arg <key=value> |
Argument for the MCP tool call (repeatable), parsed as key=value with string values |
--tool-contract |
Inspect the resolved tool extension contract from settings (read-only, redacted) and exit |
--tool <id> |
Tool id to select for --tool-contract / --invoke-tool (defaults to settings.tools.default or the sole entry) |
--invoke-tool |
Invoke the resolved-ready tool extension from settings once, gated by approval mode and command policy, confined and redacted, and exit |
--invoke-timeout <ms> |
Hard timeout in milliseconds for --invoke-tool / --invoke-mcp / --invoke-provider (default 30000, max 300000) |
--discover-extensions |
Discover the declared provider, MCP, tool, and workflow extension contracts and readiness from settings (read-only, redacted) and exit |
--extension-compat |
Report the supported provider, tool, MCP, and workflow contract versions and a redacted settings-file compatibility verdict (read-only) and exit |
--no-probe |
Skip the bounded lifecycle probe for --mcp-contract / --tool-contract / --discover-extensions / --trust-posture and report the declared state |
--list-workflows |
List the reusable workflows declared in user settings (read-only, redacted) and exit |
--run-workflow <name> |
Run a named workflow from user settings non-interactively (sequential headless steps; a failing step halts) and exit |
--list-profiles |
List the named model profiles declared in user settings (read-only, redacted) and exit |
--profile <name> |
Select a named model profile from user settings (overrides settings.defaultProfile) |
--output <format> |
-p output format: text (default) or json (headless event stream) |
--no-color |
Disable ANSI color output (also honors a non-empty NO_COLOR env var) |
--summary |
Print a privacy-safe execution summary for the run (unattended use) |
--budget <usd> |
Spend budget in USD; stop before further provider calls once the estimated cost reaches it (also honors OMC_SPEND_BUDGET_USD) |
--baseline <file> |
Baseline run-summary file to compare in scorecard mode |
--candidate <file> |
Candidate run-summary file to compare in scorecard mode |
--max-elapsed-ratio <n> |
Scorecard regression threshold: fractional elapsed-time increase tolerated (default 0.25) |
--max-failure-delta <n> |
Scorecard regression threshold: tool-failure increase tolerated (default 0) |
--recover |
Resume an interrupted task from a recovery checkpoint (read-only) and exit |
--checkpoint <file> |
Recovery checkpoint file for --recover |
--task-identity <id> |
Stable task identity (used by --recover and worktree leases) |
--evidence <file> |
Current evidence file (JSON stepId -> digest) for --recover |
--export-evidence <file> |
Export a portable, signed evidence bundle to <file> and exit |
--verify-evidence <file> |
Verify a portable evidence bundle offline and exit |
--summary-file <file> |
Run-summary file to include in --export-evidence |
--outcomes-file <file> |
Command-outcomes file (JSON array) to include in --export-evidence |
--create-worktree |
Create a leased git worktree for a mutating delegated agent and exit |
--clean-worktree |
Clean a leased git worktree after verified completion and exit |
--agent-identity <id> |
Stable agent identity for a leased worktree (with --create-worktree/--clean-worktree) |
--worktree-root <dir> |
Directory where leased worktrees live (default <workspace>/.oh-my-cli/worktrees) |
--command-policy <command> |
Evaluate one shell command against the offline command policy and exit |
--provenance <source> |
Command provenance for --command-policy: builtin, repository (default), or issue |
--trust-info |
Show the folder-trust decision for the workspace (read-only) and exit |
--trust |
Trust this workspace for this run only (not persisted) |
--trust-workspace |
Persist trust for this workspace in the user trust store and exit |
--enforce-folder-trust |
Deny mutating tools when the workspace is untrusted (env: OMC_ENFORCE_FOLDER_TRUST=1) |
--trust-posture |
Show the effective, redacted workspace trust, sandbox, approval, and extension posture (read-only) and exit |
Color is enabled by default in the interactive REPL and command palette. Pass
--no-color or set a non-empty NO_COLOR environment variable (per
no-color.org) for CI-friendly plain output; an empty
NO_COLOR is ignored.
- default — prompt interactively for every mutating tool; deny when no TTY.
- auto-edit — allow
writeandedit; still prompt forshelland deny without TTY. - yolo — allow all tools without prompting (unsafe).
Read operations never require approval.
Before a mutating tool is approved, the CLI shows a redacted preview of what it
will touch — the shell command and the file paths (src/permission-impact.ts) —
and the command-policy decision/denial renders the same way
(src/command-policy.ts). Both surfaces neutralize spoofing Unicode before
display: bidirectional override/isolate controls (e.g. U+202A–U+202E and
U+2066–U+2069), zero-width characters (e.g. U+200B–U+200D, U+FEFF, and U+2060),
and look-alike quote marks. Each such character is replaced with a visible
[U+XXXX] marker and counted, so untrusted content (a file the agent read, a
repository, an Issue, or a relayed message) cannot visually reorder or disguise
the preview into a "Trojan Source"-style trap. Secret redaction, home-path
collapsing, whitespace collapsing, and size truncation are unchanged, and
ordinary commands render identically aside from any marker.
Before any project-controlled instruction, setting, hook, extension, or mutating
tool can affect a run, the CLI decides whether the workspace folder is trusted.
This is the top-level safety boundary: it is fail closed and approval modes
are subordinate to it — --approval-mode yolo cannot widen it, and read-only
tools (read/list/glob/grep) are always permitted.
A workspace is untrusted by default. Trust is granted only by an explicit
user act, recorded in a user-owned store at ~/.oh-my-cli/trust.json that a
project can never write — so an untrusted repository cannot trust itself or
select its own model endpoint, credential source, hooks, or mutating tools. The
canonical workspace key collapses symlink aliases and linked git worktrees to one
identity, so a subagent or leased worktree inherits its parent's trust (equal
isolation) rather than escaping it.
The decision distinguishes four states, in both interactive and headless modes:
| State | Mutation | Meaning |
|---|---|---|
trusted |
permitted | Workspace is in the user trust store (or --trust for this run) |
sandbox-enforced |
permitted | An effective sandbox is enforced around tool execution (OMC_SANDBOX=enforced) |
untrusted |
denied | Not trusted and no sandbox; mutating tools fail closed |
sandbox-unavailable |
denied | Not trusted and a sandbox is required (OMC_REQUIRE_SANDBOX=1) but unavailable |
Enforcement is opt-in so existing runs are unchanged: pass
--enforce-folder-trust (or set OMC_ENFORCE_FOLDER_TRUST=1) to deny mutating
tools when the workspace is untrusted. Grant or inspect trust with:
# Show the folder-trust decision for the workspace (read-only) and exit
oh-my-cli --trust-info --enforce-folder-trust --workspace path/to/repo
# Trust this workspace for one run (not persisted)
oh-my-cli -p "…" --enforce-folder-trust --trust
# Persist trust for this workspace in the user trust store
oh-my-cli --trust-workspace --workspace path/to/repoFolder Trust
────────────────────────────────────────
Workspace: ~/code/unfamiliar-repo
Trust state: untrusted
Sandbox: none
Mutation: DENIED (fail closed)
Enforcing: yes
Reason: Workspace is not trusted; mutating tools fail closed.
The trust store fails closed on any missing, malformed, or wrong-schema file
(nothing trusted) rather than widening trust. All diagnostics redact the host
home directory to ~ and never emit secrets. --trust-info is a read-only
diagnostic (always exit 0), not a gate.
Trust posture. Before running unattended or delegating mutating work,
--trust-posture composes the folder-trust decision, sandbox isolation, approval
mode, and extension readiness (--discover-extensions, spanning the provider,
MCP, tool, and workflow contracts) into one redacted, read-only view — answering
"is this run confined the way I expect, and what will it be allowed to do?"
without running each diagnostic separately.
oh-my-cli --trust-posture --workspace path/to/repo
oh-my-cli --trust-posture --workspace path/to/repo --output json
# show the approval dimension and resolve extensions without probing
oh-my-cli --trust-posture --approval-mode yolo --no-probeIt is an audit, not a gate: it never mutates the trust store or settings and
always exits 0. The approval mode is reported as subordinate to folder trust —
the mutation line states permitted, DENIED (fail closed) (untrusted while
enforcing), or would be denied if enforcement were on (untrusted, advisory). An
invalid extension contract is surfaced as a visible warning rather than thrown
(the runtime contract resolvers still fail closed on their own). Add
--output json for a versioned record (schema oh-my-cli.trust-posture).
For CI and automation, pass --output json with -p to emit a versioned,
newline-delimited JSON event stream on stdout. The default human-readable output
is unchanged when the protocol is not selected.
oh-my-cli -p "Summarize README.md" --output jsonEach line is a self-describing record that parses independently:
{"protocol":"oh-my-cli.headless","v":1,"seq":0,"ts":"…","type":"start","sessionId":"…","model":"…","prompt":"…"}
{"protocol":"oh-my-cli.headless","v":1,"seq":1,"ts":"…","type":"assistant","round":0,"final":true,"text":"…","truncated":false}
{"protocol":"oh-my-cli.headless","v":1,"seq":2,"ts":"…","type":"complete","ok":true,"exitCode":0,"rounds":1,"reason":"completed"}- Envelope — every record carries
protocol(oh-my-cli.headless),v(schema version), a monotonicseq, an ISOts, and atype. - Events —
start,assistant(one per turn),tool_start,tool_result(okreflects success),usage(cumulative tokens and cost estimate per round, with budget state),retry(a transient provider failure is retried with bounded backoff),error(stageisproviderorinternal), and a terminalcomplete. - Exit semantics — the
completerecord'sexitCodealways equals the process exit code (0success,1failure), so wrappers can compare the terminal record against$?. - Safety — secrets and home paths are redacted and oversized payloads are
truncated (with a
truncatedflag); the stream stays clean for machine use.
For unattended runs, pass --summary to append a privacy-safe execution summary
after the run. It is opt-in: interactive sessions and plain -p runs are
unchanged unless you request it. The summary is metadata only — outcome,
exit code, classified reason, elapsed time, rounds, provider retries, bounded
tool-call/failure counts, token totals, and a cost estimate — and never carries
prompt, tool, or file content. Secret-shaped strings are redacted and the host home directory is
collapsed to ~, so the session log path stays private.
In text mode the summary prints a short block after the run:
oh-my-cli -p "Run the build" --summary
# …
# Run summary (oh-my-cli.summary v1)
# outcome: success
# exit code: 0
# reason: completed
# elapsed: 2.0s
# rounds: 1
# retries: 0
# tool calls: 1 (shell×1)
# tokens: prompt 5, completion 5, total 10
# est. cost: $0.000090 (estimate, not billing)
# evidence: session 01J… (~/.oh-my-cli/sessions/01J….jsonl)In --output json mode the same data arrives as a versioned summary event
emitted just before the terminal complete, so CI can retain it as run evidence:
oh-my-cli -p "Run the build" --output json --summary \
| tee run.ndjson \
| grep '"type":"summary"' # keep the evidence line for the job log{"protocol":"oh-my-cli.headless","v":1,"seq":3,"ts":"…","type":"summary","summary":{"schema":"oh-my-cli.summary","v":1,"outcome":"success","exitCode":0,"reason":"completed","elapsedMs":2000,"rounds":1,"retries":0,"toolCalls":{"total":1,"byName":{"shell":1}},"toolFailures":{"total":0,"byName":{}},"tokens":{"prompt":5,"completion":5,"total":10},"estimatedCostUsd":0.00009,"evidence":{"sessionId":"01J…","sessionPath":"~/.oh-my-cli/sessions/01J….jsonl"}}}The outcome is success or failure; on failure the reason classifies the
terminal state (provider_error, max_rounds, budget_reached, or error) and
the exitCode preserves the process exit code, so a wrapper can compare the
summary against $?. Distinct tool names are capped (overflow rolls into
__other__) to keep the summary bounded regardless of how many tools a run
touched.
Token usage is surfaced as it accrues: in --output json mode a usage event
is emitted once per round with cumulative prompt/completion/total tokens, a cost
estimate, and the budget state, and the run summary carries the same estimated
cost. The cost is an estimate from a bundled price table, never authoritative
billing — models not in the table fall back to a conservative rate and are
flagged as unknown (costKnown: false), so an unlisted model is over-counted
rather than under-counted.
To cap an unattended run, pass a spend budget in USD with --budget (or the
OMC_SPEND_BUDGET_USD environment variable; the flag takes precedence). Once the
running estimate reaches the cap, the loop stops before issuing further
provider calls — no additional billable calls are made — and the run ends with
reason budget_reached (exit 1 in headless mode; an actionable
Spend budget reached … line is printed to stderr otherwise). The budget is
checked before each call, so the first call always runs and the estimate is
cumulative across rounds. An invalid budget (non-positive or non-numeric) fails
fast before any provider call.
# Cap a run at half a cent; it stops before spending more
oh-my-cli -p "Refactor the parser" --budget 0.005 --output json --summaryTransient provider failures — HTTP 429, 500/502/503/504, and retryable
network errors (ECONNRESET, ETIMEDOUT, …) — are retried automatically with
bounded exponential backoff, so a momentary blip doesn't fail an otherwise
healthy run. The policy is bounded for unattended use: at most 3 attempts,
each wait capped at 2 seconds, so the worst-case added latency is small and can
never hang. A server Retry-After header is honored (clamped to the per-attempt
cap). Non-retryable failures (auth, invalid request, unsupported model) surface
immediately without a retry.
A retry happens only before any output for that attempt is produced, so a mid-stream failure is never silently restarted (which would duplicate partial assistant text). Each retry is observable:
- In
--output jsonmode aretryevent carries the upcomingattempt,maxAttempts, areasonClass(rate_limited,server_error, ornetwork_error), and the scheduleddelayMs. - The run summary reports the run's total
retries, so a consumer can distinguish an exhausted-retry failure (retries > 0, thenprovider_error) from a non-retryable one (retries: 0).
To compare two unattended runs, save each run's summary and diff them with
--baseline / --candidate. The scorecard is deterministic and evidence-based:
it reports the stable deltas (outcome, elapsed time, retry/failure counts, and
completed work) between a baseline and a candidate, and never invents a universal
quality score. Like the summary, it is metadata only — no prompts, secrets, host
paths, session ids, or tool payloads appear in the output.
# Save a run's summary as evidence (text block or the JSON `summary` event both work)
oh-my-cli -p "Run the build" --output json --summary | tee baseline.ndjson
# … later, after a change …
oh-my-cli -p "Run the build" --output json --summary | tee candidate.ndjson
oh-my-cli --baseline baseline.ndjson --candidate candidate.ndjsonRun scorecard (oh-my-cli.scorecard v1)
outcome: success -> failure [REGRESSION]
reason: completed -> error
elapsed ms: 2000 -> 5200 (+3200, up) [REGRESSION]
rounds: 1 -> 4 (+3, up)
tool calls: 1 -> 6 (+5, up)
tool failures: 0 -> 2 (+2, up) [REGRESSION]
completed work: 1 -> 4 (+3, up)
tokens total: 10 -> 40 (+30, up)
thresholds: elapsed ratio <= 0.25, failure delta <= 0
Result: REGRESSION (exit 1)
- outcome regressed (success -> failure)
- tool failures rose more than 0 above baseline
- elapsed time rose more than 25% above baseline
Inputs may be a bare summary object or a headless NDJSON stream (the summary
event is extracted automatically). Add --output json for a machine-readable
scorecard. The exit code is a documented contract for CI:
0— no documented regression threshold was crossed.1— a regression was flagged: the outcome regressed (success→failure), tool failures rose above--max-failure-delta(default0), or elapsed time rose above--max-elapsed-ratio(default0.25, i.e. +25%).2— a usage or input error (missing/only one file, malformed or version-incompatible summaries, or an invalid threshold).
An interrupted unattended run can otherwise require a full restart, repeating
work that already finished. --recover implements one bounded recovery path: it
resumes from a durable checkpoint and reports which steps are proven complete
and safe to skip, so a completed step is never executed twice.
A checkpoint records only identities and content digests — never raw evidence, prompts, secrets, or host paths. Completion is proven by matching the durable evidence digest of each completed step, never by parsing log text. The check fails closed: a stale (repository head moved), ambiguous (different task identity), or tampered (a completed step's evidence changed) checkpoint is refused without any mutation.
# A checkpoint written after step "build" completed, before the run was interrupted
oh-my-cli --recover \
--checkpoint checkpoint.json \
--task-identity deploy-task \
--evidence evidence.json \
--workspace path/to/repoRun recovery (oh-my-cli.recovery v1)
────────────────────────────────────────
decision: resume
reason: all completed-step evidence verified; safe to skip the proven steps
task: deploy-task
repo head: 3a61045b781f27e95b496ede6dfd23d0b63a6b4b
completed: 1 step(s) safe to skip:
- build
The checkpoint stores the task identity, the repository head, and each completed
step's id and evidence digest; the evidence file is a flat JSON object of
stepId -> digest for the artifacts present now. Add --output json for a
versioned plan (schema oh-my-cli.recovery) whose completed array lists the
steps to skip. The exit code is a documented contract:
0—resume: task identity and repository head match and every completed step's evidence still verifies; the listed steps are safe to skip.1—refuse: the checkpoint is stale, ambiguous, or tampered; do not resume.2— a usage or input error (missing--checkpoint/--task-identity, or a malformed/incompatible checkpoint or evidence file).
Two mutating agents in one workspace can silently overwrite or corrupt each
other's work. --create-worktree carves out one leased git worktree per
mutating agent so they run in isolation, and --clean-worktree removes a lease
again only after its work is verified complete. The lease identity (branch +
worktree path) is derived deterministically from the repository, the
--task-identity, and the --agent-identity, so the same task+agent always maps
to the same lease (idempotent across interruption) while distinct agents never
collide.
# Give each mutating agent its own isolated worktree for one task
oh-my-cli --create-worktree --workspace path/to/repo \
--task-identity deploy-task --agent-identity worker-1
oh-my-cli --create-worktree --workspace path/to/repo \
--task-identity deploy-task --agent-identity worker-2
# After the agent's branch is merged, clean its lease
oh-my-cli --clean-worktree --workspace path/to/repo \
--task-identity deploy-task --agent-identity worker-1Worktree lease (oh-my-cli.worktree-lease v1)
────────────────────────────────────────
action: create
result: ok
status: created
lease: d0a3e7bc65723c0e
branch: lease/wt-d0a3e7bc65723c0e
worktree: ~/code/repo/.oh-my-cli/worktrees/d0a3e7bc65723c0e
base: 3a61045b781f
task: deploy-task
agent: worker-1
Creation is fail-closed and refuses before any mutation when the target is
not a repository, the parent worktree is dirty, the identity is ambiguous
(missing --task-identity/--agent-identity, or a repository with no commit to
base from), or the lease already exists in a partial/conflicting state. Cleanup
never deletes work: it refuses a worktree with uncommitted changes or a
branch with unmerged commits, uses only non-forcing git commands, and never
touches the parent worktree. There is no automatic merge and no forced removal.
Both operations are idempotent — re-creating an existing lease returns it, and
cleaning an absent lease is a no-op. Leased worktrees live under
<workspace>/.oh-my-cli/worktrees (git-ignored) by default; pass
--worktree-root to keep them elsewhere. Add --output json for a versioned
record (schema oh-my-cli.worktree-lease); host home paths and secrets stay
redacted. The exit code is a documented contract:
0— success: the lease was created or cleaned, or the request was an idempotent no-op.1— a safety refusal: non-repository, dirty parent, ambiguous target, already-leased, uncommitted changes, or unmerged commits.2— a usage error (missing identities, both--create-worktreeand--clean-worktree, invalid--output) or an unexpected git failure.
The shell tool runs an arbitrary /bin/bash -c <command>. Before any shell
command executes, a deterministic, offline command policy classifies what it
will do and denies a small set of known-dangerous shapes — naming the violated
rule — without running it. The same gate protects interactive runs and is
applied before approval, so a denial cannot be bypassed by --approval-mode yolo; commands that pass keep the existing approval/yolo behavior unchanged.
The policy distinguishes trusted builtin commands from repository/issue -provided ones (provenance). Only untrusted provenance is denied. It always classifies network use, writes, credential access, destructive Git, and path escape, and it denies:
destructive_git— force push /--delete/--mirror/+/:refspecs,reset --hard,clean -f/-d,branch -D,checkoutdiscard,filter-branch.credential_access— reading~/.ssh,id_rsa,.env,*.pem,*.key,~/.aws/credentials,/etc/shadow, …, or printing secret env vars.path_escape— writes (or>/>>redirects) that resolve outside the workspace.destructive_removal—rm -r/-Raimed at/,~,$HOME,., or...device_overwrite—dd of=/dev/…,mkfs/fdisk/parted, redirects onto a device.
It is a bounded quote/substitution-aware tokenizer over the compiled command
string (not a general shell parser): it descends into $(...), backticks, and
subshells, sees through sudo/env/VAR=val wrappers, and ignores
dangerous-looking text inside quotes (echo "rm -rf /" is allowed). Evaluate any
command offline with --command-policy:
oh-my-cli --command-policy "git push --force"
oh-my-cli --command-policy "cat ~/.ssh/id_rsa" --output jsonCommand policy (oh-my-cli.command-policy v1)
decision: deny
provenance: repository
command: git push --force
classify: network=no write=no credential=no destructive-git=yes path-escape=no
violations:
- destructive_git: git push --force / --delete / refspec rewrites remote history
The exit code is a documented contract:
0— allowed.1— denied (one or more violations).2— a usage error (invalid--provenanceor--output).
Run summaries and recovery checkpoints are otherwise machine-local, which makes
independent audit and regression reproduction hard. --export-evidence bundles
the durable, already-redacted evidence of a run — the versioned run summary,
recovery checkpoint metadata, command outcomes, and content digests — into one
portable, deterministic JSON archive with a signed manifest. --verify-evidence
checks that archive offline.
The bundle carries only metadata and digests — never prompts, raw tool
payloads, credentials, or absolute host paths (free-form values are
secret-redacted and the home directory is collapsed to ~). It is built for
three guarantees:
- Privacy — no sensitive content reaches the archive.
- Determinism — identical normalized evidence yields byte-identical bytes (sorted keys, entries ordered by name, no wall-clock timestamps).
- Integrity — each entry carries a sha256 of its content and the manifest carries a sha256 signature, so verification fails closed on any missing, extra, reordered, or modified entry. ("Signed" is a deterministic integrity digest — there is deliberately no key management in this slice.)
# Export one portable bundle from a run's artifacts
oh-my-cli --export-evidence evidence-bundle.json \
--summary-file summary.json \
--checkpoint checkpoint.json \
--outcomes-file outcomes.json \
--task-identity deploy-task
# Verify a bundle offline (human or machine-readable)
oh-my-cli --verify-evidence evidence-bundle.json
oh-my-cli --verify-evidence evidence-bundle.json --output jsonEvidence archive (oh-my-cli.evidence-archive v1)
────────────────────────────────────────
task: deploy-task
outcome: success
repo head: 3a61045b781f27e95b496ede6dfd23d0b63a6b4b
entries: 3
- checkpoint (checkpoint-metadata) 2f0cf65114dd…
- command-outcomes (command-outcomes) e8638708332b…
- run-summary (run-summary) e6b89a9f130a…
signature: 3768d82e18366cff751258b98761ec87506b011c34e72e0923e645819e32bcbb
The exit code is a documented contract:
--export-evidence:0on success,2on a usage/input error (no inputs, or an unreadable/malformed artifact).--verify-evidence:0when the bundle is intact,1when it is tampered,2on a usage error (unreadable/malformed bundle).
After installing, run a read-only health check to catch runtime, resolution, state-directory, and platform problems before a real task does:
oh-my-cli --doctorIt verifies the Node runtime version, that the CLI entry is present, that the
state directory is writable (or creatable), and that the platform is supported.
Each check is categorized ✓ pass, ⚠ warning, or ✗ failure with actionable
remediation. The command never installs, creates, or edits anything, redacts
host paths and secrets, and exits 0 when there are no failures (1 otherwise).
When an autonomous task is blocked on a repository prerequisite, run a read-only inspection to explain the single blocker with bounded, structured evidence:
oh-my-cli --readiness
# or point it at another checkout / expected branch / remote
oh-my-cli --readiness --workspace path/to/repo --expected-branch main --remote originIt checks the working tree (clean vs. uncommitted changes), the branch (on a
branch, detached, or not the --expected-branch), the test command (a test
script in package.json), required tools (on PATH; git by default), and the
remote (configured and reachable). Each check reports ✓/✗ with a redacted
detail and, when failing, a safe next action — a recommendation that is
never executed. The command inspects repository-local and Git metadata only; it
never installs, creates, edits, fetches into, or otherwise mutates anything, and
secrets and host paths stay redacted.
Repository readiness (oh-my-cli.readiness v1)
────────────────────────────────────────
✓ Worktree clean
✓ Branch on "main"
✓ Test command vitest run
✓ Required tools git available
✓ Remote remote "origin" reachable
Ready: no blocker detected.
Add --output json for a versioned report (schema oh-my-cli.readiness) whose
blocker names the first failing check (or null when ready). The exit code is a
documented contract for CI: 0 when ready (no blocker), 1 when blocked.
To see how the CLI models the repository it is working in — before entrusting it with a task — run a read-only context probe:
oh-my-cli --repo-context
# or point it at another checkout
oh-my-cli --repo-context --workspace path/to/repoIt reports the toolchain (package manager + lockfile), the canonical
build/test/typecheck/lint commands resolved from package.json scripts,
Makefile targets, or pyproject.toml tool sections (reported, never run), the
primary languages by a bounded file-extension signal, a bounded top-level
structure outline, and the current VCS state (branch and clean/dirty). The probe
inspects workspace-local files and Git metadata only; it never installs,
creates, edits, executes, fetches into, or otherwise mutates anything, and
secrets and host paths stay redacted.
Repository context (oh-my-cli.repo-context v1)
──────────────────────────────────────────────
Toolchains : npm (package.json, package-lock.json)
Commands :
build [package.json] tsc
test [package.json] vitest run tests/unit
typecheck [package.json] tsc --noEmit
lint —
Languages : TypeScript (84 files; .ts), Markdown (6 files; .md), JSON (3 files; .json)
Structure : src/ tests/ docs/ package.json tsconfig.json …
VCS : on "main" — clean
Add --output json for a versioned record (schema oh-my-cli.repo-context)
that a downstream planning or verification step can parse independently. The
probe is a snapshot, not a gate, so it always exits 0; an unknown toolchain
degrades gracefully (reports unknown rather than failing).
A fresh session otherwise starts with no automatic view of the workspace: to use existing code the model must be told which files to read or guess paths. To give it a concise, always-current overview, the CLI builds a bounded, ranked map of the workspace's key files and their top-level symbols (signatures only) and injects it into every fresh session's context, so the model can locate relevant code and reuse existing abstractions. Inspect the same map directly:
oh-my-cli --repo-map
# point it at another checkout, and/or set the token budget (default 1024)
oh-my-cli --repo-map --workspace path/to/repo --map-tokens 512The map honors .gitignore and a built-in skip set, never follows symlinks (so
it cannot escape the workspace), and excludes binary and likely-secret paths.
Only declaration signatures are surfaced — never file bodies — and every
signature is secret-redacted and length-capped. Files are ranked by relevance
(symbol density, entry-point and source-dir bonuses, depth and test penalties),
each file shows at most its first key declarations, and the rendered map is
clipped to the token budget (≈4 chars per token), so a large repository stays
responsive and cannot flood context.
Repository map (oh-my-cli.repo-map v1)
──────────────────────────────────────
Files : 10 of 179 symbol-bearing; budget 4096 chars, used 4073
src/index.ts
export function main()
export class App
…
… truncated: 169 more symbol-bearing file(s) omitted
Add --output json for a versioned record (schema oh-my-cli.repo-map). The
map is a snapshot, not a gate, so it always exits 0; an empty, untrusted, or
unreadable workspace yields an explicit, non-destructive result rather than a
crash.
Every fresh model-backed session is seeded with the effective instruction context instead of a generic "you are a helpful coding assistant" prompt. To see exactly what a session will load — and why — run a read-only probe:
oh-my-cli --instruction-context
# or point it at another checkout
oh-my-cli --instruction-context --workspace path/to/repoIt discovers supported instruction files (QWEN.md, AGENTS.md) from the
trusted workspace hierarchy — the workspace root plus a bounded walk of its
ancestor directories — and reports each source's trust class, precedence, byte
size, and content fingerprint. Files inside the workspace are workspace trust;
files in a strict ancestor directory are ancestor trust and lower precedence,
so an out-of-workspace instruction can never override the workspace's own policy
on conflict. A symlinked instruction file whose real path escapes its directory
is rejected (recorded as symlink-escape). All content is treated strictly as
data — it can never activate tools, change configuration, or override any policy
or safety boundary — and secrets, spoofing characters, and host paths stay
redacted.
Instruction context (oh-my-cli.instruction-context v1)
──────────────────────────────────────────────
Loaded : 1 file(s)
Sources :
[workspace] QWEN.md — prec 91, 348 bytes
Fingerprint: 9f2c4e…
Add --output json for a versioned record (schema
oh-my-cli.instruction-context) whose combinedText is the framed block injected
into the session prompt and whose fingerprint changes iff that block does. The
probe is a snapshot, not a gate, so it always exits 0; an empty workspace
degrades gracefully (no sources, empty combinedText).
Before entrusting the agent with a task, derive a bounded, deterministic plan grounded in the repository context — so execution has an objective sequence a later verification or review step can check against:
oh-my-cli --plan "add a feature"
# or point it at another checkout
oh-my-cli --plan "fix a bug" --workspace path/to/repoIt emits a fixed, dependency-ordered phase sequence — understand → implement → verify → review — whose verify step is grounded in the canonical
build/test/typecheck/lint commands the repository-context probe actually
detected (listed, never run). The objective and every command are secret-redacted
and bounded, and the plan is deterministic for fixed inputs (same task + same
repository state → same plan). The planner inspects the workspace only; it never
executes the commands it lists, never calls a provider, and never mutates
anything. When no canonical verification command is detected, the verify step
degrades gracefully to a manual-verification note rather than inventing commands.
Task plan (oh-my-cli.plan v1)
──────────────────────────────────────────────
Objective : add a feature
Toolchain : npm
Steps :
1. understand Read the relevant files and the repository context (toolchain: npm) before editing.
2. implement Make the minimal change described by the objective, confined to the workspace.
3. verify Run the detected verification commands and confirm they pass before finishing.
- tsc
- vitest run tests/unit
- tsc --noEmit
4. review Summarize the change and produce completion evidence (diff and test results).
Add --output json for a versioned record (schema oh-my-cli.plan) whose
steps array carries the ordered phases and whose verifyCommands lists the
grounded verification commands. An empty task description is rejected (exit 2);
an unknown toolchain degrades gracefully rather than failing.
After a change, get an objective, machine-checkable answer to "does this
repository actually pass its own build/test/typecheck/lint?" --verify-task
runs the same canonical commands the planner lists — build → test → typecheck → lint, as detected from the repository context — against the
workspace, with a bounded per-command timeout and bounded output capture, and
reports a pass/fail verdict bound to the repository head:
oh-my-cli --verify-task
# or verify another checkout
oh-my-cli --verify-task --workspace path/to/repoIt runs only the commands the repository itself declares (the same ones a
developer runs by hand) and never accepts arbitrary command strings. Captured
output is secret-redacted and the absolute workspace path is scrubbed; the
command set is exactly what --plan reports. The verdict is pass only when
every detected command passes, fail if any fails, or no-verify-commands
when none are detected (not a failure). Exit code: 0 pass/none-detected, 1
any failure, 2 usage error.
Task verification (oh-my-cli.task-verify v1)
──────────────────────────────────────────────
Head : 9f53e65c64b8dbd9a2616e61d4db18a62ea298ed
Verdict: pass
Commands:
[PASS] build tsc (exit 0, 1180ms)
[PASS] test vitest run tests/unit (exit 0, 8423ms)
[PASS] typecheck tsc --noEmit (exit 0, 1095ms)
Add --output json for a versioned record (schema oh-my-cli.task-verify)
whose head binds the verdict to the repo head and whose results array
carries each command's exit code, pass flag, duration, and redacted output tail.
Before opening or merging a PR, get an objective, machine-checkable answer to
"what does this change actually alter, and does it introduce an obvious,
reviewable risk?" --review-change computes the change set between a base ref
(default origin/main, then HEAD) and the current head/worktree using Git
only, and emits a bounded, redacted, head-bound review brief:
oh-my-cli --review-change
# or review against an explicit base
oh-my-cli --review-change --base origin/main --workspace path/to/repoIt runs no commands and calls no provider; every signal is objective and
reproducible. Secret-like content is reported only as a count (never literals)
and the absolute workspace path never appears in the output. The verdict is
needs-attention when any objective signal fires — a secret-like string
introduced, a protected governance/security/license path mutated, source
changed without a corresponding test change, an oversized change, or a new
runtime dependency added — clean otherwise, or no-change for an empty diff.
Exit code: 0 clean/no-change, 1 needs-attention, 2 usage error.
Change review (oh-my-cli.change-review v1)
──────────────────────────────────────────────
Head : 2426b8272da5c9d56c9d0a4b9355eaa5e6b6f8ac
Base : origin/main (9f53e65c64b8dbd9a2616e61d4db18a62ea298ed)
Verdict: clean
Changes: 3 file(s), +410 -0
Files:
A src/change-review.ts (+180 -0)
M src/index.ts (+24 -0)
A tests/unit/change-review.test.ts (+206 -0)
Signals:
Secrets introduced : 0 added line(s)
Protected paths : none
Source w/o tests : no
Oversized change : no
Runtime deps : n/a
Add --output json for a versioned record (schema oh-my-cli.change-review)
whose head binds the brief to the repo head and whose signals object carries
each objective risk signal.
At the CI boundary, get one objective answer to "is this change safe to hand to
CI, and what should CI run?" --ci-handoff composes the verify and review
slices into a single bounded, redacted, head-bound handoff brief: the exact
commit, the canonical commands CI should run with their local pass/fail
status, the change summary and review signals, and any local blocker that must
be cleared first.
oh-my-cli --ci-handoff
# or hand off against an explicit base
oh-my-cli --ci-handoff --base origin/main --workspace path/to/repoIt runs only the repository's own canonical verify commands (the same ones
--verify-task runs); it never mutates the repository or governance paths and
never calls a provider. Secret-like content is reported only as a count (never
literals) and the absolute workspace path never appears in the output. The
verdict is local-blockers when an introduced secret, a mutated protected
governance path, or a failing local verify is present; no-change when nothing
changed; otherwise ready-for-ci. Exit code: 0 ready-for-ci/no-change, 1
local-blockers, 2 usage error.
CI handoff (oh-my-cli.ci-handoff v1)
──────────────────────────────────────────────
Head : 2426b8272da5c9d56c9d0a4b9355eaa5e6b6f8ac
Base : origin/main (9f53e65c64b8dbd9a2616e61d4db18a62ea298ed)
Verdict: ready-for-ci
Change : 3 file(s), +410 -0
Commands for CI:
[PASS] build tsc (exit 0)
[PASS] test vitest run tests/unit (exit 0)
[PASS] typecheck tsc --noEmit (exit 0)
Review signals:
Secrets introduced : 0 added line(s)
Protected paths : none
Source w/o tests : no
Oversized change : no
Runtime deps added : none
Blockers: none
Add --output json for a versioned record (schema oh-my-cli.ci-handoff)
whose head binds the brief to the repo head and whose commands, review,
and blockers fields carry the handoff evidence.
After CI finishes, get one objective answer to "is this change ready to ship?"
--delivery-brief composes the plan, verify, review, and CI-handoff slices with
a bounded CI result into a single bounded, redacted, head-bound completion
verdict: ship, hold, or no-ship. It is the capstone of the verify → review
→ handoff → deliver arc — the pre-CI handoff brief answers "is this safe to hand
to CI?", while the delivery brief answers "is this clear to ship?" once the CI
result is known.
oh-my-cli --delivery-brief --ci-result pass
# or deliver against an explicit base
oh-my-cli --delivery-brief --ci-result pass --base origin/main --workspace path/to/repoThe CI outcome is a bounded, validated input (--ci-result pass|fail|pending,
default pending) so the verdict stays deterministic and offline. It runs only
the repository's own canonical verify commands (via the handoff slice); it never
mutates the repository or governance paths and never calls a provider. Secret-
like content is reported only as a count (never literals) and the absolute
workspace path never appears in the output. The verdict is no-ship when an
introduced secret, a mutated protected governance path, a failing local verify,
or a failed CI is present; hold when there is no change to deliver, CI is still
pending, or the plan has no grounded verification command; otherwise ship. Exit
code: 0 ship, 1 hold/no-ship, 2 usage error.
Delivery brief (oh-my-cli.delivery-brief v1)
──────────────────────────────────────────────
Head : 2426b8272da5c9d56c9d0a4b9355eaa5e6b6f8ac
Base : origin/main (9f53e65c64b8dbd9a2616e61d4db18a62ea298ed)
Verdict: ship
Change : 3 file(s), +410 -0
Signals:
plan grounded
verify pass
review clean
handoff ready-for-ci
ci pass
Blockers: none
Holds: none
Add --output json for a versioned record (schema oh-my-cli.delivery-brief)
whose head binds the verdict to the repo head and whose signals, blockers,
and holds fields carry the completion evidence.
The CLI's model configuration is a flat set of env-derived settings. To adapt the
CLI to a different provider without forking the core, declare one or more
providers as a versioned extension contract in the same unified settings file
(~/.oh-my-cli/settings.json, or --settings <path>). --provider-contract
negotiates the contract version, selects one provider, and resolves its
non-secret configuration — read-only, redacted, and without changing core code.
{
"providers": {
"contractVersion": 1,
"default": "alt",
"entries": [
{
"id": "alt",
"baseUrl": "https://alt.example/v1",
"model": "alt-model",
"models": ["alt-small", "alt-large"],
"apiKeyEnv": "ALT_KEY",
"capabilities": { "vision": true }
}
]
}
}oh-my-cli --provider-contract
# select one explicitly when several are declared
oh-my-cli --provider-contract --provider alt --output jsonThe contract is untrusted input. The credential is supplied by an
environment-variable name (apiKeyEnv, or OPENAI_API_KEY when absent) and its
value is never printed; a raw credential field (e.g. apiKey) inside an entry is
rejected rather than ignored; an unsupported contractVersion fails closed
instead of being silently coerced. Selection is deterministic: an explicit
--provider id wins, then settings.providers.default, then the sole entry;
ambiguity or an unknown id fails with a clear reason. The resolved endpoint is
reduced to its host and the home path is collapsed to ~. Credential
availability is reported (credentialAvailable) but not gated, so the contract
stays inspectable even when the credential is not currently exported. Exit code:
0 on success, 2 on a contract/usage error.
Provider: alt
Contract: oh-my-cli.provider-contract v1 (settings contract version 1)
Endpoint: alt.example (settings)
Model: alt-model
Catalog: alt-small, alt-large
Credential: ALT_KEY
Capabilities: vision
Settings: ~/.oh-my-cli/settings.json
Add --output json for a versioned record (schema oh-my-cli.provider-contract)
carrying the negotiated contractVersion, the selected providerId, the redacted
endpoint, modelCatalog, capabilities, and credential provenance.
The bounded health inventory (--health) lists every configured MCP server
and extension with a health category — a read-only snapshot. To depend on one
MCP server as a governed extension, declare it as a versioned contract in the
same unified settings file (~/.oh-my-cli/settings.json, or --settings <path>).
--mcp-contract negotiates the contract version, deterministically selects one
server, and resolves its lifecycle state — read-only, redacted, and without
changing core code.
{
"mcp": {
"contractVersion": 1,
"default": "filesystem",
"entries": [
{
"id": "filesystem",
"transport": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
"capabilities": { "tools": true }
}
]
}
}oh-my-cli --mcp-contract
# select one explicitly when several are declared
oh-my-cli --mcp-contract --server filesystem --output json
# resolve the declaration without probing (declared state)
oh-my-cli --mcp-contract --no-probeThis slice is limited to safe local/stdio transport: a server's command is
resolved on PATH but never executed, so probing cannot run arbitrary code.
Remote (http/sse) transports are refused (fail closed) in contract version 1. The
contract is untrusted input — a raw credential field inside an entry is rejected
rather than ignored, and an unsupported contractVersion fails closed instead of
being silently coerced. Selection is deterministic: an explicit --server id
wins, then settings.mcp.default, then the sole entry; ambiguity or an unknown id
fails with a clear reason.
The selected server resolves to one lifecycle state with safe failure defaults:
declared (valid contract, not probed — via --no-probe), ready (the command
is resolvable), or isolated (disabled, misconfigured, command missing, or the
bounded probe timed out). A disabled or unavailable server resolves to isolated
and the command still exits 0 — the consumer skips it without crashing. A
contract/usage error exits 2. Argument values are never printed (only their
count) and the home path is collapsed to ~.
Server: filesystem
Contract: oh-my-cli.mcp-contract v1 (settings contract version 1)
Transport: stdio
Command: npx
Arguments: 3
Enabled: true
State: ready [command resolved]
Probe: 1ms (timeout 3000ms)
Capabilities: tools
Settings: ~/.oh-my-cli/settings.json
Add --output json for a versioned record (schema oh-my-cli.mcp-contract)
carrying the negotiated contractVersion, the selected serverId, transport,
command, argCount, the resolved state and reason, and probeMs.
The built-in tool registry defines the agent's own capabilities (read, write,
shell). To depend on an external tool as a governed extension — a local
executable an operator declares for the core and non-interactive automation to
hand off to — declare it as a versioned contract in the same unified settings
file (~/.oh-my-cli/settings.json, or --settings <path>). --tool-contract
negotiates the contract version, deterministically selects one tool, and resolves
its readiness state — read-only, redacted, and without changing core code. It
completes the provider/tool/MCP contract triad alongside --provider-contract
and --mcp-contract.
{
"tools": {
"contractVersion": 1,
"default": "ripgrep",
"entries": [
{
"id": "ripgrep",
"kind": "command",
"command": "rg",
"args": ["--json"],
"capabilities": { "readOnly": true, "filesystem": true }
}
]
}
}oh-my-cli --tool-contract
# select one explicitly when several are declared
oh-my-cli --tool-contract --tool ripgrep --output json
# resolve the declaration without probing (declared state)
oh-my-cli --tool-contract --no-probeThis slice is limited to the safe local command kind: a tool's command is
resolved on PATH but never executed, so probing cannot run arbitrary code.
Remote or network tools (a url field or a non-command kind) are refused (fail
closed) in contract version 1. The contract is untrusted input — a raw credential
field inside an entry is rejected rather than ignored, and an unsupported
contractVersion fails closed instead of being silently coerced. Selection is
deterministic: an explicit --tool id wins, then settings.tools.default, then
the sole entry; ambiguity or an unknown id fails with a clear reason.
The selected tool resolves to one readiness state with safe failure defaults:
declared (valid contract, not probed — via --no-probe), ready (the command
is resolvable), or isolated (disabled, misconfigured, command missing, or the
bounded probe timed out). A disabled or unavailable tool resolves to isolated
and the command still exits 0 — the consumer skips it without crashing. A
contract/usage error exits 2. Argument values are never printed (only their
count) and the home path is collapsed to ~.
Tool: ripgrep
Contract: oh-my-cli.tool-contract v1 (settings contract version 1)
Kind: command
Command: rg
Arguments: 1
Enabled: true
State: ready [command resolved]
Probe: 1ms (timeout 3000ms)
Capabilities: readOnly, filesystem
Settings: ~/.oh-my-cli/settings.json
Add --output json for a versioned record (schema oh-my-cli.tool-contract)
carrying the negotiated contractVersion, the selected toolId, kind,
command, argCount, the resolved state and reason, and probeMs.
--provider-contract is read-only — it resolves a provider's configuration but
never issues a request. --invoke-provider is the governed next step: it issues
one bounded model request to one resolved-ready provider once,
non-interactively, reusing the same providers contract, without changing core
code.
# invoke the default (or sole) ready provider with a minimal safe ping
oh-my-cli --invoke-provider --approval-mode yolo
# select one explicitly, send a prompt, and emit a versioned JSON result
oh-my-cli --invoke-provider --provider alt --provider-prompt "hello" --output json
# bound a slow provider with a shorter hard timeout
oh-my-cli --invoke-provider --approval-mode yolo --invoke-timeout 5000Every gate runs before the request and fails closed:
- Readiness — only a
readyprovider is called; one whose credential environment variable is unset or whose endpoint is not a valid URL is refused. - Approval mode — a provider request is a network call to an external model
API (it can spend credit and read whatever the endpoint returns), so it is
gated as the most cautious category:
defaultandauto-editrequire approval (an interactive terminal may grant it); a non-interactive run is refused unless the mode isyolo.
The request is bounded in three ways: a hard timeout (--invoke-timeout, default
30s, max 300s) aborts the in-flight call, a bounded generation (max tokens)
caps what the provider returns, and an output-size cap bounds what enters the
report. The credential is supplied by an environment-variable name and its value
is never printed, logged, or sent through the approval prompt — only the variable
name is reported. The endpoint is reduced to its host, the prompt is reported as
a character count (never echoed), and the response is redacted (secrets and
home/workspace paths) in both text and JSON. Exit codes: 0 on a response with
content; 2 for a contract/selection/version error, a non-ready provider, or a
missing approval (refused before calling); 1 for a request runtime failure (an
empty response, auth rejection, unsupported model, rate limiting, network/API
error, timeout, or oversized output) — the run never crashes.
Provider: alt
Contract: oh-my-cli.provider-invocation v1 (settings contract version 1)
Endpoint: alt.example (settings)
Model: alt-model
Credential: ALT_KEY
Prompt: 4 chars
Gate: passed
Invoked: true
Outcome: called
Tokens: 8 (prompt 3, completion 5)
Bounds: 123ms (timeout 30000ms, max tokens 256, output cap 65536 bytes)
Reason: provider returned a response
Result: pong
Settings: ~/.oh-my-cli/settings.json
--tool-contract is read-only — it resolves readiness but never runs the tool.
--invoke-tool is the governed next step: it invokes one resolved-ready
tool extension once, non-interactively, reusing the same tools contract and the
command trust policy (--command-policy), without changing core code.
# invoke the default (or sole) ready tool, confined to the workspace
oh-my-cli --invoke-tool --approval-mode yolo --workspace ./project
# select one explicitly and emit a versioned JSON result
oh-my-cli --invoke-tool --tool ripgrep --approval-mode yolo --output json
# bound a slow tool with a shorter hard timeout
oh-my-cli --invoke-tool --approval-mode yolo --invoke-timeout 5000Every gate runs before execution and fails closed:
- Readiness — only a
readytool is invoked; adeclaredorisolatedtool (disabled, misconfigured, or command missing) is refused. - Command policy — the declared
commandandargsare evaluated as untrusted input, confined to the workspace; a denied command (destructive git, credential access, path escape, destructive removal, device overwrite) is not executed. - Approval mode — a command tool is gated as a shell mutation, so
defaultandauto-editrequire approval (an interactive terminal may grant it); a non-interactive run is refused unless the mode isyolo.
The command runs directly (no shell, so arguments cannot be reinterpreted),
confined to the workspace and bounded by a hard timeout (--invoke-timeout,
default 30s, max 300s) and an output-size cap. Captured output is redacted
(secrets and home/workspace paths) in both text and JSON. Exit codes: 0 on a
successful invocation; 2 for a contract/selection/version error, a non-ready
tool, a policy denial, or a missing approval (refused before execution); 1 for
a tool runtime failure (timeout, oversized output, non-zero exit, or a spawn
error) — the run never crashes.
--mcp-contract is read-only — it resolves a server's lifecycle state but never
connects. --invoke-mcp is the governed next step: it connects to one
resolved-ready MCP server over the safe local stdio transport, performs the
initialize handshake, lists tools, and calls one tool once,
non-interactively — reusing the same mcp contract and the command trust policy
(--command-policy), without changing core code.
# connect to the default (or sole) ready server and call its sole tool
oh-my-cli --invoke-mcp --approval-mode yolo --workspace ./project
# select a server and one of its tools, with arguments, as versioned JSON
oh-my-cli --invoke-mcp --server filesystem --mcp-tool read_file \
--mcp-arg path=README.md --approval-mode yolo --output json
# bound a slow server with a shorter hard timeout
oh-my-cli --invoke-mcp --approval-mode yolo --invoke-timeout 5000Every gate runs before connecting and fails closed:
- Readiness — only a
readyserver is connected; adeclaredorisolatedserver (disabled, misconfigured, or command missing) is refused. - Command policy — the declared
commandandargsare evaluated as untrusted input, confined to the workspace; a denied command (destructive git, credential access, path escape, destructive removal, device overwrite) is not spawned. - Approval mode — connecting to a server is gated as a shell mutation, so
defaultandauto-editrequire approval (an interactive terminal may grant it); a non-interactive run is refused unless the mode isyolo.
The server command runs directly (no shell, so arguments cannot be
reinterpreted), confined to the workspace; the whole session (connect, handshake,
list, and call) is bounded by a single hard timeout (--invoke-timeout, default
30s, max 300s) and an output-size cap. The tool result is redacted (secrets and
home/workspace paths) in both text and JSON, and only text content parts are
captured. Tool selection is deterministic: an explicit --mcp-tool wins,
otherwise the sole exposed tool is used; ambiguity or an unknown name fails
closed. Exit codes: 0 on a successful tool call; 2 for a
contract/selection/version error, a non-ready server, a policy denial, or a
missing approval (refused before connecting); 1 for a session runtime failure
(handshake failure, timeout, oversized output, tool-selection ambiguity, a
tool-level error, or a spawn error) — the run never crashes.
Once providers (--provider-contract), MCP servers (--mcp-contract), tools
(--tool-contract), and workflows (--list-workflows) are declared as versioned
contracts, --discover-extensions composes all four resolvers into a single
read-only, redacted view of which extension surfaces are declared and ready —
without re-probing every integration (--health) and without changing core code.
It reads the same unified settings file and reports, per surface, the negotiated
contract version, declared entry count, default, and the entry a consumer would
select (plus the MCP and tool selected entries' lifecycle/readiness state). A
workflow has no default and no external entrypoint to probe — it is selected by
explicit name at run time — so the workflow surface reports only the definition
count and the contract-level readiness (ready whenever the contract negotiates
and validates).
oh-my-cli --discover-extensions
oh-my-cli --discover-extensions --output json
# resolve the declarations without probing (MCP and tool reported as declared)
oh-my-cli --discover-extensions --no-probeA surface with no declared section is reported as absent (not an error), and
a missing settings file reports every surface absent — the command still exits
0. An invalid contract (unsupported version, raw credential field, malformed
section) fails closed and exits 2, the same guarantee each contract provides on
its own. Multiple entries with no default are reported as ambiguous (no
selection) rather than failing — discovery never picks on your behalf. No secret
value, argument value, or remote response body is ever printed; only counts, ids,
negotiated versions, and lifecycle state appear, with the home path collapsed to
~.
Extension Discovery
────────────────────────────────────────
Settings: ~/.oh-my-cli/settings.json
Schema: oh-my-cli.extension-discovery v1
Provider contract: 2 entries (contract version 1)
Default: primary
Selected: primary
MCP contract: 1 entry (contract version 1)
Default: filesystem
Selected: filesystem
State: ready [command resolved]
Tool contract: 1 entry (contract version 1)
Default: ripgrep
Selected: ripgrep
State: ready [command resolved]
Workflow contract: 1 definition (contract version 1)
State: ready [1 workflow definition resolvable]
Add --output json for a versioned record (schema
oh-my-cli.extension-discovery) whose surfaces array carries one entry per
contract (kind provider / mcp / tool / workflow), each flagged
present, with its contractVersion, entryCount, default, selectedId, and
— for MCP and tool — the resolved state, stateReason, and probeMs. The
workflow surface carries default and selectedId as null (workflows are
selected by explicit name at run time) and reports the contract-level readiness
state with no probeMs.
Where --discover-extensions reports the version a present section negotiated,
--extension-compat answers the proactive, pre-run question "will this settings
file's extension contracts work on this build?" It publishes the supported
contract-version matrix for all four surfaces (provider, tool, MCP, workflow) —
schema id plus supported version range, sourced from the same
SUPPORTED_*_CONTRACT_VERSIONS constants the parsers enforce (no new source of
truth) — and reads the user-owned settings file to emit a per-surface verdict:
compatible (declared version within the supported range), incompatible
(declared version outside the range, or no valid integer version, naming the
declared and supported versions), or absent (section not declared). It reads
only each section's declared contractVersion — it never re-validates the full
contract, re-probes readiness, or executes any extension — so a fleet or CI
system can check up front instead of triggering a fail-closed error mid-run.
oh-my-cli --extension-compat
oh-my-cli --extension-compat --output json
# check a specific settings file before deploying it across mixed CLI versions
oh-my-cli --extension-compat --settings path/to/settings.json --output jsonAn unsupported version is reported as a verdict (the command still exits 0
— an audit, not a gate); only a malformed settings root (invalid JSON or a
non-object) fails closed and exits 2, matching the settings-level guarantee of
discovery. Because the surface reads only contractVersion, no entry id,
argument, or secret value is ever printed; only the home path is collapsed to
~.
Extension Compatibility
────────────────────────────────────────
Settings: ~/.oh-my-cli/settings.json
Schema: oh-my-cli.extension-compat v1
Provider contract: compatible
Schema: oh-my-cli.provider-contract
Supported: 1
Declared: 1
Reason: declared version 1 is supported
Tool contract: incompatible
Schema: oh-my-cli.tool-contract
Supported: 1
Declared: 99
Reason: declared version 99 is outside the supported range
MCP contract: absent
Schema: oh-my-cli.mcp-contract
Supported: 1
Declared: (not declared)
Reason: section not declared
Add --output json for a versioned record (schema
oh-my-cli.extension-compat) whose surfaces array always carries one entry per
contract (kind provider / tool / mcp / workflow), each with its
schema, supportedVersions, present flag, declaredVersion (an integer or
null), verdict (compatible / incompatible / absent), and a reason.
Encode a repeatable, non-interactive automation (for example a CI sequence) as a
versioned workflow in the same unified settings file
(~/.oh-my-cli/settings.json, or --settings <path>), then run it by name. A
workflow is a named, ordered list of steps and each step is a bounded prompt run
through the existing headless -p path in its own process — so steps are
isolated and there is no core-code change per consumer. --list-workflows
inventories the declared workflows (read-only, redacted); --run-workflow <name>
runs one non-interactively.
{
"workflows": {
"contractVersion": 1,
"definitions": {
"ci-readonly": {
"description": "Two read-only checks",
"steps": [
{ "prompt": "List the files in this directory" },
{ "prompt": "Summarize README.md" }
]
}
}
}
}oh-my-cli --list-workflows
oh-my-cli --run-workflow ci-readonly
oh-my-cli --run-workflow ci-readonly --output jsonSteps run sequentially in declared order. Safe failure defaults: the first
failing step halts the run with a bounded non-zero verdict and the remaining
steps do not run. The contract is untrusted input read only from the user-owned
scope — a project-local settings file can never define or run a workflow. A raw
credential field (in a workflow or a step), an unknown/misspelled key, a
malformed step, or an unsupported contractVersion fails closed and exits 2
before any side effect. A completed run exits 0; a halted run exits 1.
Output is redacted in both modes — secrets, credentials, and home/workspace
paths never appear — and each step reports pass/fail and bounded wall-clock time.
Human mode streams one line per step; --output json emits a single record
(schema oh-my-cli.workflow-contract) with the workflow name, result
(completed or failed), stepsRun/stepsTotal, and a steps array carrying
each step's redacted prompt, ok, exitCode, and elapsedMs.
Step 1/2: List the files in this directory — ok (615ms)
Step 2/2: Summarize README.md — ok (718ms)
Workflow "ci-readonly": completed (2/2 steps, 1340ms)
| Tool | Category | Description |
|---|---|---|
read |
read | Read a workspace-relative file with optional line offset/limit |
list |
read | List the immediate entries of a workspace directory (types, deterministic order) |
glob |
read | Recursively match workspace-relative paths against a glob pattern |
grep |
read | Search file contents for a regular expression, returning path:line matches |
write |
mutate-file | Create or replace a workspace-relative UTF-8 file |
edit |
mutate-file | Replace exactly one occurrence of text in a file |
shell |
mutate-shell | Execute a command via /bin/bash (30s default timeout, 120s max, 1 MiB output cap) |
File operations are confined to the workspace directory. Symlink escapes are detected and rejected. Shell commands run with their working directory confined to the canonical workspace root, so a command cannot inherit a launch directory outside the trust boundary.
The list, glob, and grep tools are read-only and therefore never require
approval, so the agent can explore the repository in any approval mode. They
stay strictly inside the workspace (every base path is confined with symlink
escape detection), never follow symbolic links, and apply repository ignore
rules (the root .gitignore plus a built-in set of generated directories such
as node_modules and dist); glob/grep accept an ignore:false option to
search ignored trees and grep accepts an include glob to narrow matches.
Every collection is bounded by depth, file count, match count, per-file size,
and a wall-clock deadline; binary, oversized, and over-long-line inputs are
skipped or truncated with explicit metadata rather than flooding context, and
results are deterministically ordered. Because no subprocess is ever spawned,
cancellation and time limits cannot leave background processes behind.
A long-running or silent shell command (a build, install, or test that emits no
output) need not look stuck: after ~5s the CLI prints a periodic
… still running (Ns elapsed) heartbeat to the terminal in interactive mode,
and reports the elapsed wall-clock time in the headless tool_result event
(--output json) so non-interactive consumers see progress too. The heartbeat
carries only elapsed seconds — never command output, secrets, or host paths —
and the existing timeout and 1 MiB output cap are unchanged.
npm run build # Compile TypeScript
npm run typecheck # Type-check without emitting
npm test # Unit tests
npm run test:integration # Integration tests (fake provider, no network)
npm run smoke # Smoke tests against built binarynpm run smoke also runs the first-run documentation check, which executes
every oh-my-cli command documented in docs/FIRST-RUN.md
and fails if the documented syntax goes stale.
Before cutting a release, follow docs/RELEASE.md: it records supported platforms, artifact verification, and rollback evidence.
src/config.ts— environment variable validation (zod)src/provider.ts— OpenAI-compatible streaming client with text + tool-call aggregation and bounded transient-error retrysrc/agent.ts— agent loop with 30-round hard cap and spend-budget gatesrc/cost.ts— bundled model price table, token→USD cost estimate, and budget parsing (--budget)src/tools.ts— tool definitions (read, list, glob, grep, write, edit, shell)src/discovery.ts— bounded, read-only, symlink-safe discovery primitives (list, glob, grep) backing the same-named toolssrc/workspace.ts— path confinement with symlink escape detectionsrc/approval.ts— approval mode logicsrc/folder-trust.ts— folder-trust boundary and effective-sandbox detection (--trust/--trust-info/--trust-workspace/--enforce-folder-trust)src/command-policy.ts— deterministic, offline shell-command classification and denial (--command-policy)src/permission-impact.ts— redacted permission-impact preview for the approval promptsrc/color.ts— ANSI color toggle (--no-color/NO_COLOR) and palette factorysrc/session.ts— JSONL session persistencesrc/compaction.ts— bounded, versioned, fail-closed session compaction (--compact/--compact-threshold)src/session-export.ts— deterministic, redacted local session export to Markdown + JSON manifest (--export-session)src/turn-checkpoint.ts— content-based, fail-closed undo/redo of one completed agent turn (--undo-turn/--redo-turn/--dry-run)src/side-question.ts— structurally-isolated side question against a bounded, read-only session snapshot, no tools/mutation/persistence (--side-question/--session,/ask)src/session-stats.ts— deterministic, no-fabrication session activity/efficiency stats engine shared by the/statsoverlay and the headless--session-statsformsrc/lsp-runtime.ts— deterministic, secret-safe language-server runtime engine: trust-gated discovery (no implicit install) plus workspace/version/instance-bound diagnostics with stale-event rejection, shared by the/lspoverlay and the headless--lsp-statusformsrc/task-runtime.ts— deterministic, secret-safe background-task engine: an eight-state lifecycle with authoritative concurrency/leases/approvals, idempotent scoped cancellation with durable receipts, and restart reconciliation against real process state (never complete from UI alone), shared by the/tasksoverlay and the headless--tasksformsrc/headless-protocol.ts— versioned NDJSON event stream (--output json)src/run-summary.ts— privacy-safe execution summary builder/formatter (--summary)src/run-scorecard.ts— deterministic, privacy-safe comparison of two summaries (--baseline/--candidate)src/run-recovery.ts— bounded run recovery from a durable checkpoint (--recover)src/evidence-archive.ts— portable, deterministic, signed evidence bundle export/verify (--export-evidence/--verify-evidence)src/repo-readiness.ts— read-only repository-readiness inspection (--readiness)src/repo-context.ts— read-only, bounded, redacted repository-context snapshot (--repo-context)src/repo-map.ts— read-only, bounded, ranked repository map of key files and top-level symbols, seeded into every fresh session (--repo-map)src/instruction-context.ts— effective, bounded, redacted repository instruction context seeded into every fresh session (--instruction-context)src/task-plan.ts— deterministic, bounded, redacted task planner grounded in the repo context (--plan)src/task-verify.ts— bounded, redacted, head-bound pass/fail verification of the repo's canonical commands (--verify-task)src/change-review.ts— bounded, redacted, head-bound review brief for the current change against a base ref (--review-change)src/ci-handoff.ts— bounded, redacted, head-bound CI handoff brief composing verify + review (--ci-handoff)src/delivery-brief.ts— bounded, redacted, head-bound completion verdict composing plan + verify + review + handoff with a CI result (--delivery-brief)src/provider-contract.ts— versioned, redacted provider extension contract: declare providers in settings, negotiate the contract version, select one, and resolve its non-secret config (--provider-contract)src/provider-invocation.ts— governed, non-interactive issuance of one bounded model request to one resolved-readyprovider through its contract: gated by readiness (credential available, endpoint valid) and approval mode, bounded by a hard timeout, a bounded generation, and an output-size cap, redacted in text and JSON with the credential value never printed, and fail-closed on every error (--invoke-provider)src/mcp-contract.ts— versioned, redacted MCP server extension contract: declare servers in settings, negotiate the contract version, select one, and resolve its lifecycle state (declared/ready/isolated) with safe failure defaults (--mcp-contract)src/mcp-invocation.ts— governed, non-interactive connection to one resolved-readyMCP server over safe local stdio: initialize handshake, tool listing, and one tool call, gated by readiness, the command trust policy, and approval mode, confined to the workspace, bounded by a hard timeout and output cap, redacted in text and JSON, and fail-closed on every error (--invoke-mcp)src/tool-contract.ts— versioned, redacted tool extension contract: declare tools in settings, negotiate the contract version, select one, and resolve its readiness state (declared/ready/isolated) with safe failure defaults (--tool-contract)src/tool-invocation.ts— governed, non-interactive invocation of one resolved-readytool extension through its contract: gated by readiness, the command trust policy, and approval mode, confined to the workspace, bounded by a hard timeout and output cap, redacted in text and JSON, and fail-closed on every error (--invoke-tool)src/extension-discovery.ts— read-only discovery view composing the provider, MCP, tool, and workflow contract resolvers into one redacted report of which extension surfaces are declared and ready, without core changes (--discover-extensions)src/extension-compat.ts— read-only compatibility view publishing the supported provider, tool, MCP, and workflow contract-version matrix (from theSUPPORTED_*_CONTRACT_VERSIONSconstants) and a proactive, redacted per-surface verdict (compatible/incompatible/absent) for the user settings file, without re-validating, probing, or executing any extension (--extension-compat)src/workflow-contract.ts— versioned, redacted workflow contract: declare reusable named workflows (ordered steps) in user settings, negotiate the contract version, select one by name, and list them (--list-workflows)src/workflow-runner.ts— run a named workflow non-interactively, each step a bounded prompt through the headless-ppath in its own process; steps run in declared order, a failing step halts the run, and output is redacted in human and machine modes (--run-workflow)src/model-profiles.ts— named model profiles in user settings: declare, list, validate, select, and disable profiles that reuse themodelsection's shape (endpoint, model name, credential env-var name), selected via--profileorsettings.defaultProfileand resolved through the same secure, redacted path as themodelsection (--list-profiles)src/trust-posture.ts— read-only posture view composing folder trust, sandbox isolation, approval mode, and extension readiness into one redacted audit of what a run will be allowed to do, without core changes (--trust-posture)src/worktree-lease.ts— collision-safe leased git worktrees per mutating agent (--create-worktree/--clean-worktree)src/index.ts— CLI entry point (commander)tests/fake-provider.ts— fake OpenAI-compatible HTTP server for tests