Reference

The CLI, command by command

On this page

grooph makes, checks, draws, and shares workflow diagrams (graphs, plans, and operation maps) from a terminal. It never runs an agent and never calls a model. The commands under Experimental watch a session as it runs; they may change, or go, in a later version. Every command below is shown with the help it prints itself, grooph help <command>, in the order grooph --help lists them. New here? Start with the quickstart.

Generated by scripts/cli-reference.mjs from the CLI's own help text. Do not edit it; CI fails when it is stale.

grooph: draw workflow diagrams, check them, and share them. It never runs an agent.

Usage: grooph <command> [options]      grooph help <command> for one command in full

Start
  new          make an empty graph document
  template     list, show and use ready-made graphs (try: template list)
  sub          place a template inside a graph as one box, and keep it current
  apply        change a graph with a list of JSON ops
  pick         write one candidate of a proposal set out as a graph
  import       read Mermaid text into a graph document

Check
  validate     check a graph or an operation map; say what to fix
  explain      what a graph's brakes are: rounds, budgets, gates, worst case
  shape        counts and brakes at a glance
  canonicalize print or rewrite a document in canonical form

See
  plan         a plan for people to follow: PLAN.md, the picture and the document
  image        a picture of a graph or map, SVG or PNG
  outline      the whole document as Markdown to read
  page         one offline HTML file with a viewer
  glyph        the small wordless picture of a graph's shape
  mermaid      Mermaid text: a flowchart, or an agentflow (beta)

Share
  share        a link that opens a graph, set, run or map in the app
  embed        one line of HTML that shows a graph on any page

Experimental
  watch        serve the app and the live sessions to this machine's browser
  hooks        install the event hook (it records and never steers)
  sessions     what the hook has seen
  events       push or inspect recorded events
  mcp          an MCP server for an agent to call

First time? https://ryanjosephkamp.github.io/grooph/docs/quickstart/   grooph --version prints the version.

grooph new#

Make an empty graph document.

grooph new --name <name> [--goal <goal>] [--target <harness>] [--out <file>] [--force]

Make a minimal graph document: the id is the name's slug, no nodes yet.
At a terminal it writes <id>.grooph.json here; piped, or with --out, it prints or writes
where you say. It never overwrites a file without --force.

Example
  grooph new --name "Fix the flaky test" --target claude-code

grooph template#

List, show and use ready-made graphs (try: template list)

Usage
  grooph template list [--json] [--registry <url>]
  grooph template show <name> [--json] [--registry <url>]
  grooph template use <name> [--name <graph name>] [--set key=value …] [--out <file>] [--force]
  grooph template insert <name> --into <file> [--set key=value …] [--prefix <p>] [--write]
  grooph template save <file> --id <id> --title <t> --summary <s> --when <w>
                       [--fragment --nodes a,b,c] [--to project|user] [--force]
  grooph template add <name | url> [--to project|user] [--registry <url>] [--force]

