cli

package
v0.22.155 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 2, 2026 License: MIT Imports: 67 Imported by: 0

Documentation

Overview

Package cli — `clawtool a2a` subcommand. Phase 1 surface for ADR-024 (A2A networking): emits the agent's A2A Agent Card to stdout, lists registered peers from the daemon's local registry. mDNS announce, cross-host transport, and capability tier enforcement land in Phase 2+.

Package cli — `clawtool autodev` self-trigger loop.

`autodev` is a flag-driven Stop-hook continuation: when armed, the `clawtool autodev hook` subcommand emits `{"decision":"block", "reason":"..."}` on Claude Code's Stop event so the conversation keeps going with the supplied prompt instead of ending the turn. `/clawtool-autodev-stop` (or `clawtool autodev stop`) is the only path back to operator control.

The hook itself is wired through the marketplace plugin's `hooks/hooks.json` — every install of clawtool@clawtool-marketplace already has the Stop event bound. There is no separate install step: the operator runs `clawtool autodev start` to flip the arm-flag, and the next turn-end in any clawtool-bound Claude Code session triggers the loop continuation.

Subcommands:

clawtool autodev start    Arm the loop (creates flag, resets
                          counter).
clawtool autodev stop     Disarm (deletes flag + counter).
clawtool autodev status   armed/disarmed + counter value.
clawtool autodev hook     Hook entry-point. Reads the flag,
                          emits Stop-block JSON on stdout when
                          armed, exits 0 silently otherwise.
                          Wired by hooks/hooks.json.

Reference: https://docs.claude.com/en/docs/claude-code/hooks

Stop event — decision="block" prevents Claude from stopping,
continues the conversation with `reason` as a fresh prompt.

Package cli — `clawtool autonomous` subcommand. A single-message-driven self-paced dev loop: the operator types ONE prompt and the binary keeps dispatching it back to the chosen BIAM peer until the agent emits `DONE: <summary>` (or writes a tick.json with `done: true`), the max-iterations cap is hit, or the operator hits Ctrl-C.

Tests stub the dispatcher via the AutonomousDispatcher interface (set --cooldown=0s to skip the 5-min sleep).

Package cli — `clawtool autopilot` subcommand. Self-direction backlog: items the agent itself works on, dequeued in a loop so each finished task triggers the next without operator re-prompting.

─── Design (200 words) ────────────────────────────────────────────

Shape of a backlog item: id (auto-generated, sortable), prompt (free text the agent reads), priority (int; higher first), status (pending|in_progress|done|skipped), created_at, claimed_at, done_at, optional note.

Storage: TOML at $XDG_CONFIG_HOME/clawtool/autopilot/queue.toml (defaults to ~/.config/clawtool/autopilot/queue.toml). Per-host, not per-repo — a queue the operator builds at lunch survives until they reopen Claude Code in the evening. Atomic writes via internal/atomicfile keep the file from tearing if the daemon crashes mid-rewrite.

Six verbs:

  • clawtool autopilot add "<prompt>" [--priority N] [--note "..."]
  • clawtool autopilot next — atomic dequeue, marks in_progress
  • clawtool autopilot done <id> [--note] — marks done
  • clawtool autopilot skip <id> [--note] — drops without finishing
  • clawtool autopilot list [--status X] [--format text|json]
  • clawtool autopilot status — histogram

MCP mirror: AutopilotAdd / AutopilotNext / AutopilotDone / AutopilotSkip / AutopilotList / AutopilotStatus, defined in internal/tools/core/autopilot_tool.go. Same TOML store; CLI and MCP are interchangeable surfaces.

Agent loop pattern: agent calls AutopilotNext → does the work → calls AutopilotDone → calls AutopilotNext again. When Next returns empty (no pending items), the agent ends the loop. This is the "devam edebilme yeteneği" — non-stalling continuation — the operator described.

Package cli — `clawtool bootstrap` subcommand. The zero-click onboarding verb. After a fresh install the operator should do nothing: clawtool spawns the chosen BIAM peer's CLI with the elevation flag, pipes the bootstrap prompt to it, and streams the agent's reply back to stdout. The agent is expected to run OnboardWizard + InitApply via MCP and print a 3-line summary.

Test seam: production wires lookPath / spawnAgent to os/exec; tests stub them so the suite never spawns a real CLI.

Package cli implements clawtool's user-facing subcommands.

Subcommand layout (ADR-004 §4):

clawtool init              interactive wizard: pick recipes, apply to repo
clawtool serve             run as MCP server (delegates to internal/server)
clawtool version           print version
clawtool help              print top-level usage
clawtool tools list        list known tools and resolved enabled state
clawtool tools enable <s>  set tools.<selector>.enabled = true
clawtool tools disable <s> set tools.<selector>.enabled = false
clawtool tools status <s>  print resolved state and the rule that won

Source / profile / group subcommands are scaffolded in main.go usage but not wired in v0.2 — they land alongside the source-instance feature.

Package cli — short markdown completion message for init runs.

ChatRender produces a 3-line markdown block aimed at a chat session: a parent LLM context driving clawtool over MCP wants to surface a digest of what just happened without re-rendering the full TTY output. The format is deliberately rigid so downstream consumers can pattern-match it:

✓ N recipes applied: a, b, c
○ M already present (idempotent skip)
→ Suggested next: <first NextStep>; <second NextStep>

Lines are emitted unconditionally (zeros render as "0"). When NextSteps is empty the third line is omitted. Lives in its own file so the chat-onboard branch can import this single helper without pulling the whole init wizard's dependency closure.

`clawtool daemon` — manage the persistent shared MCP server every host (Codex / OpenCode / Gemini / Claude Code) fans into. The adapter (internal/agents/mcp_host.go) calls daemon.Ensure under the hood when the operator runs `clawtool agents claim <host>`, but the CLI exposes the lifecycle directly so the operator can start / stop / inspect the daemon without going through claim.

Package cli — `clawtool doctor` is the one-command diagnostic. It surveys binary / agents / sources / recipes and prints a colour-coded checklist with suggested fix commands. Pure composition of existing internal helpers — no new deps.

