For AI Agents
outdo is deliberately easy for coding agents (and any automation) to drive: every question an agent needs to ask has a JSON answer, every failure has a stable code, and mistakes are caught before side effects.
The agent loop
AGENT RECIPE
- Discover —
outdo ls --json: every task, its flags (types, choices, positional), deps, steps,readonly/destructivetags, whether it asks before running, and whether it needs env - Preview —
outdo --dry=json <task>: exact execution order + what's already cached - Diagnose —
outdo --json explain <task>: why each task will run or skip, down to the changed files and fingerprint component — without running anything - Preflight env —
outdo --print-env <task> --json: exits3with structured issues if the environment won't validate — before anything runs - Run —
outdo --json <task>: a complete run report on stdout (per-task status, timing, cache result, critical path), logs on stderr, error object on stderr, exit code0/1/2/3/130
Why agents make fewer mistakes here
- Dep typos are compile errors. An agent editing
do.tsseesDid you mean "build"?fromtscin the editor diagnostics — before ever executing anything. - Unknown tasks/flags exit
2with aDid you meanhint — trivially recoverable programmatically. - Env schemas fail closed. A missing secret is one structured error with a path, not a half-completed deployment.
--dry=jsonis a real plan, not a guess — the same resolver that executes produces it.- The run report closes the loop. After
outdo --json build, the agent doesn't parse log soup — it readstasks[].status,error,cache, andtiming.criticalPathfrom one JSON document. explainanswers "why did that re-run?" with a named reason (inputs-changed: src/app.ts), which is exactly the question agents get asked.- Passthrough is explicit. Anything after
--reaches only the named task (ctx.rest), so an agent can forward arguments without auditing the whole graph.
Handy one-liners
# does this repo define a "deploy" task, and what flags does it take?
outdo ls --json | jq '.tasks[] | select(.name == "deploy") | {deps, args}'
# would running "release" rebuild anything, or is it all cached?
outdo --dry=json release | jq '[.plan[] | select(.cached | not) | .name]'
# why exactly will "build" re-run?
outdo --json explain build | jq '.tasks[] | select(.verdict == "would-run") | {name, reasons}'
# is the environment ready for "deploy"? (exit 3 + issues if not)
outdo --print-env deploy --json
# run and report: which tasks failed, and what bounded the wall-clock time?
outdo --json build | jq '{failed: [.tasks[] | select(.status == "failed").name], critical: .timing.criticalPath}'
# in CI: run only what this branch actually touched
outdo --affected=origin/main --json test
# machine-readable dependency graph
outdo graph --json
# which tasks does nothing use, and which are duplicates?
outdo usage --json | jq '{unused: [.tasks[] | select(.status == "unused").name], duplicates}'MCP server
outdo mcp serves every task as a Model Context Protocol tool over stdio, so Claude (or any MCP client) calls tasks with typed inputs instead of guessing flags from --help:
// .mcp.json
{ "mcpServers": { "outdo": { "command": "bunx", "args": ["outdo", "mcp"] } } }- One tool per task. Names are made tool-safe (
db:reset→db_reset); the description is the task'sdesc, plus the command it runs and whether it asks first. Services (they never finish), hooks and empty tasks are left out. - Typed inputs. The input schema is built from the task's flags — a schema flag brings its own JSON Schema (enums, patterns, ranges), an ArgSpec maps to
string/booleanwithenumfromchoices. Every tool also takesrest(args after--), andmodewhen the project declares modes. - Hints.
readonly: true→readOnlyHint;destructive: true,protectorconfirm→destructiveHint, so the client asks you before calling it. - Confirmation stays with you. A task that asks before running gets a
yesinput described as "set true only after the user explicitly agreed". Without it the call fails with outdo's "asks for confirmation" error — nothing runs. - Results. A call runs
outdo run <task>in a child process and returns its output (the last 30,000 characters) plus the exit code; a non-zero exit is anisErrorresult. A cancelled request kills the run. - Under
OUTDO_READONLY=1only read-only tasks are listed.
Unattended loops
Agents running on their own (loops, worktrees, CI bots) should only touch tasks that can't hurt:
- Tag tasks
readonly: true/destructive: true;outdo ls --jsonexposes the tags, so a tool-use hook can allowreadonlytasks and block the rest. - Set
OUTDO_READONLY=1in the loop's environment and outdo itself refuses everything else (exit2, before anything runs). - protect/confirm tasks refuse without a terminal; an agent must never add
--yeson its own.
Cleaning up a do.ts
outdo usage --json is the evidence for a cleanup: every task with its runs (you, agent, deps, hooks, typed), status (used / referenced / unused), what references it (usedBy), when outdo first saw it (firstSeen), each flag's given / runs / unusedValues, and the static duplicates. Runs an agent makes are recorded as the agent's (AI_AGENT, CLAUDECODE, MCP), so you can see what only your agents use.
- Act on
duplicatesfirst — they are static facts. - Treat
unusedas a proposal: checktrackedSince(a young log proves little), and member scripts (pkg#name) that a container or turbo may run. Ask before deleting.
The outdo skill
outdo ships an agent skill — how to read the task list, run tasks safely, write do.ts entries, and move a repo off scripts/turbo/makefiles — in the Vercel skills layout (skills/outdo/SKILL.md):
bunx skills add meslzy/outdo # Claude Code, Cursor, Codex, ...outdo skill install # this repo: .claude/skills/outdo
outdo skill install --global # every repo: ~/.claude/skills/outdobunx skills experimental_sync # links skills bundled in installed packagesoutdo skill install needs nothing but outdo itself (no Node); it copies the skill bundled with the installed version, so the instructions always match the CLI you run. outdo skill path prints where that copy lives.
Writing agent-friendly tasks
- Give every task a
desc— it's the documentationls --jsonand MCP expose - Prefer typed
argsover parsingctx.rest— they're discoverable, validated, and become MCP input schemas - Declare
envschemas for anything touching credentials — agents get preflight instead of mid-run explosions - Declare
inputsso repeated agent runs are near-free - Mark tasks
readonly/destructive, andprotectthe dangerous ones