A name is a template id. It resolves, first hit wins, in .grooph/templates/ at the root of
the working tree, then ~/.grooph/templates/ ($GROOPH_HOME/templates), then the built-in
pattern library, then the plan templates beside it, and only then remotely: each --registry <url> (an index.json, or the folder
holding one), or else the published library ($GROOPH_REGISTRY overrides it).

  list     Every local template with its kind, profile and when-to-use line; --registry adds a
           remote registry's. A local template shadows a later one with the same id. --json
           adds each row's index entry, source and location, and `glyph`: the path or URL of
           its pre-drawn glyph where the registry keeps one (grooph glyph draws any other).
           The plan templates (graphs a person follows) are listed last under a heading of their
           own, Plans, with no profile, since nothing was measured of one; they are not counted
           with the templates, and --json gives them in a list of their own, `plans`.
  show     One template: summary, when to use and not, profile, slots with their questions,
           nodes, edges and loops.
  use      A new graph from a whole-graph template: slots filled from --set, a new id and name
           (--name, else the template's title), version 1, lineage naming the template. At a
           terminal it writes <id>.grooph.json here; piped, it prints the document. --out writes
           that file (never over a file without --force). Questions for unfilled slots go to stderr.
           For a graph that is a plan, the last line names grooph plan.
  insert   Add a template's nodes, edges, loops and policies to a graph, renaming ids that
           collide (or prefixing them all with --prefix) and printing where each id landed.
           Nothing is connected to the graph's nodes. Dry run unless --write.
  save     Make a template from a graph, or with --fragment --nodes from some of its nodes,
           and write it to the project (default) or user folder, refreshing its index.json.
           Replacing an existing template needs --force and bumps its version.
  add      Copy a template from a remote registry (by name) or a URL into the project
           (default) or user folder, so it resolves locally from then on.

Example
  grooph template use grind-loop --name "Fix the flaky test" --set task="make the checkout test pass" --out flaky.grooph.json

grooph sub#

Place a template inside a graph as one box, and keep it current.

grooph sub add <template> --into <file> --as <id> [--name <name>] [--set key=value …] [--after <node>] [--then <node>] [--write]
grooph sub list <file> [--json]
grooph sub update <file> [<group> …] [--allow <change> …] [--write]
grooph sub extract <file> <group> --id <id> --title <t> --summary <s> --when <w> [--to project|user] [--force]

A subgrooph is a template placed inside a graph as a unit: a group that remembers which
template and version it came from, and the values it was filled with. Its nodes are ordinary
nodes of the one document. Nothing is fetched or inlined when a package is compiled.

  add      Place a template in the graph as a group named --as. Everything that comes in gets an
           id that starts with it (review-builder). --after <node> leads into it from a node of the
           graph. --then <node> leads on from it: the edges that reached the template's own
           success stop go to that node, and that stop is dropped; a stop that halts stays.
           Every id that begins with --as and a dash is the subgrooph's own from then on, so an
           --as the graph already uses that way is refused, and so is a template with a lead
           node. If the graph kept a node behind a person and the subgrooph now leads to it
           around that person, that is said. Dry run unless --write.
  list     Every group: the template and version it came from if it is a subgrooph, how many
           nodes it holds, and the edges that lead in and out. --json prints the same as data.
  update   What a newer version of its template would change in each subgrooph (or in the ones
           named), then the graph with those changes. A change that removes or loosens a brake
           (a human gate, an approval, an irreversible marker, a loop's round cap and budget, a
           bar's acceptance, critic isolation, a check) is listed first and NOT applied unless you ask
           for it by its name with --allow. The brakes are compared on the whole graph as it would
           be written, so one cannot be shed under a new id or in two changes: what a run could
           reach only by a gate, an approval, a critic's verdict or a check's, it may not reach
           without it afterwards. Any change to what a check runs or what counts as its pass is
           held: a program cannot tell a stricter command from a looser one.
           A change says every reason it is held for. Tightening applies with the rest. The shape moves as a whole: while a change to the
           nodes, edges or loop members is held back, the others wait for it. A node you added
           inside the box under another id is yours, and stays. Dry run unless --write.
  Neither add nor update writes a graph it would leave with an error the file does not have now.
  extract  Save any group as a template, in the project (default) or user folder.

A template is found as grooph template finds it: the project, then your home folder, then
the built-in library, then --registry <url> or the published library.

Example
  grooph sub add review-gate --into plan.grooph.json --as review --after plan --then release \
    --set task="the checkout flow" --set test-command="pnpm test" --set checklist=docs/checklist.md --write

grooph apply#

Change a graph with a list of JSON ops.

grooph apply <file> --ops <ops.json | -> [--write] [--complete] [--loops] [--json]

Apply a JSON list of document operations ({"op": "addNode", ...}; the list is in
packages/core/README.md), read from a file or from stdin with --ops -. All or nothing: an op
that cannot apply is named and nothing is written. Prints the resulting issues; --write saves
the result in canonical form (never one that fails the schema). Exits 1 while errors remain.
--complete and --loops check the result as grooph validate does with them.

The graph a package keeps (.grooph/<id>/graph.grooph.json) is not written by apply, or by any
command of grooph's: what placed the package compares the next graph with it. Work on a copy of
your own, .grooph/graphs/<id>.grooph.json by habit.

Example
  echo '[{"op":"addNode","kind":"agent","name":"Builder"}]' | grooph apply g.grooph.json --ops - --write

grooph pick#

Write one candidate of a proposal set out as a graph.

grooph pick <proposal set> <candidate id | label> --out <graph file> [--force]

Write the chosen candidate out as an ordinary graph document. The candidate is
named by its id or its label, ignoring case; a name that matches one candidate's id and
another's label is refused as ambiguous. The graph is checked first and nothing is written
while it has errors. A candidate with a step that is a person's, or one that names no
harness, is a plan: what only a package would ask of it is no error of it, so it is picked
like any other, and the command says so and that grooph plan exports it. An existing --out is replaced only with --force
(or when it already holds the same graph).

grooph import#

Read Mermaid text into a graph document.

grooph import <file.mmd | -> [--name <name>] [--out <graph file>]

Read Mermaid text into a graph document: a flowchart (or graph) or an
agentflow-beta, as grooph mermaid writes them or as people write them by hand.
A Markdown file's first ```mermaid block is read the same way.

A node's shape says its kind: { } a critic, {{ }} a check, [/ \] a human gate,
(( )) a merge, ((( ))) a stop, and any other shape an agent. In an agentflow a
flow is an agent (its model and instruction carried over), an action is a
person's step, a decision is a check, and an input is the goal. A cycle becomes
a loop, and a subgraph that holds no loop becomes a group. Text that grooph
mermaid wrote comes back with its loops' names and stops.

What the text cannot say, such as a brief or an agent's outputs, is filled in,
and each guess is printed on stderr, one per line. Loop rules are advice, so a
sketch with no stops still opens: grooph validate --loops says what each loop
lacks. Anything else in the text is refused, with what was met and what is read.

Reads stdin with -. Prints the document, or writes it with --out. --name names
the graph when the text does not.

grooph validate#

Check a graph or an operation map; say what to fix.

grooph validate <file> [--complete] [--loops] [--json]

Check a document against the schema and the rules in docs/graph-ir.md §3; docs/rules.md lists
every code with an example. Exits 1 when there are errors. --complete also checks that the
document is finished: every {{slot}} filled (E_UNFILLED_SLOT), a goal (E_NO_GOAL), and a graph
rather than a template (E_IS_TEMPLATE); and, for a package for a harness, which is experimental,
a harness grooph has a compiler for (E_NO_TARGET) and no person's step (E_PERSON_STEP_NOT_COMPILED),
which a plan may keep. --for-export is its old name, and still works. --loops adds the advice
about loops and stops: six rules on whether each loop can end and is judged against something
that can be read, given as warnings and never as errors. --json prints the issue list as JSON. A proposal set is not a graph (grooph share checks one). An operation map
(*.grooph-map.json) is checked against its own rules, docs/operation-map.md §3.

Example
  grooph validate --complete flaky.grooph.json

grooph explain#

What a graph's brakes are: rounds, budgets, gates, worst case.

grooph explain <file> [--json]

Say in plain words what a graph's brakes are: for each loop, how many rounds at most, its budget and
what happens at each stop; every human gate and what it guards; and the worst case in one line.
It adds no rule: it reads what the validator reads. --json gives the same as data.

Example
  grooph explain flaky.grooph.json

grooph shape#

Counts and brakes at a glance.

grooph shape <graph file> [--json]

The structure of a graph at a glance (docs/executive.md §1): agents, checks, gates, loops,
tiers, the worst-case number of loop rounds (nested loops multiplied; unknown when a loop
has no max-iterations stop) and each loop's budget. Counts and brakes, no dollar figures.
--json prints the shape as data.

For an operation map (*.grooph-map.json): lanes, sessions, handoffs, and how many of the
handoffs a person carries.

grooph canonicalize#

Print or rewrite a document in canonical form.

grooph canonicalize <file> [--write]

Print the document in canonical form (docs/graph-ir.md §7), or rewrite the file with --write.

Example
  grooph canonicalize g.grooph.json --write

grooph plan#

A plan for people to follow: PLAN.md, the picture and the document.

grooph plan <graph> [--into <dir>] [--force]

Write a plan of a graph: something for people to read and follow, whether or not a coding
harness could run it. Three files:
  PLAN.md             who does what, what has to be fixed before a harness can run it, then
                      every step in full
  <id>.svg            the picture, as grooph image draws it
  <id>.grooph.json    the document in canonical form: the one to edit

Any document that reads as a graph gets its plan: one with no harness named, no goal, or an
error of its own. Each such finding is printed, and written into PLAN.md under "To fix before a
harness can run this". The exit code is 0 when the files were written. Only a document that does
not match the schema gets none, since nothing can draw it.

A plan is not a package: it has no lead's brief, no agent files and no kickoff, and nothing in
it is handed to a harness.

  --into <dir>   the folder to write into. Default: <id>-plan in the current folder.
                 No part of the way to it may begin with a dot: a plan is for people to
                 read, and those folders (.git, .claude, .codex) are a tool's.
  --force        replace a file already there that is not this plan's: a PLAN.md grooph did not
                 write for this graph, a picture grooph did not draw, a copy of the graph that
                 differs from the one given and is not the file given, or a file that cannot
                 be read. Without it such a file stops the command, and nothing is written.

PLAN.md says the copy of the graph beside it is the one to edit. To bring the plan up to date
after editing it, make the plan from that copy: grooph plan <dir>/<id>.grooph.json --into <dir>.
PLAN.md and the picture are drawn again from the graph each time: what a person adds to them is
not kept, so notes belong in the graph or in a file of their own.

The graph a package keeps (.grooph/<id>/graph.grooph.json) is never written by this command.

Examples
  grooph plan study.grooph.json
  grooph plan study.grooph.json --into docs/study-plan

grooph image#

A picture of a graph or map, SVG or PNG.

grooph image <graph | operation map> [--out <file.svg | file.png>] [--theme <name>] [--scale <n>] [--open <group> | all]... [--layout phone | wide] [--view <view>] [--events <id>=<source>]...

The picture of a document with its words on it, laid out for a phone: 400 units wide,
so it reads at a phone's width without zooming.

A graph is drawn as one column in the order work reaches each node: a card per node
(kind, name, role and tier), an arrow to the next card, every other edge in the left
margin, every loop's back edge in the right, and below the cards each loop with its
bar and its stops in order.

A subgrooph (a template placed as a unit, grooph sub) is one box: its name, the template
and version it came from, how many nodes it holds, which brakes are among them, and the
glyph of what is inside. What crosses its edge starts or ends at the box.

  --open <group>         draw that subgrooph open: its nodes as cards of their own, kept
                         together inside a frame under its name. As often as wanted.
  --open all             every subgrooph open. One inside a closed one stays out of sight.

An operation map (*.grooph-map.json, docs/operation-map.md) is drawn as its lanes top to
bottom, each session a card in its lane, each handoff a numbered arc in the margin, and
the handoffs listed below with what carries each.

An operation map has two more views, for a screen with room or a map with many handoffs
(docs/operation-map.md §4c and §4d). Both are wider than a phone:

  --layout wide          its lanes side by side: the people in a band across the top, each lane
                         a column, each handoff an arc in the gutters between the lanes, and
                         the list below in columns. As wide as its lanes need, about 900 units
                         for three lanes. --layout phone is the picture above, and the default.
  --view sequence        a column for each person and session, and a row for each handoff in the
                         order the map lists them: a numbered arrow from sender to receiver, with
                         what carries it and what is handed. An order, not a clock: a map records
                         no times. --view picture is the default.

A still in three dimensions: the view the app opens, drawn once from where the app's view
starts, with the document's name over it and what the view is in a sentence (docs/exports.md,
"A still from the command line"). For a window, 800 units wide; --layout phone draws it for a
phone, 400 wide, things standing one under another as the app has them there.

  --view stairs          each loop a floor with its own nodes standing on it, each edge an arc,
                         numbered in the order of a first pass. An operation map's stairs: each
                         lane a sheet, the people's on top, each handoff an arc (--events marks
                         the cards, as on the picture).
  --view panes           the picture as it is, each loop and each subgrooph lifted on a pane.
  --view spiral          each loop a spiral, a round one turn up, the lid where max iterations
                         stops it; each loop's brakes in words under it.
  --view rings           each loop a ring, its nodes standing round it.
  --view columns         every node where the picture has it, an agent a column as tall as its
                         tier: an order, not a model.

  --theme <name>         one of six looks for the same picture: paper, blueprint, ink, phosphor, transit, chalk
                         (docs/exports.md). Without it, paper: the picture as it has always been.
                         A theme changes colors, line weights, lettering and the ground, never
                         the words or where anything is.
  --theme light | dark   light or dark only, the colors written into the file: it looks the
                         same anywhere. With a name: --theme chalk-dark
  --theme auto           (SVG only, the SVG default) light and dark both; follows the viewer
  --out <file.svg>       write the SVG; without --out it is printed
  --out <file.png>       write a PNG, 3 pixels to the unit (1,200 px wide for the phone's
                         picture); --scale changes that. A PNG is light unless told dark
                         (Phosphor has one form, and is dark either way). Its renderer knows
                         a theme's colors, line weights, lettering, ground and Chalk's wobble,
                         and not the rules for corners, capitals, and Transit's route color
                         and larger arrowheads: for those, the SVG, or Keep a copy in the app.

  --events <id>=<src>    for an operation map: draw what the event hook has seen on the
                         session with that id (working, waiting or ended; subagents running
                         and done). <src> is an events file, a folder, a project, or
                         git:<ref>. As often as wanted. A snapshot of now: run it again
                         for a newer one. docs/operation-map.md §4b.