`clawtool egress` — runs the egress allowlist proxy (ADR-029 phase 4, task #209). Sandbox workers route their HTTP_PROXY / HTTPS_PROXY through this binary so model-generated network calls pass through an explicit allowlist before reaching the host network.

Operator path:

clawtool egress --listen :3128 \
    --allow api.openai.com,api.anthropic.com,.github.com

In the worker container:

docker run -e HTTP_PROXY=http://egress:3128 \
           -e HTTPS_PROXY=http://egress:3128 \
           clawtool-worker:0.21 ...

Package cli — `clawtool fanout` subcommand. Parallel-subgoal orchestrator: spawn N subgoals, each in its own git worktree, dispatch each to a BIAM peer in parallel (mini-autonomous loop), then sequentially fast-forward-merge each completed subgoal back into the main branch with a cooldown between merges.

Reuses autonomous.go's AutonomousDispatcher + Tick types — every subgoal IS an autonomous run with its own per-sub max-iterations cap. Tests stub the dispatcher via SetAutonomousDispatcher and the git ops via SetFanoutGitExec.

Why this exists: today this orchestration is done by Claude Code's built-in Agent tool, which is host-coupled. clawtool needs its own primitive so any agent host (or bare terminal) can drive parallel sub-processes.

Package cli — `clawtool ideate` subcommand. Top of the three-layer self-direction stack:

Ideator   — what to work on (this verb)
Autopilot — when to work on it (`clawtool autopilot accept`)
Autonomous — how to work on it (`clawtool autonomous "<goal>"`)

`ideate` surveys cheap repo-local signals (open ADR questions, TODOs, recent CI failures, manifest drift, BM25 baseline drift) and prints ranked Idea candidates. With --apply, every selected Idea is pushed onto the autopilot backlog at status=proposed. Operator approval (`clawtool autopilot accept <id>`) flips proposed → pending; only then does AutopilotNext claim it. Without that gate the agent could silently drive its own autonomous pipeline past human review.

This file owns the CLI parsing + edge wiring of default sources; the orchestrator + IdeaSource interface live in internal/ideator/, the concrete sources in internal/ideator/sources/. Splitting defaults into the edge avoids an import cycle between ideator and its sources.

Package cli — wizard-side install-prompt UX. Implements setup.Prompter (the abstraction the runner uses for missing prereqs) on top of huh + os/exec, plus the CommandRunner that actually shells out the per-platform install commands.

Kept in a separate file from init_wizard.go so the prompter + runner pair can be swapped or wrapped (e.g. by a dry-run wrapper) without touching the rest of the wizard. Test files inject a recordingRunner and a scriptedPrompter against the same interfaces — the wizard surface stays huh-bound, but every install decision is exercised in unit tests.

Package cli — structured summary of an `init` run.

InitSummary is the machine-parseable counterpart to the human- readable lines `clawtool init` prints. The chat-driven onboard flow (a parallel branch) consumes either the `--summary-json` stdout payload or the in-process struct directly to decide what to surface to the operator and what to do next.

The shape is intentionally narrow and append-only: chat-onboard can take a hard dependency on these field names. Adding new fields is fine; renaming or removing them is a breaking change.

Package cli — `clawtool init` wizard. Two-scope welcome screen: repo (recipes) and user-global (agents + sources + secrets), with "both" and a read-only "show me what's available" preview path.

Honors --yes for non-interactive runs and falls back to the same path when stdin/stdout aren't TTYs (CI / containers).

Package cli — `clawtool install` is the zero-touch first-run verb.

Operator runs `clawtool install` ONCE after they put the binary on PATH; this file orchestrates everything else: daemon up, host detection, bridge install, agent-claim, MCP config write, hooks install, peer registration of the current shell, init --all on the repo, and a final daemon-health verify. Single status line per step on stderr (✓ / ⚠ / ✗ / ⤳); the only thing that lands on stdout is the closing one-liner:

clawtool kuruldu — N agent(s) registered, M recipe(s) applied, daemon @ 127.0.0.1:<port>

The verb is IDEMPOTENT: every step short-circuits when its target state is already present (daemon healthy, bridge already added, agent already claimed, hooks already installed, peer already registered for this session). Failures of steps 2–10 are logged non-fatally and continue; only step 1 (daemon) aborts.

Test seam: package-level vars (installLookPath, installDispatcher, installDaemonStarter, installPeerRegister, installInitAll) are stubbed by install_first_run_test.go so the suite exercises the dispatch flow without spawning real binaries / daemons / agents.

Package cli — `clawtool mcp` subcommand surface (ADR-019).

v0.17 fills in `new`, `list`, `run`, `build`, `install`. The `new` verb runs the huh.Form wizard implemented in mcp_wizard.go; `install` lives in mcp_install.go; this file keeps the dispatcher + the read-only `list` walker.

Package cli — `clawtool mcp install` (ADR-019).

Reads `.clawtool/mcp.toml` from the project at <path>, derives the launch command from the project's language + transport, writes a [sources.<name>] block into ~/.config/clawtool/config.toml. Same surface as `clawtool source add` for catalog entries — just auto-discovers the command instead of asking.

Package cli — `clawtool mcp new` interactive wizard (ADR-019).

huh.Form sequence collects the operator's spec, hands it to internal/mcpgen which renders + writes the project. Tests substitute mcpgenDeps to drive the wizard without hitting disk.

Package cli — `clawtool` (no args) launches a friendly TUI landing menu that points at the most-used flows. Designed for users who'd rather not memorise subcommands; pure huh.Select.

internal/cli/onboard_resume.go — wizard progress persistence so `clawtool onboard` can survive mid-flow interruption (Ctrl-C, terminal close, accidental crash) and pick up where it left off instead of starting from step 1 each time.

Wire:

  • State file: $XDG_CONFIG_HOME/clawtool/.onboard-progress.json (mode 0600 — same conventions as the rest of the config tree).
  • Saved after every wizard step completion (step index + the onboardState snapshot at that point).
  • Cleared after a successful finish so the next `clawtool onboard` either starts fresh (if .onboarded marker absent) or hits the "already onboarded → redo?" guard.

Re-entry behaviour:

  • .onboarded marker present, no progress file → "Already onboarded. Re-run the wizard?"
  • Progress file present → "Resume from step X?" (No = wipe progress + start fresh).
  • Neither → fresh wizard, no extra prompt.

internal/cli/onboard_tui.go — Bubble Tea wizard for `clawtool onboard`. Replaces the prior linear huh.NewForm(groups...) flow with a step-by-step wizard: each question gets its own focused viewport with a "Step X of Y" indicator, the rounded-box header stays pinned at the top, and the side-effect run phase renders as live progress inside the same alt-screen program.

Why:

  • Operator wanted bounded TUI ("vim/htop feel") instead of the scroll-pollution we'd get from emitting a clear sequence and dumping output below the prompt. tea.WithAltScreen() owns a dedicated screen buffer; on exit the operator's terminal state is restored exactly as it was.
  • Stepwise progression makes the wizard feel structured. The prior huh.NewForm rendered all groups in one continuous form; the operator couldn't tell where they were in the sequence.

Non-TTY / `--yes` invocations still run through the linear onboard() path so CI scripts, Dockerfiles, and the test harness keep their stable plain-text contract.

internal/cli/onboard_ux.go — visual rendering for `clawtool onboard`. Onboard is the first ten seconds the operator spends with clawtool; the wizard either hooks them or churns them. This file polishes that surface:

  • Clear screen on entry so the operator sees a clean canvas, not the pile of `npm install` / `git status` noise that was in their terminal when they typed `clawtool onboard`.
  • Boxed header with the live host-detection result rendered as a single tight row of ✓ / ✗ pills.
  • Phase-style side-effect output (Section / PhaseStart / PhaseDone) instead of raw `stdoutLn` lines, so a multi- bridge install reads as a labelled progress block.
  • Tight final summary: a ✓-checklist of what was wired, not the full `clawtool overview` dump.

Mirrors upgrade_ux.go's design constraints: TTY-aware (plain ASCII when piped), no spinners (Ctrl-C-friendly), one-shot output.

internal/cli/onboard_widgets.go — minimal custom wizard widgets (Select / MultiSelect / Confirm) that replace charmbracelet/huh inside the onboard alt-screen TUI.

Why custom: huh.Form embedding inside our parent tea.Program had two intractable bugs we kept rediscovering:

  1. huh's Select widget renders only the cursor row when its internal viewport height is unset. WindowSizeMsg.Height does NOT propagate to per-field viewports — only Form.WithHeight() and Select.Height() do, and we don't want clamping anyway.
  2. Wrapping huh.View() in a height-clamped lipgloss style fights huh's own internal styles.Base.Height() — the inner clamp wins at minHeight=1, killing the option list.

These widgets render every option every frame, no viewport, no height drama. They expose:

  • Update(msg) — route a tea.Msg, returns updated widget + cmd
  • View() — render full natural-size output
  • Done() — true once the operator submitted
  • Keybinds() — short hint string for the wizard's footer (e.g. "↑/↓ select · enter confirm")

The wizard's outer model owns navigation between widgets; the widgets only handle their own keys.

Package cli — `clawtool orchestrator` (aliases: dashboard, tui, orch). One Bubble Tea program — the orchestrator — fronted by four interchangeable verbs because operators reach for whichever name they remember. All four routes call this single handler.

Two modes:

default                interactive Bubble Tea TUI in alt-screen
--plain / --once       stdout snapshot for chat-visible pairing
                       with the Monitor tool (no TUI)

Pre-v0.22.36 we shipped two distinct programs (dashboard.go + orchestrator.go) that both called tui.RunOrchestrator and got maintained independently. They drifted, the docstrings disagreed on which "is the real one", and operators had to memorise the alias-to-program mapping. The single-handler shape replaces all of that.

`clawtool overview` — one-screen status of the running system (UX gap from the #193 smoke pass). Operators wanted a single verb that reports daemon + sandbox-worker + agents + bridges without remembering five subcommand names.

This deliberately skips diagnostic depth (`clawtool doctor` remains the deep checklist). Overview is the at-a-glance "is everything wired?" answer.

Package cli — `clawtool peer` subcommand. Phase 1 surface for ADR-024 peer discovery: the runtime-side primitive every hook (claude-code, codex, gemini, opencode) calls to register the running session into the daemon's peer registry.

Three verbs:

clawtool peer register --backend X [--display-name Y] [--session ID]
clawtool peer heartbeat [--session ID] [--status busy|online]
clawtool peer deregister [--session ID]

State: each register writes the assigned peer_id to a session- keyed file under ~/.config/clawtool/peers.d/<session>.id, so the downstream heartbeat / deregister calls find the right peer without the hook having to thread the id explicitly. Session IDs come from the runtime's hook payload (claude-code's transcript_path already has one); when --session is omitted, falls back to "default" — single-session-per-host hosts work out of the box.

Package cli — `clawtool portal` subcommand surface (ADR-018).

Read-only + persistence operations land in v0.16.1. The interactive `ask` flow that drives Obscura over CDP arrives in v0.16.2; today it returns a clear "deferred" error so the surface is discoverable before the engine ships.

Package cli — `clawtool portal record <name>` (ADR-018, v1.1).

Drives portal.Record (CDP-backed via the existing chromedp wrapper) and persists the captured Recording to a per-portal TOML file under ~/.config/clawtool/portals/<name>.toml. The per-portal-file shape is new in v1.1 — the legacy wizard writes stanzas into config.toml and that path keeps working; record uses its own directory so a recorded session can be inspected, edited, or deleted without rewriting the main config.

Scope (per ADR-018 §Resolved 2026-05-02): the heuristic recorder + TOML persistence ship in v1.1; the streamed Network.responseReceived + DOM.documentUpdated listener stack for fingerprinted response-done predicates is deferred. Operator edits the captured TOML by hand to refine the predicate, same way the wizard's "custom" choice flows.

Package cli — `clawtool portal add` interactive wizard (ADR-018, v0.16.3).

Rebuilt on top of the chromedp-backed BrowserSession (ADR-007). Spawns the user's installed Chrome with --headless=false + a temp profile, waits for them to log in (optionally with a copy/paste prompt for the Claude in Chrome side-panel), pulls cookies via Network.getAllCookies, collects the three CSS selectors + a "response done" predicate template, and writes config.toml + secrets.toml.

Per ADR-017 we never wrap claude-in-chrome — the wizard generates a plain-text prompt the operator can paste. clawtool stays MCP-server-free for the wizard transport.

Package cli — `clawtool rules` subcommand. Lifecycle management for the operator's predicate-based invariants in .clawtool/rules.toml (project-local) or ~/.config/clawtool/rules.toml (user-global).

Operator-facing surface:

clawtool rules list                     show every loaded rule + its source
clawtool rules show <name>              detail view of one rule
clawtool rules new <name> [flags]       add a new rule (asks scope when ambiguous)
clawtool rules remove <name>            delete a rule
clawtool rules path [--user|--local]    print the rules file path
clawtool rules check <event> [flags]    one-shot evaluation against current state

Why this lives in CLI: the operator wants to add a rule from a fresh-context shell without firing up an editor; the parallel MCP-side tool (RulesAdd) is a thin wrapper that calls the same rules.AppendRule helper this CLI does.

Package cli — `clawtool sandbox` subcommand surface (ADR-020).

v0.18 ships read-only verbs (list / show / doctor) plus the surface stub for `run`. The dispatch-time integration (`clawtool send --sandbox <profile>`) lands v0.18.1+ alongside the per-OS engine implementations.

`clawtool sandbox-worker` — runs the sandbox worker (ADR-029 phase 1). Mirrors `clawtool serve --listen` semantics but for the worker leg of the orchestrator+worker pair: bearer-auth'd WebSocket endpoint that the daemon dials to route Bash / Read / Edit / Write tool calls into an isolated container.

Operator runs this inside a docker / runsc container; the daemon is the only trusted dialer. Auth is a shared bearer token; the worker reads it from a file or stdin so it never lands in argv.

Package cli — `clawtool setup` is the unified first-run entry. Phase 2 of ADR-027: one huh form with a per-feature opt-in matrix instead of the onboard → init verb chain. --legacy falls back to the Phase 1 sequential dispatch for operators who hit a bug or prefer the old prompts.

Package cli — Phase 2 setup state machine. Collapses onboard + init into one huh form with a single per-feature opt-in matrix. Per ADR-027: probe → matrix → required options → apply → verify.

Phase 2 v1 ships the matrix for: bridge installs, MCP host claims, daemon up, BIAM identity, secrets store init, telemetry, AND the subset of recipes that are Stable + don't require any caller-supplied options. Recipes with required options (license, codeowners, …) still flow through `clawtool init`'s per-recipe prompts since the matrix can't ask for option values inline.

`clawtool setup --legacy` falls back to the Phase 1 chain (onboard → init) for operators who prefer the old verb shape.

Package cli — helper wiring for setup_wizard.go. Lives alongside onboard.go so the production callbacks share one import set (daemon, agents, biam, secrets) without bloating setup_wizard.go.

Package cli — `clawtool skill` subcommand. Scaffolds and lists Agent Skills per the agentskills.io spec: a folder containing SKILL.md (required, with frontmatter name + description) plus optional scripts/, references/, assets/ subdirectories.

The standard was authored by Anthropic and is now open. clawtool's skill subcommand is the bootstrap layer — `clawtool skill new` is the analogue of `npm init` for Agent Skills.

Package cli — `clawtool spawn` subcommand. Opens a NEW terminal window/pane running the requested agent CLI (claude-code / codex / gemini / opencode), then immediately registers the freshly-spawned agent as a peer in the daemon's BIAM registry so the caller can dispatch to it via SendMessage / PeerSend by peer_id.

Why a verb (and not just `bootstrap`): bootstrap spawns the agent in the SAME terminal and pipes a single prompt; spawn opens a fresh terminal pane the operator (or another agent) keeps interacting with. Pairs with the orchestrator pattern where one running Claude Code session asks clawtool to fan out new sessions of codex / gemini etc, then talks to them via the peer surface.

Terminal detection cascade is deliberately conservative — picks the FIRST viable launcher rather than asking. Operators with strong preferences pass --terminal explicitly. The cascade order reflects "what's most likely already in use":

  1. tmux — if $TMUX is set we're in a tmux session; new-window is the cheapest, most predictable option
  2. screen — if $STY is set, same logic for GNU screen
  3. wt.exe — Windows Terminal under WSL (very common dev surface)
  4. gnome-terminal — Linux GNOME default
  5. konsole — Linux KDE default
  6. kitty — popular cross-platform GPU terminal
  7. macOS Terminal.app via `open -a Terminal -n`
  8. headless `nohup …&` fallback — last resort, no visible window

Tests stub `defaultLauncher` and `registerPeerHTTP` at package level so the suite never spawns a real terminal or daemon.

Package cli — `clawtool task watch` (ADR-026, Gemini design pass b8ab4c9a). Streams BIAM task state transitions as one stdout line per event so the operator can pair it with Claude Code's native Monitor tool and see dispatch progress as inline chat events.

Two modes:

clawtool task watch <task_id>   single task, exits when terminal
clawtool task watch --all       every active task, runs forever
                                 (or until SIGINT / pipe close)

Output format defaults to human-readable; --json switches to NDJSON for downstream tooling.

Polling cadence is 250ms by default — sub-second feel with negligible disk pressure on SQLite WAL. Tunable via --poll-interval.

Per the ADR's security clause, watch lines NEVER carry the task's body / completion text — only metadata (status, agent, message_count, last_message preview capped at 80 chars). A gigabyte-sized completion blob landing in the operator's chat would be its own outage.

Package cli — `clawtool unattended` subcommand. Operator-facing trust management for ADR-023's --unattended dispatch mode.

Two surfaces:

clawtool unattended status [<repo>]    show whether <repo> (or cwd) is trusted
clawtool unattended grant  [<repo>]    explicitly trust <repo> for unattended dispatch
clawtool unattended revoke [<repo>]    remove the trust grant
clawtool unattended list               list every granted repo
clawtool unattended path                print the trust file location

`clawtool yolo` is a deliberately-jokey alias so operators searching docs / muscle-memory the Cline term find it.

Package cli — `clawtool uninstall` removes every artifact clawtool drops on the host. Designed for the tester / dogfooder who installs the binary fresh ten times a day and ends up with duplicate sources / portals / sticky defaults.

The cleanup is intentionally exhaustive — config + secrets + caches + data dirs + sticky pointers + worktrees + BIAM SQLite + telemetry id. The binary itself is opt-in (--purge-binary) because the user may have installed via Homebrew / curl / Go and the right removal command differs by source.

Per ADR-007 doesn't apply here: this is "rm -rf clawtool's own files", which is by definition not delegable to an upstream. We still rely on stdlib os.RemoveAll for the actual removal.

Package cli — `clawtool upgrade` PATH-shadow sync.

When the operator has multiple clawtool binaries on $PATH (the canonical case is `~/go/bin/clawtool` from `go install` AND `~/.local/bin/clawtool` from `install.sh`), `os.Executable()` only resolves to the one inode we were spawned from. The unupgraded twin keeps shadowing $PATH for any consumer that doesn't ask the caller's choice — most importantly Claude Code's MCP plugin, which spawns clawtool via `mcp add` config that hard-coded a specific path at install time. The result is a silent version skew: the operator runs `clawtool upgrade`, sees the new version on the terminal, but every MCP call still routes through the stale binary.

syncPathShadowsTo walks $PATH, finds every clawtool that resolves to a different inode than the just-upgraded one, and copies the new binary's bytes over each. Best-effort per file: a permission error on one shadow doesn't abort the others — the operator hears about every one that succeeded and every one that failed, and the upgrade as a whole still returns 0 because the canonical (`os.Executable()`) path was already updated.

internal/cli/upgrade_ux.go — visual rendering for `clawtool upgrade`. The upgrade flow is one of the rare CLI moments where the user is actively waiting on us; that's where polish earns disproportionate trust. This file encapsulates the rendering so upgrade.go's orchestration stays linear and readable.

Design constraints:

  • TTY-aware: colours + box-drawing only when stdout is a real terminal. Pipe-redirect (e.g. `clawtool upgrade | tee`) gets plain ASCII so log files stay greppable.
  • No spinner / animation: the upgrade is short (1–5s on a local network), and an animated spinner stuck to the terminal control codes turns into garbage when redirected. Static phase markers ("→ doing X" → "✓ X (350ms)") read fine in both modes.
  • One-shot output: each phase prints its line as it completes, so a Ctrl-C mid-flow leaves a partial but legible transcript instead of a half-redrawn screen.

Package cli — `clawtool watchers list / tail` verbs.

Tag-after-ship watchers historically ran as bare bash scripts under `/tmp/clawtool-tag-watcher*.{sh,log}`. They worked, but were invisible to every clawtool UI surface — operator could not see what the loop was waiting for from Claude Code, dashboard, or `clawtool overview`. The autodev Stop-hook prompt also couldn't tell the model "v0.22.X is on its way" because the state lived in raw log files no one was reading.

`clawtool watchers list` parses every `/tmp/clawtool-tag-watcher*.log`, extracts the latest pollN status + target tag + ship time, and renders a table. `clawtool watchers tail [N]` returns the last N log lines for a single watcher (default: latest). The same data feeds the autodev hook prompt so the model reads it on every self-trigger.

Index

Constants

View Source
const AutodevSelfTriggerCap = 200

AutodevSelfTriggerCap is the per-arming budget. Set high enough that a real productive loop doesn't trip it (~200 turns ≈ a full work session) but low enough that a buggy loop runs out of fuel before the operator's bill does.

View Source
const EnvUnattended = "CLAWTOOL_UNATTENDED"

EnvUnattended is the canonical env var that mirrors `--unattended`. Set to "1" by `clawtool send` whenever the dispatch is running in unattended mode (flag OR env), so any nested `clawtool send` that the upstream peer agent runs (codex spawning gemini, claude orchestrating opencode, etc.) inherits the trust + audit context without re-acquiring per-repo consent.

Compounding-trust clamp (ADR-023): the upstream's own bridge MUST NOT use this env to re-elevate to root or skip user-attached confirmations. The clamp lives at the cross-operator A2A boundary and is enforced by other code paths — this propagation is intra-operator only (same UID, same trust grant). See ADR-023 §"Q2 resolution" for the boundary rule.

View Source
const ExitIsolatedPortalConflict = 4

ExitIsolatedPortalConflict is the exit code emitted when `clawtool send --isolated` detects a portal call in the resolved tool plan. Post-ADR-018 the obscura serve pool lives on the daemon side of the isolation boundary, so an isolated subprocess cannot see portal sessions and would silently fail. Fail-closed here with a distinct code so CI / wrappers can pattern-match the gate instead of conflating it with a generic dispatch error.

Variables

View Source
var ErrApmManifestMissing = errors.New("apm: manifest file not found")

ErrApmManifestMissing is returned when the apm.yml the operator pointed at doesn't exist on disk. Tests pin this typed error.

View Source
var ErrApmYAMLParse = errors.New("apm: yaml parse error")

ErrApmYAMLParse wraps a yaml.v3 decode failure so tests can assert errors.Is without depending on the upstream's error type.

View Source
var ErrSourceNotFound = errors.New("source not found")

ErrSourceNotFound is returned by `source inspect` when the operator names an instance that isn't in the local config. Stable error so scripts can branch on it via errors.Is.

Functions

func IsOnboarded added in v0.22.13

func IsOnboarded() bool

IsOnboarded reports whether the operator has completed the onboard wizard at least once. Exported so the SessionStart hook (claude_bootstrap.go) and the no-args first-run check can both consume the same signal.

func KillTmuxPane added in v0.22.107

func KillTmuxPane(paneID string) error

KillTmuxPane is the package's exported entry point for the peer-lifecycle auto-close path. The supervisor's BIAM terminal- status hook lives in internal/agents and can't import internal/cli (the dependency would invert the daemon → CLI relationship), so the daemon wires this function into the agents-side hook seam at boot. Honors CLAWTOOL_TMUX_SOCKET via the same tmuxArgv path so containerised tmux servers stay reachable.

func KillTmuxPaneAndMaybeWindow added in v0.22.109

func KillTmuxPaneAndMaybeWindow(paneID, windowID string) error

KillTmuxPaneAndMaybeWindow closes the named pane, then probes the window — if no panes remain (or the window was already auto- collapsed by tmux) it ALSO closes the window so an auto-spawned pane doesn't leave an empty tmux window behind. Empty windowID (peer registered before the spawner started recording window_id) short-circuits to legacy pane-only close so the existing-peer path is unaffected.

Returns the kill-pane error (if any). The kill-window step is best-effort: a stale window id, a tmux server that already reaped the window, or a permission glitch all surface as non-fatal — we still return success on the kill-pane step, which is the caller's load-bearing concern.

func SetFanoutGitExec added in v0.22.85

func SetFanoutGitExec(g fanoutGitExec) fanoutGitExec

SetFanoutGitExec installs a stub and returns the prior one.

Types

type App

type App struct {
	Stdout io.Writer
	Stderr io.Writer
	Stdin  io.Reader
	// ConfigPath overrides the default config location. Empty = config.DefaultPath().
	ConfigPath string
	// contains filtered or unexported fields
}

App holds CLI dependencies. Stdout/stderr are injected so tests can capture.

func New

func New() *App

New returns an App writing to the process's stdout/stderr and using the default config path.

func (*App) AgentUnset added in v0.20.0

func (a *App) AgentUnset() error

AgentUnset clears the sticky default file. Idempotent.

func (*App) AgentUse added in v0.20.0

func (a *App) AgentUse(instance string) error

AgentUse persists the sticky default. We validate the instance exists in the supervisor's registry up front so the user gets a clean error here rather than at the next `clawtool send`.

func (*App) AgentWhich added in v0.20.0

func (a *App) AgentWhich() error

AgentWhich resolves the empty selector and prints the result. Same precedence chain Send uses, exposed read-only for the user to inspect what would happen.

func (*App) BridgeAdd added in v0.20.0

func (a *App) BridgeAdd(family string) error

BridgeAdd resolves the family to its recipe and applies it. Idempotent; if the bridge is already installed Detect returns Applied and Apply short-circuits.

func (*App) BridgeList added in v0.20.0

func (a *App) BridgeList(format listfmt.Format) error

BridgeList prints all known bridge recipes with their Detect state. Output format follows the operator's --format flag: table (default, human-readable), tsv (pipe-friendly), json (programmatic).

func (*App) BridgeRemove added in v0.20.0

func (a *App) BridgeRemove(family string) error

BridgeRemove is a placeholder. Claude Code's `claude plugin remove` surface isn't standardized yet across plugin types; v0.10.x will add proper uninstall semantics. For now we print a manual hint.

func (*App) HooksInstall added in v0.22.36

func (a *App) HooksInstall(runtime string) error

HooksInstall prints the runtime-specific snippet that wires <runtime> into clawtool's peer registry. We deliberately *print* rather than mutate config files: each runtime's config layout changes between versions, and an operator can paste the snippet into whichever location their version expects. claude-code's bundled hooks/hooks.json already covers it via the plugin, so we short-circuit there.

func (*App) HooksList added in v0.20.0

func (a *App) HooksList(format listfmt.Format) error

HooksList prints every configured event with its entry count. Empty config → friendly hint.

func (*App) HooksShow added in v0.20.0

func (a *App) HooksShow(event string) error

HooksShow dumps the per-entry config for a single event.

func (*App) HooksTest added in v0.20.0

func (a *App) HooksTest(event string, payload map[string]any) error

HooksTest synthesises the event with the given payload and runs every configured entry. Prints per-entry success/failure so the operator can iterate on hook scripts without firing the real lifecycle event (which might be hard to reproduce).

func (*App) Init

func (a *App) Init() error

Init writes a default config to disk if the file does not already exist. Returns an "already exists" error if it does — callers can ignore that.

func (*App) McpList added in v0.20.0

func (a *App) McpList(argv []string) error

McpList walks `root` (default cwd) for `.clawtool/mcp.toml` markers and prints one line per project. Skips node_modules / vendor / .git so a recursive walk doesn't melt on a typical repo.

func (*App) Path

func (a *App) Path() string

Path returns the resolved config path (override > default).

func (*App) PortalAdd added in v0.20.0

func (a *App) PortalAdd(name string) error

PortalAdd opens $EDITOR with a TOML template for the named portal. On save we validate the parsed stanza and append it to config.toml. The validation refuses anything that wouldn't drive an Ask flow successfully, so a fat-finger landing in config never reaches the dispatch path.

func (*App) PortalAsk added in v0.20.0

func (a *App) PortalAsk(argv []string) error

PortalAsk is the deferred-feature placeholder. Validates the resolved portal so the operator gets the same diagnostics they will get in v0.16.2, then surfaces the deferred error.

func (*App) PortalList added in v0.20.0

func (a *App) PortalList(format listfmt.Format) error

PortalList prints the configured portals one per line — same shape as `clawtool send --list` so the operator sees both surfaces consistently. Compile-time drivers (bifrost and other gateway integrations registered via portal.RegisterDriver) are merged into the same table with a STATUS column so operators discover deferred / ready integrations alongside their hand-saved web-UI portals.

func (*App) PortalRemove added in v0.20.0

func (a *App) PortalRemove(name string, dryRun bool) error

PortalRemove rewrites config.toml without the [portals.<name>] stanza. Cookies in secrets.toml are left in place so a temporary remove-then-re-add doesn't lose the export. Operators clean secrets manually when they want a true uninstall.

When dryRun is true, the existence check still runs (so typos surface at preview time) but the file is not rewritten — the caller gets a `(dry-run) would remove …` banner instead of the success line. Symmetric with `source remove --dry-run` (b364ec6) and the rest of the dry-run uniformity series.

func (*App) PortalUnset added in v0.20.0

func (a *App) PortalUnset() error

PortalUnset removes the sticky-default file. Idempotent.

func (*App) PortalUse added in v0.20.0

func (a *App) PortalUse(name string) error

PortalUse persists the sticky default for `clawtool portal ask`.

func (*App) PortalWhich added in v0.20.0

func (a *App) PortalWhich() error

PortalWhich resolves the sticky-default portal. Same precedence chain as the agent sticky default (env > sticky file > single- configured fallback).

func (*App) RecipeApply added in v0.9.0

func (a *App) RecipeApply(name string, kvs []string) error

RecipeApply runs the recipe against the current working directory. Options are key=value strings; comma-separated values become []string. v0.9 keeps options simple — wizard / MCP path will pass richer types as the surface evolves.

func (*App) RecipeList added in v0.9.0

func (a *App) RecipeList(category string) error

RecipeList prints every registered recipe grouped by category with its Detect state in the cwd.

func (*App) RecipeStatus added in v0.9.0

func (a *App) RecipeStatus(name string) error

RecipeStatus prints the Detect output for one recipe or all recipes.

func (*App) Run

func (a *App) Run(argv []string) int

Run dispatches argv (excluding program name) to the right subcommand. Returns the exit code; 0 = success, 2 = usage error, 1 = runtime failure.

Every dispatch is timed and emitted as a `cli.command` telemetry event (when telemetry is opted in) — command, subcommand, exit_code, duration_ms, error_class. Long-running verbs (`serve`, `dashboard`, `daemon` foreground) emit on dispatcher exit so a 2-hour `serve` session lands as one event with the full uptime.

func (*App) SandboxDoctor added in v0.20.0

func (a *App) SandboxDoctor(asJSON bool) error

SandboxDoctor reports every registered engine's availability. JSON path emits a stable {engines, selected} shape — automation pipelines that gate on `selected == "noop"` to flag missing enforcement can branch on it without parsing the human table.

func (*App) SandboxList added in v0.20.0

func (a *App) SandboxList(format listfmt.Format) error

SandboxList prints every configured profile + the engine that would run it on this host.

func (*App) SandboxShow added in v0.20.0

func (a *App) SandboxShow(name string) error

SandboxShow parses one profile + prints the resolved view.

func (*App) SecretsPath

func (a *App) SecretsPath() string

SecretsPath returns the secrets-store path. Tests can shadow App.SecretsPath to point at a tmp file; production uses secrets.DefaultPath().

func (*App) Send added in v0.20.0

func (a *App) Send(args sendArgs) error

Send routes through Supervisor.Send and streams stdout.

func (*App) SendList added in v0.20.0

func (a *App) SendList() error

SendList prints the supervisor's agent registry — same shape as the MCP `AgentList` response and the HTTP `GET /v1/agents` body.

func (*App) SetSecretsPath

func (a *App) SetSecretsPath(p string)

SetSecretsPath lets tests redirect the secrets store to a tmp file.

func (*App) TaskCancel added in v0.21.5

func (a *App) TaskCancel(taskID string) error

TaskCancel flips a pending/active task to "cancelled". The CLI invocation is a separate process from the runner that owns the dispatch goroutine, so we do a store-only flip + Notifier publish here — the runner side handles in-process cancel via Runner.Cancel when the same caller already holds it. Cross-process pollers (`clawtool task watch`) wake on the Notifier broadcast.

Audit fix #204: pairs with Runner.Cancel — without this the CLI had no way to abort a runaway --async dispatch short of kill -9 on the binary.

func (*App) TaskGet added in v0.20.0

func (a *App) TaskGet(taskID string) error

TaskGet prints the task row + every message envelope for the task, JSON-formatted so a script can parse it.

func (*App) TaskList added in v0.20.0

func (a *App) TaskList(limit int, filter taskFilter, statusOverride string) error

TaskList prints the recent BIAM task summary, filtered by `filter`. When filter == taskFilterStatus, `statusOverride` names the single status to keep (done | failed | cancelled | expired). To honour the operator-supplied --limit while still filtering meaningfully, we pull a wider window from the store (10× limit, capped at 1000) and slice client-side.

func (*App) TaskWait added in v0.20.0

func (a *App) TaskWait(taskID string, timeout time.Duration) error

TaskWait blocks until the task is terminal, then dumps the same shape TaskGet does.

func (*App) ToolsDisable

func (a *App) ToolsDisable(selector string) error

ToolsDisable writes tools.<selector>.enabled = false.

func (*App) ToolsEnable

func (a *App) ToolsEnable(selector string) error

ToolsEnable writes tools.<selector>.enabled = true.

func (*App) ToolsExportTypeScript added in v0.22.31

func (a *App) ToolsExportTypeScript(outDir string) error

ToolsExportTypeScript emits the manifest as a TypeScript module tree under outDir. One .ts per tool plus an index.ts barrel. The underlying generator (registry.Manifest.ExportTypeScript) is the single source of truth — this method just wires the manifest + stdout chatter.

func (*App) ToolsList

func (a *App) ToolsList() error

ToolsList prints every shipped tool — both the file/exec/web primitives in config.KnownCoreTools and the dispatch/agent/task/ recipe/bridge surface registered via core.BuildManifest().

Pre-v0.22.20 this only listed config.KnownCoreTools (9 entries), which created a confusing UX gap: SendMessage / AgentList / TaskGet / etc. WERE registered with the MCP server at daemon boot (host CLIs see them as `mcp__clawtool__SendMessage`) but `clawtool tools list` never showed them — operators couldn't confirm what surface their hosts actually had access to. Now the union of both sources is rendered, deduped on Name, sorted alphabetically. Resolution still flows through cfg.IsEnabled so per-selector overrides work for every tool — even ones that don't have an explicit core_tools.X entry.

func (*App) ToolsStatus

func (a *App) ToolsStatus(selector string) error

ToolsStatus prints the resolved enabled state for a selector and the rule that won, per ADR-004 §4.

func (*App) Uninstall added in v0.20.0

func (a *App) Uninstall(args uninstallArgs) error

Uninstall performs the cleanup. Public so the MCP tool surface + integration tests can call it without going through argv.

func (*App) WorktreeGC added in v0.20.0

func (a *App) WorktreeGC(minAge time.Duration) error

WorktreeGC reaps orphans (dead PID + minAge cutoff).

func (*App) WorktreeList added in v0.20.0

func (a *App) WorktreeList() error

WorktreeList prints every worktree under ~/.cache/clawtool/worktrees with its marker info. Useful before running gc to see what's reapable.

func (*App) WorktreeShow added in v0.20.0

func (a *App) WorktreeShow(taskID string) error

WorktreeShow dumps the marker JSON for one worktree.

type AutonomousDispatcher added in v0.22.71

type AutonomousDispatcher interface {
	Dispatch(ctx context.Context, agent, prompt, workdir string, iter int) (Tick, error)
}

AutonomousDispatcher is the test seam for autonomous mode.

func SetAutonomousDispatcher added in v0.22.71

func SetAutonomousDispatcher(d AutonomousDispatcher) AutonomousDispatcher

SetAutonomousDispatcher installs a stub and returns the prior one.

type InitSummary added in v0.22.60

type InitSummary struct {
	AppliedRecipes []RecipeApply     `json:"applied_recipes"`
	SkippedRecipes []RecipeSkip      `json:"skipped_recipes"`
	PendingActions []string          `json:"pending_actions"`
	Generated      map[string]string `json:"generated"`
	NextSteps      []string          `json:"next_steps"`
}

InitSummary is the structured tail of `clawtool init`. Designed for two audiences:

  • Operators on a TTY get the human-readable rendering (the pre-existing wizard output, unchanged by default).
  • LLM / chat-onboard sessions get this struct serialised as compact JSON via `--summary-json`, or in-process via ChatRender() when re-used from the parallel chat branch.

Field semantics:

  • AppliedRecipes: every recipe the apply loop touched, including idempotent skips (status=already-present) and failures (status=failed). One entry per recipe considered.
  • SkippedRecipes: recipes that never reached Apply because the pre-filter dropped them (deselected, needs options, not Core).
  • PendingActions: human-curated next-step suggestions built from the registry's known follow-ups (e.g. "install gh CLI").
  • Generated: files written this run, keyed by absolute path, value is the managed-by marker ("clawtool" / "external").
  • NextSteps: ordered, prompt-friendly bullets for an LLM to read aloud. Distinct from PendingActions: NextSteps is the curated short-list (≤5), PendingActions is the full registry.

func (InitSummary) AlreadyPresentCount added in v0.22.60

func (s InitSummary) AlreadyPresentCount() int

AlreadyPresentCount returns the count of idempotent skips.

func (InitSummary) AppliedCount added in v0.22.60

func (s InitSummary) AppliedCount() int

AppliedCount returns the number of rows whose terminal status is RecipeStatusApplied. Used by ChatRender and by the JSON consumer to decide whether anything actually changed.

func (InitSummary) AppliedNames added in v0.22.60

func (s InitSummary) AppliedNames() []string

AppliedNames returns recipe names in apply order, filtered to status=applied. Used by ChatRender for the "✓ N recipes applied: foo, bar" line.

func (InitSummary) ChatRender added in v0.22.60

func (s InitSummary) ChatRender() string

ChatRender returns the markdown completion paragraph. Stable output: pinned by TestInitSummary_ChatRender. The chat-onboard branch (parallel) imports this verbatim.

func (InitSummary) FailedCount added in v0.22.60

func (s InitSummary) FailedCount() int

FailedCount returns the count of recipes whose Apply errored.

func (InitSummary) WriteJSON added in v0.22.60

func (s InitSummary) WriteJSON(w io.Writer) error

WriteJSON emits the summary as compact JSON terminated by a newline. Used by the `--summary-json` flag in runInitAll. Errors are returned to the caller so the dispatcher can downgrade the exit code if the encoder fails (it shouldn't — InitSummary has no non-marshallable types).

type RecipeApply added in v0.22.60

type RecipeApply struct {
	Name     string            `json:"name"`
	Category string            `json:"category"`
	Status   RecipeApplyStatus `json:"status"`
	Error    string            `json:"error,omitempty"`
}

RecipeApply is one row of the apply loop. Status is mandatory; Error is set only when Status == RecipeStatusFailed.

type RecipeApplyStatus added in v0.22.60

type RecipeApplyStatus string

RecipeApplyStatus enumerates the terminal states of a single recipe in an init run. Stable string values — chat-onboard reads them verbatim.

const (
	// RecipeStatusApplied: recipe wrote artifacts this run.
	RecipeStatusApplied RecipeApplyStatus = "applied"
	// RecipeStatusAlreadyPresent: Detect reported non-Absent
	// before Apply ran. Idempotent skip — nothing was written.
	RecipeStatusAlreadyPresent RecipeApplyStatus = "already-present"
	// RecipeStatusSkipped: recipe was deselected, deferred (e.g.
	// needs required options), or the prompter voted skip.
	RecipeStatusSkipped RecipeApplyStatus = "skipped"
	// RecipeStatusFailed: Apply returned a non-skip error.
	RecipeStatusFailed RecipeApplyStatus = "failed"
)

type RecipeSkip added in v0.22.60

type RecipeSkip struct {
	Name   string `json:"name"`
	Reason string `json:"reason"`
}

RecipeSkip captures why a recipe never reached Apply. Reasons are short kebab-case tokens so chat-onboard can branch on them:

  • "operator-deselected" — user unchecked the row in the wizard
  • "missing-required-option" — recipe needs holder/owners/etc.
  • "not-core" — `--all` filter excluded the row
  • "stability-not-stable" — non-interactive defaults filter

type SpawnResult added in v0.22.98

type SpawnResult struct {
	PeerID   string   `json:"peer_id"`
	Backend  string   `json:"backend"`
	Family   string   `json:"family"`
	Terminal string   `json:"terminal"`
	PID      int      `json:"pid,omitempty"`
	Bin      string   `json:"bin"`
	Argv     []string `json:"argv"`
	Cwd      string   `json:"cwd"`
	DryRun   bool     `json:"dry_run,omitempty"`
}

SpawnResult is the structured outcome of a spawn — also surfaced verbatim by the matching MCP tool so a calling agent gets one shape regardless of entry point.

type SummaryRow added in v0.22.38

type SummaryRow struct {
	Label   string
	Outcome string // "ok" | "skip" | "fail"
	Detail  string // optional dim suffix
}

SummaryRow is one line in the closing checklist.

type Tick added in v0.22.71

type Tick struct {
	Summary      string   `json:"summary"`
	FilesChanged []string `json:"files_changed,omitempty"`
	NextSteps    string   `json:"next_steps,omitempty"`
	Done         bool     `json:"done"`
}

Tick is the structured return from each iteration; the peer is contracted to write it to <workdir>/.clawtool/autonomous/tick-<N>.json.

type WatcherSnapshot added in v0.22.155

type WatcherSnapshot struct {
	ID         int    // watcher number (e.g. 55 for tag-watcher55.log)
	LogPath    string // /tmp/clawtool-tag-watcher55.log
	Tag        string // v0.22.154 (parsed from "tag=vX.Y.Z" or "tagged ... @")
	Target     string // commit SHA the watcher's RUN_ID belongs to (best-effort)
	Status     string // last poll status: in_progress / completed / no-poll-yet / tagged / refused
	Conclusion string // last poll conclusion: success / failure / cancelled / "" / tagged
	Tagged     bool   // saw "tagged X @ Y" line
	LastLine   string // last non-empty line, for one-glance triage
}

WatcherSnapshot is the parsed state of one tag-watcher log. Exported because the autodev hook prompt also reads these.

Directories

Path Synopsis
Package listfmt — small renderer used by every `clawtool * list` subcommand (bridges, agents, sources, recipes, sandboxes, portals, hooks, …).
Package listfmt — small renderer used by every `clawtool * list` subcommand (bridges, agents, sources, recipes, sandboxes, portals, hooks, …).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL