flow is the one command Flow puts on the PATH. It runs the ticket board, sets up a machine and its projects, and switches skills and settings. This page holds the rules every flow command follows. Read it before adding a command or changing one. scripts/flow.js names the commands, and scripts/lib/cli.js turns what each one declares into parsing and help.
- The shape: where the command, the ids and the flags go
- One default noun: why tickets have no group name
- The kinds of command: board, one ticket, groups, setup, rule checks
- Hidden from help: the command and flags only the agent and the tests use
- The actions:
new,ls,get,edit,drop, switches and defaults - Status verbs: one command per status
- Print the command, never perform it: why no command moves a ticket on its own
- Flags: dashes, single letters, whole names
- Every command declares what it accepts: one list for parsing, checking and help
- The status table: the columns each status carries
- Ticket ids: prefix, number and label
flow <command> [id]... [--flags]
- The command always sits at position 1, in every command, without exception.
- A word naming no command is a ticket id.
flow exp-47shows one;flow get exp-47is the same thing spelled out. Help prints both as one row,flow [get] <id>. - Positionals name what the command acts on: one id, several ids, or the title for
new, where no ticket exists yet to point at. - A positional names one target, never 2 things.
gettakes one id, and so does each status verb:flow build exp-47, thenflow get exp-47. - No path names what a command acts on. Every command finds the root from the current directory.
- A last word may name where something goes.
flow move exp-47 homemoves tickets,flow store branchputs a project's records on a branch, andflow restore machineputs this machine back. - Everything else is a flag.
Tickets are never named in a command: flow ls, flow new "…", flow build exp-47. Every other stored thing keeps a group and reads flow <things> <action>: flow cases ls, flow cases new "…".
Exactly one stored thing goes unnamed: the most typed. A second unnamed noun would collide the moment both wanted ls.
- The board:
next,check,ls,tree. Each answers a question about the work as a whole.nextis the session opener:/flow:startruns it. - One ticket:
<id>,handoff,new,edit,file,drop,move, and the status verbs. Each names a ticket and acts on it. - A group:
cases,skills,settings,audit,restore. A different stored thing, carrying its own actions behind its own name. - Setup:
install,init,store,update,sync,doctor,surveyanduninstall. Each sets up, checks or removes a machine or a project, never a ticket.installwrites outside any project, into~/.agents,~/.claude,~/.flowand~/.local/bin.doctorreads the same places back.surveylists every place Claude Code reads its setup from, Flow's or not. Both setup sessions start from it, anddoctorreports its problems.installruns beforeflowis a command at all. Typed by path, it makes the link that lets everything else be typed by name.- A setup session's steps are flags:
flow install --checkruns before the session's first change, andflow install --finishstamps the version at its end.initandupdatetake the same 2.
- Rule checks:
scorecard. It reads~/.flow/logs/scorecards/, which the check hooks write, so it stands apart fromaudit, which reads the transcript index.
All of them share one flat namespace: a name is available once. Help prints them in sections.
A command with no section runs and never prints in help. status-line is the one Claude Code runs and nobody types. contribute waits for the sharing command that replaces it after V1.
Hidden from help
A command or a flag declared hidden: true runs like any other and never prints in flow --help. The user docs leave them out too. They exist for the agent and the tests:
flow handoff <id>: adds a line to the ticket'shistory.mdsaying this session handed the work on./flow:handoffruns it after writing## State.--root <dir>: stands in for~, so a command works on a pretend machine. Taken byinstall,init,update,doctor,survey,sync,store,uninstalland everyrestoreaction. A setup session never opens under it: the command prints what it would hand over.install --no-binanddoctor --no-bin: skip~/.local/bin, so a test leaves the realPATHalone.install --no-clone: skips cloningutil,toolboxanddomain-skills, so a test needs no network.install --drafts: links the skills inskills/drafts/too, for trying one before it ships.doctor --tests: runs both test suites as well, which takes about 14 seconds.
Recording the originals has no flow command. ~/.flow/scripts/record-originals.js <path>... copies each path into the place's original, run by a setup session before its first change, and refused outside a setup. A command on PATH can be typed by accident weeks after the setup.
Every stored thing gets these 5:
new: create onels: list many, filteredget: show one in fulledit: change a field on onedrop: remove one
The rules around them:
addstands in fornewwhere the thing already exists elsewhere and gets fetched.flow skills add <owner/repo>clones a skill repository someone else wrote.- A switch reads
on,offandreset.onandoffwrite a line at one level: this project or folder, or--globalfor every project.resetremoves that level's line. The level above then decides.flow skillsandflow settingsboth work this way. - Extra commands are allowed, and one test decides.
editsets one field, on one ticket, to a value you typed. An extra command earns its place by breaking one of those 3:dropre-points every ticket that depended on this one, andfilestamps several tickets at once.treewrites nothing at all. - A list field is set whole.
flow edit <id> --deps <id,id>replaces the list the wayflow new --depswrites it, and an empty value clears it. Adding one item means typing the list again, which keeps one command per field. - A missing action is deliberate, and the file says why. Cases have no
drop: a recorded failure is never removed. - A group names its most typed action the default, and that word can be left out.
flow skills reactisflow skills ls react, andflow audit <id>isflow audit get <id>. - A bare group name prints help, unless its default action needs no argument. Then the bare form runs that action:
flow skillsanswers,flow caseshelps. Nothing declares which: an action whoseargsare absent or bracketed, such as[words...], runs bare.flow restorelists the originals. - A group names no default where that action would write.
Every status is a command, named after where it lands. flow build exp-47, flow review exp-47, flow park exp-47 --reason "…".
- The verb comes off the status table, never hand-written: one column beside the name.
- Every verb runs the same move, through the one function holding every refusal. A verb carries no logic beyond naming a target.
- A status with no verb says why.
droppedhas none: killing a ticket repairs whatever depended on it, andflow dropis where that repair lives. editnever takes a status.
No command computes a status from something it read. flow <id> prints pick up with: flow groundwork exp-47 and stops.
The skill that picks the ticket up runs the move, after it opens the phase's own artifact. A command that moved it would move first: Claude Code runs an injected shell line before the model reads a word.
Skipped moves → add a check that catches the skip. Never put the write back in front of the read.
- Start every flag with 2 dashes.
- A single letter after one dash only where a wide convention already owns it.
flow init -yanswers yes to its question, asnpm init -yandapt -ydo. Such a flag has no 2-dash form. - Type the whole name.
--statreaches nothing.
Each command carries a list: the flags it takes, which of them are required, and the legal values wherever the list is closed.
Four things read that list:
- Parsing: an undeclared flag fails.
- Validation: a bad value fails and prints the legal ones.
- Resolving: a typed word is matched against the names legal in its position, and this is where they live.
- The help text:
flow --helpprints the whole surface from the declarations.--helpor-hafter a command prints that command's lines alone, and runs nothing.
One row per status, in lifecycle order, the order help prints the verbs in:
nameverb: the command that moves a ticket into it. Empty where the move needs code of its ownrank: where it sorts in a list. Separate from row order: in flight first, then what could start, then what was set aside, then historyopen: counts as unfinishedlive: still in play, so anything depending on it stays blockedinFlight: someone is working on itsatisfies: a dependency on this ticket counts as metterminal: history, so the folder moves to.flow/tickets/archive/reason: the move refuses without--reason
Adding a status means adding a row and nothing else. The one thing a row cannot carry is a refusal. done refuses on a parent with open children; dropped refuses while live dependents exist. Those guards are code, attached to a status by name.
An id is the project's prefix and a number: exp-47. The folder adds a label: exp-47-parser-split.
- The prefix names the place. 2 to 8 lowercase letters,
ticketPrefixin the project's.flow/settings.json, andhomefor the tickets in~/.flow/. A bare number means the current place's ticket. - The number is the identity; the label is decoration. A reference stored as
exp-47-old-labelstill resolves. - Write the label as 1 to 3 words, lowercase, joined by hyphens, generated from the title and editable afterwards. Generating one drops
the,of,toand the rest of that list first. - Any unambiguous part of an id resolves it:
exp-47,47,parser, or the whole thing. Ambiguity fails and lists the matches. - Changing a label renames the folder and nothing else:
flow edit exp-47 --label parser-split, and in practice only just after creation.depsandparenthold the id alone. A retitle leaves the label alone.