Deterministic: the same document and theme give the same SVG bytes. The PNG is drawn
with this machine's fonts, so it can differ by machine. For the wordless shape, grooph glyph.

grooph outline#

The whole document as Markdown to read.

grooph outline <graph | operation map> [--out <file.md>]

The whole document to read from top to bottom, as Markdown: a graph's every node with
its full brief, each edge as a sentence, each loop with its bar and stops; a map's lanes,
sessions and handoffs. One way only: edit the document, never the outline.

grooph page#

One offline HTML file with a viewer.

grooph page <graph | operation map> --out <file.html> [--theme <name>] [--events <id>=<source>]...

One HTML file that holds the document and a viewer for it: the picture, the outline, the
validator's list and the document itself. It asks the network for nothing (its content
security policy forbids every request), so it opens on a phone with no connection, from
a message, a drive or a folder. Light and dark; tap a card to read about it; Save
document writes the .grooph.json (or .grooph-map.json) back out for the app to import.

For an operation map, --events <session id>=<source> (as often as wanted) marks each
session with what the event hook has seen of it, as grooph image does: a snapshot, taken
when the page is made.

--theme <name> draws the page's picture in one of six looks (paper, blueprint, ink, phosphor, transit, chalk;
docs/exports.md). The page follows the device's light or dark and has its own button for
it, so neither is named here. Without --theme the picture is paper, as always.

A document with rule errors still makes a page, with the errors listed. One that does
not match its schema cannot be drawn.

grooph glyph#

The small wordless picture of a graph's shape.

grooph glyph <graph file> [--out <svg file>] [--scale <n>]

The glyph of a graph: a small SVG of its shape with no words, the same picture the
app's template list, the compare cards and the pattern write-ups show. Node kinds
and role families are shapes (writer square, critic diamond, check hexagon, human
gate octagon, merge circle, stop dot; an irreversible step carries a bar), edges are
lines styled by their condition (pass solid, fail dashed, a verdict dotted, approval
doubled), a loop is a dashed hull with its back edges drawn returning. Deterministic:
the same document gives the same bytes. Colors are CSS variables with fallbacks, so
the file reads on its own and inline in either theme.

Prints the SVG, or writes it with --out. --scale multiplies the width and height
attributes (the viewBox is unchanged). Uses the document's own layout when it has
one, otherwise a layered layout, left to right.

grooph mermaid#

Mermaid text: a flowchart, or an agentflow (beta)

grooph mermaid <graph file> [--agentflow] [--out <file>]

A Mermaid projection of a graph: a flowchart LR with one subgraph per loop
(nested when loops nest), node labels shaped by kind and role family, edge labels
from the edge's condition and approval, a loop's back edges dotted, and one note
per loop that lists its stops. grooph import reads it back: its nodes, edges, loops
and stops. Briefs, bars, brakes and the rest live only in the graph document.

--agentflow (beta) writes Mermaid's agentflow-beta syntax instead, the diagram type
Mermaid 12 added for agent workflows: an agent is a flow holding its task, with its
role, model tier and brief as metadata; a person's step and a human gate are actions;
a check is a decision; the goal is an input; and a loop's way back carries its stops.
Mermaid labels agentflow beta, so its syntax may still change; the flowchart stays
the default.

Prints the text, or writes it with --out.

grooph share#

A link that opens a graph, set, run or map in the app.

grooph share <graph | proposal set | run bundle | operation map> [--base <url>] [--open] [--out <file>]

Turn a graph, a set of one to four candidate graphs, a recorded run or an operation map into a link
that opens it in the grooph app on any device. The document travels in the link's #fragment, which browsers
do not send to any server; nothing is uploaded.

A recorded run (a *.grooph-run.json bundle) opens in the run view: what ran, what the run
changed and why, its proposals, and its notes. --out then writes the bundle again, the
fallback when the link is too long.

An operation map (*.grooph-map.json, docs/operation-map.md) opens as its picture with its
handoffs listed. A map is never compiled, so one with rule errors still shares: the view
shows them.

Validates first and refuses a graph, or a candidate, with errors. Inlines { "file" }
candidates, computes each candidate's shape, prints the comparison and the link with its
length, and warns when the link is over 32,000 characters (messengers cut long links).

  --base <url>   where the app is served (default https://ryanjosephkamp.github.io/grooph/);
                 http://localhost:4173/grooph/ for a local build
  --open         also open the link in the default browser
  --out <file>   also write the self-contained document (graphs inlined, shapes computed);
                 import it in the app when a link is too long to send

A proposal set, <set-id>.grooph-proposals.json (docs/executive.md §1):

  {
    "groophProposals": 0,
    "id": "csv-export",
    "title": "CSV export for the orders list",
    "brief": "The project and its constraints as you understood them.",
    "candidates": [
      {
        "id": "lean",
        "label": "Lean",
        "graph": { "file": "lean.grooph.json" },
        "basedOn": "grind-loop",
        "rationale": "Why this shape fits this project, in three or four sentences.",
        "pros": ["what it buys"],
        "cons": ["what it costs, and each validation warning in plain words"],
        "profile": { "cost": "low", "speed": "fast", "rigor": "light" }
      }
    ],
    "recommendation": { "candidate": "lean", "why": "One or two sentences." }
  }

  ids are kebab-case and unique; one to four candidates; labels distinct, case ignored
  (a person picks by saying one back); "graph" is a path relative to this file's folder,
  or the graph document itself; "basedOn" and "recommendation" are optional; cost is
  low | medium | high, speed fast | medium | slow, rigor light | standard | high.
  Leave "shape" out: share computes it.

grooph embed#

One line of HTML that shows a graph on any page.

grooph embed <file> [--theme <name>] [--height <px>] [--frame] [--play] [--base <url>]

<file> is a graph, a recorded run (a *.grooph-run.json bundle), an operation map, or a proposal set.

Print the HTML that puts a live, read-only picture of the document in any web page: an
<iframe> on the first line, and on the second a one-line script that sizes the frame to the
picture. A reader can pan and zoom it, tap a node for its brief, and open it in grooph. A
recorded run (a *.grooph-run.json bundle) plays: play, step and a scrubber.

The document travels in the frame's address after the #, as in grooph share, so the host
page's server never sees it. Nothing about the reader is sent anywhere.

  --theme <t>    one of six looks for the picture: paper, blueprint, ink, phosphor, transit, chalk
                 (docs/exports.md); without it, paper. Or light or dark, which hold the picture
                 to that form; without one it follows the reader's color scheme. Both as
                 chalk-dark. The theme is in the frame's address, and fetched only then.
  --height <px>  the frame's height when the page has no script to size it
                 (default: the picture's own height at a phone's width)
  --frame        draw the embed's own background and border, not the page's background
  --play         start a run's replay as soon as it is shown
  --base <url>   where the app is served (default https://ryanjosephkamp.github.io/grooph/);
                 http://localhost:4173/grooph/ for a local build

Without the script the frame keeps its height, and the picture shrinks to fit or can be
dragged. Example:

  grooph embed review.grooph.json --theme light >> post.html

grooph watch#

Serve the app and the live sessions to this machine's browser.

grooph watch [--sessions] [--events <source>]... [--map <operation map>] [--port 4174] [--host 127.0.0.1] [--open]

Experimental: this command may change, or go, in a later version.

Watch from a browser, on this machine or a phone on the same network. Serves the built
grooph app and one read-only endpoint, read again from disk on every request:

  the sessions  what the event hook has recorded (grooph hooks install): each session
                and its subagents as they start and stop, in Claude Code and in Codex

  --sessions    taken and not needed: the sessions are what watch serves
  --events <s>  more events to merge in, as often as wanted: a file, a folder of files,
                another project's folder, or git:<ref> for a branch that carries its
                .grooph/events/ (read from the watched project's repository, no checkout).
                name=<source> shows its sessions under that name. The project's own
                .grooph/events/ is always read; in a folder listed with grooph hooks watch,
                so is the record kept for it outside the project.
  --map <file>  an operation map to draw above the sessions, each of its sessions marked
                with what the hook has seen. Name each --events source for the map session
                it belongs to: --events operator=git:origin/operator.
  --port <n>    default 4174; 0 picks a free one
  --host <h>    default 127.0.0.1, this machine only. Another address (0.0.0.0 for every
                interface) lets anyone on that network read all of it while watch runs.
  --open        open the view in the default browser

It takes no folder: it serves the project it is started in, and --events adds another's.
It writes nothing, needs no credentials, and stops with Ctrl-C. It is a local viewer, not a
backend: the app it serves makes no request except to this server.

grooph hooks#

Install the event hook (it records and never steers)

grooph hooks install | status | remove [--dir <project>] [--harness claude-code,codex] [--tools] [--local]
grooph hooks install | remove --user [--harness claude-code,codex] [--tools]
grooph hooks watch | unwatch <folder>...

Experimental: this command may change, or go, in a later version.

Record which subagents a session starts and stops, by a hook, into one file per session
under <project>/.grooph/events/. grooph watch and grooph sessions read those files.

  install   copy the hook to <project>/.grooph/hooks/grooph-event.mjs and add it to the
            harness's hook settings: .claude/settings.json for Claude Code,
            .codex/hooks.json for Codex. Safe to run again: it replaces its own entries
            and leaves every other setting as it was.
  status    say what is installed, how many session files there are, and how the last
            push of them went (a push made by the hook says nothing aloud: this is where
            one that failed is seen)
  remove    take the entries and the hook file out again; the events stay

  --dir <project>   the project (default: the current folder)
  --harness <list>  claude-code (the default), codex, or both separated by a comma
  --push            also send the events to their own branch, in the background (grooph events
                    push): at the start and the end of every turn, and during a long turn at
                    most every ten minutes. Off unless asked for: it uses the session's right
                    to push. The branch is grooph-events/<the branch checked out>, or
                    grooph-events-detached while none is. --push-branch <name> names one branch
                    for every session that reads these settings; many sessions may share it.
  --tools           also record every finished tool call (its name only), so a subagent
                    shows its last tool. Without it, Claude Code's spawn tool is the only one recorded.
  --local           Claude Code only: write .claude/settings.local.json (this machine)
                    instead of .claude/settings.json (shared with the repository)

Outside the project (--user): installed once for the machine, for the folders you list. A
watched project then holds nothing of grooph's: no hook, no settings entry, no events.

  install --user    copy the hook to ~/.grooph/hooks/grooph-event.mjs and add it to the
                    harness's own settings for every project: ~/.claude/settings.json for
                    Claude Code, ~/.codex/hooks.json for Codex. Safe to run again. Its entries
                    go into the file where it stands and every other character of it is left
                    as it was; a file it cannot read, or could not put back whole, is not changed.
  watch <folder>    list a folder. A session working in it, or in a folder inside it, is
                    recorded from then on. Until a folder is listed nothing is recorded.
                    A worktree or a copy kept in another place is another folder.
  unwatch <folder>  take a folder off the list; its events stay
  status            also prints the list, and what is installed outside the project
  remove --user     take the entries and the hook file out again; the list and the events stay

  The events go to ~/.grooph/events/<a name made from the folder's>/, one file per session.
  In a listed folder grooph sessions, grooph watch --sessions and grooph hooks status read
  them from there, and say so.
  Every session on the machine then starts the hook at each of its events, whatever folder
  it works in. For a folder that is not listed the hook reads the list and leaves.
  GROOPH_HOME names another folder than ~/.grooph, by its whole path. CLAUDE_CONFIG_DIR and
  CODEX_HOME name the harnesses' folders, as they do for the harnesses themselves.
  --push, --local and GROOPH_EVENTS_DIR are for an install in a project: grooph events push
  sends a project's own .grooph/events/ and nothing else.

The hook appends one line and exits 0. It prints nothing, never blocks, and records ids,
names and times: never a prompt, a tool's input or result, or a reply. It needs Node.
Codex runs a hook only after you have reviewed it (open /hooks in Codex once and trust it),
and a project's hooks only in a folder it trusts. In a project, commit .codex/hooks.json: the
Codex app works in its own copy of the repository, which holds only committed files.
What each harness tells a hook, with sources: docs/subagents.md.

grooph sessions#

What the hook has seen.

grooph sessions [<source>...] [--json]

Experimental: this command may change, or go, in a later version.

What the event hook has seen: each session, and under it each subagent with whether it is
running or done, for how long, and its last tool. With no source, ./.grooph/events/;
in a folder listed with grooph hooks watch, the record kept for it outside the project
(and both, where the folder has both: a session is shown once).

  <source>   an events file, a folder of them, or a project folder (its .grooph/events/).
             git:<ref> reads .grooph/events/ from a git ref without checking it out:
             a session that sends its events with grooph events push is read, after a
             fetch, as git:origin/grooph-events/<its branch>. Several sources merge
             into one list, in time order. A session two sources both hold is shown
             once, under the source that holds the most of it.
             name=<source> shows that source's sessions under a name: a project, a machine.
  --json     the same as data: the live view grooph watch serves (docs/subagents.md)

Reads files; starts nothing, asks no harness anything, and writes nothing.

grooph events#

Push or inspect recorded events.

grooph events push [--branch <name>] [--remote <name>] [--since <time> [--session <id>]] [--no-push] [--dir <project>]

Experimental: this command may change, or go, in a later version.

Send this project's session events to a branch of their own, so another machine can
read them and no pull request ever carries them.

  --branch <name>   the branch to write. Default: grooph-events/<the branch checked out>,
                    and grooph-events-detached when no branch is checked out.
                    Give the whole name when a harness only lets a session push under a
                    prefix, for example --branch claude/grooph-events-laptop
  --remote <name>   default: origin
  --no-push         make the commit and print its id; send nothing
  --since <time>    leave out event files whose last line is older than this, unless the
                    branch already holds them. The hook at a turn's end gives its own
                    session's first line, so files an earlier session left in the folder
                    do not go to a branch that never had them
  --session <id>    with --since: this session's own files go whatever their times say
  --dir <project>   default: the current directory

It makes one commit whose tree is .grooph/events/ and nothing else, on top of what that
branch already holds, and pushes it. It never touches the working tree, the index, HEAD
or the branch checked out. Event files already on the branch that this clone does not
have are kept, and a file both have is never made shorter, so many sessions may share
one. When another session sends first, this one looks again and goes on top of it.

What is sent is what the hook wrote, less two paths: a folder goes as its name, and the
path of a subagent's transcript is left out. Only whole lines that are events go. The files
here keep all.

It writes only to a branch that holds events and nothing else: a branch with any other
file on it, and the branch checked out here, are refused.

How the last push went is kept in .grooph/events/.last-push.json (never sent), and
grooph hooks status says it.

A session with no grooph installed runs the same code as a script that
grooph hooks install puts beside the hook:
  node .grooph/hooks/grooph-events-push.mjs

Read them elsewhere, after a fetch:
  grooph sessions laptop=git:origin/grooph-events/<branch>

grooph mcp#

An MCP server for an agent to call.

grooph mcp [--dir <project>] [--harness <name>] [--chat]

Experimental: this command may change, or go, in a later version.

Run grooph's MCP server on standard input and output, for an agent to call: in a coding
session, or in a chat app that runs local servers. No model is called and nothing leaves
the machine.

To author a graph with tool calls alone (docs/agents.md). A document goes in and comes
back as JSON, so no file has to exist; path reads a file and out writes one:

  grooph_templates      the library with when to use each; one template in full by id
  grooph_use_template   a graph from a template: id, name, slot values
  grooph_new            an empty graph
  grooph_apply          a graph and typed operations: the graph, or the failing one by index
  grooph_validate       the issues by code, with what to do about each
  grooph_explain        its brakes: rounds, budgets, who must say go, the worst case
  grooph_shape          counts and brakes on one line
  grooph_share          a link the app opens on any device, and the embed line
  grooph_picture        the picture, or a still in 3D, as SVG text, and a PNG when asked
  grooph_export_plan    a plan for people to follow: PLAN.md, the picture, the document
  grooph_export         the prompt package's files, returned or written into the project

For a session's lead, beside what the event hook sees (docs/subagents.md §7):

  grooph_plan      declare the subagents the session is about to start
  grooph_note      leave a short note for whoever is watching
  grooph_running   what the event hook has seen: sessions, subagents, what is running,
                   and each declared plan with how much of it has started

A tool writes a file only when it is given a name for one, only inside the project folder,
never under .git and never through a link. A graph is saved as <name>.grooph.json. A file
already there is replaced only when it is the graph the call read, a picture grooph drew,
or a package's files as grooph last wrote them; anything else needs "replace": true.
A plan and a note are appended to <project>/.grooph/events/said-<session>.jsonl.
The graph a package keeps (.grooph/<id>/graph.grooph.json) is written only by grooph_export.
grooph_export reads the target's own tier variable from the server's environment (GROOPH_MODELS
for claude-code, GROOPH_MODELS_CODEX for codex; neither for the other), lays the call's own "models"
over it, says what every tier means, and asks before it changes the model of an agent file
already in place. Over a package in place for the same graph id, while the graph that
package keeps reads, it also lists each change that may remove or loosen a brake of that graph,
and places it only when that change is named in "allow"; the reply's "brakes:" line says whether
a comparison was made.

Add it to a harness:
  Claude Code   claude mcp add grooph -- grooph mcp
                or in .mcp.json: { "mcpServers": { "grooph": { "command": "grooph", "args": ["mcp"] } } }
  Codex         in ~/.codex/config.toml:  [mcp_servers.grooph]
                                          command = "grooph"
                                          args = ["mcp", "--harness", "codex"]
  Claude's desktop app, in a chat (docs/agents.md, "From a chat app"), in claude_desktop_config.json:
                { "mcpServers": { "grooph": { "command": "npx", "args": ["-y", "grooph", "mcp", "--chat"] } } }

  --dir <project>   the project (default: CLAUDE_PROJECT_DIR, else the folder it starts in).
                    When that is the file system's root or a home folder and no --dir said
                    so, the tools return every document and write no file.
  --harness <name>  claude-code or codex, when it cannot be told from the environment
  --chat            for a chat app: only the authoring tools, and no file of yours is read
                    or written; a document goes in as an argument, and every document,
                    picture and package comes back in the reply