# Plansmith CLI

Plan, spec, and push from your terminal. Zero dependencies, every command scriptable.



## Review documents and follow conversations [#review-documents-and-follow-conversations]

Use `plansmith discussion <operation> --file request.json --json` for threads, replies,
suggested edits and mentions. `plansmith discussion help` lists the supported operations.
`plansmith inbox list --unread --json` reads your Inbox; `inbox preferences` reads email
preferences. See [Comments and suggested edits](/docs/comments-and-suggestions) for an example
request and retry behavior.

## Install and sign in [#install-and-sign-in]

```bash
npm install -g plansmith.co
plansmith auth
```

Signing in uses the device flow: the CLI shows a short code, your browser confirms it, and the CLI
receives a token bound to the workspace you picked.

<Callout>
  The package is published as `plansmith.co`; the binary it installs is `plansmith`.
</Callout>

### Staying current [#staying-current]

Features reach this page when they ship in the product, and reach your machine when you upgrade, so
the two can drift. The CLI closes the gap itself: it reads the current version from the API and
tells you once a day when yours is behind.

```
A newer plansmith is available (0.3.0 → you have 0.2.0).
npm i -g plansmith.co
```

That line goes to **stderr** and never appears under `--json`, off a terminal, or when
`PLANSMITH_NO_UPDATE_NOTICE` is set, so it cannot get into piped data. You can always ask directly:

```bash
plansmith --version
curl -s https://api.plansmith.co/v1 | jq -r .cli.latest
```

Anything on this page that needs a particular version says so.

## Everyday commands [#everyday-commands]

```bash
plansmith use recipe-box            # pin a default project
plansmith plan list                 # plans you can see
plansmith plan tree PLAN-12         # specs, sub-plans, dependencies
plansmith spec show PLAN-12/SPEC-3
plansmith context add ./notes.md --plan PLAN-12
plansmith plan status PLAN-12 READY
plansmith push PLAN-12 --to linear
```

A plan can be named three ways: fully qualified (`recipe-box/PLAN-12`), with `--project`, or bare
once you have pinned a default with `plansmith use`.

### Adding images [#adding-images]

```bash
plansmith image add ./checkout.png --plan PLAN-12 --spec SPEC-3 --alt "Checkout screen"
plansmith image add https://example.com/flow.png --plan PLAN-12 --epic
plansmith image add ./wireframe.webp --plan PLAN-12        # stored on the plan, printed as markdown
```

PNG, JPEG, GIF or WebP up to 10 MB. With `--spec` or `--epic` the image goes at the end of that
document; without either it is stored on the plan and the command prints the `![alt](url)` markdown
to paste anywhere. A file over 4 MB uploads straight to storage, so large screenshots work too. A
URL must be public https; the server fetches it and follows no redirects. If the document changed
while the command ran, it retries once with the image it already uploaded, so a retry never stores
a second copy. See [Images in documents](/docs/images-in-documents).

## Pulling a plan to your machine [#pulling-a-plan-to-your-machine]

```bash
plansmith plan pull PLAN-12 --out plan            # epic.md, specs/SPEC-n.md, index.json
plansmith plan pull --check --out plan            # exit 0 when current, 1 when behind
plansmith plan pull --check --out plan || plansmith plan pull PLAN-12 --out plan
```

`plan pull` saves the plan as files an agent can read without parsing markdown. Each spec lands in
`specs/SPEC-n.md` exactly as it is stored, the epic in `epic.md`, and `index.json` describes all of
it as data: for each spec its title, issue tracker key, parent, what it needs first, status, readiness,
open questions, a heading `outline`, and its acceptance criteria with `id`, `text`, `checked` and
`body` (the lines written under the criterion, such as a Given/When/Then block). Read the criteria
from `index.json` rather than from the markdown: they are parsed once, by Plansmith, with the same
rules its own checks use. Without `--out` the folder is named after the plan.

Every spec and the epic carry a `revision`, a hash of what the file says. `--check` compares the
folder's revisions with Plansmith's and writes nothing: it exits `0` when they match and `1` when
they do not, listing each spec that changed, was added or was removed, a new plan name or epic,
and specs that moved to a new order. A status or readiness change alone does not count, because
the files would not change; a new issue tracker key does. `--check` reads the plan from the
folder's `index.json`, so you can leave it out, and it refuses a folder pulled from another
workspace rather than compare it with a plan that shares the handle. Add `--json` for
`{ upToDate, changes }`.

A pull replaces the folder in one step, so a reader never sees half of one. It only replaces a
folder that is empty or an earlier pull of the same plan from the same workspace. A folder of your
own files is refused, and so is the folder you are standing in. An earlier pull of another plan,
or one holding files the pull did not write (notes you or an agent added beside the specs), needs
`--force`, which deletes them. Keep your own files and any earlier version outside the folder; a
pull keeps no history. The same index comes back from
`GET /v1/projects/{project}/plans/{plan}/export?format=index` and from the MCP tool
`plansmith_export_plan` with `format: "index"`.

<Callout>
  Building a repeatable coding-agent workflow from these files? [Build a skill around a
  plan](/docs/developers/build-a-skill) covers the package layout, context and prototype pulls,
  coverage checks, fresh review loop, publication and readback.
</Callout>

## Reading and maintaining formats [#reading-and-maintaining-formats]

```bash
plansmith format show spec --project web --json
plansmith format set spec --file format.md --project web --expected-revision absent
plansmith format reset epic --project web --expected-revision <hash>
```

Omit `--project` for company scope. Reads include the effective source, direct document links,
and the destination's `overrideRevision`. Workspace owners can save and remove either spec or epic
formats at both scopes. An active workspace and write credential are required.

Use the reviewed hash with `--expected-revision`, or `absent` to create a new override. If omitted,
the CLI reads the current destination revision before writing. A conflict requires another read
and review. Reset returns the inherited format now in force. See [Formats](/docs/formats).

## Moving context around [#moving-context-around]

```bash
plansmith context add ./report.pdf --plan PLAN-12 --folder research
plansmith context add ./design.md --plan PLAN-12 --folder docs --replace
plansmith context list --plan PLAN-12                                  # ids, folders, sizes
plansmith context download --plan PLAN-12 --out plan-context.zip
plansmith context download --plan PLAN-12 --folder research --out research.zip
plansmith context copy --from PLAN-12 --to PLAN-19 --dry-run
```

`context add <file>` uploads the file itself for the types Plansmith can read as files (`.pdf`,
`.docx`, `.doc`, `.xlsx`, `.pptx`, `.csv`, `.md`, `.txt`, `.json`, `.yaml`, `.xml`, `.xsd`, `.xsl`,
`.xslt`, `.html`, `.svg`, images, and recordings as `.mp4`, `.mov` or `.webm`). Files up to 25 MB, a legacy `.doc` up to 4 MB, and a
recording up to 2 GB and 2 hours. The old 4 MB limit is gone for every other format, because a large file now
uploads straight to storage instead of through the API. Anything else that decodes as text is still
added as a note, exactly as before. An older `.xls` or `.ppt` is refused with a message naming the
format to save it as. Pass `--folder` to file it somewhere in the tree; without one it lands at the
scope root. A URL argument is fetched server-side and `-` reads stdin as a note, both unchanged.

To republish a document, add it again with `--replace`. The document with the same title in that
folder, at that scope, gets the new file and its text, and keeps its id, so nothing pointing at it
breaks. The title matches with or without its extension, so `design.md` finds a document the app
titled `design`. With no title match, a document in that folder with exactly the same bytes is the
one replaced; with neither, the file is added as usual. When two documents share the title,
`--replace` refuses and lists both ids; pass the one you mean with `--id <source id>`, which
replaces exactly that document whatever the new file is called, in its own folder. `--replace`
applies to files: a URL, stdin, or a file Plansmith adds as a note cannot be replaced this way, and
`--id` naming a note or a web page is refused. On the API it is the upload's `onConflict=replace`
field, or `replaceSourceId` for a named document.

Adding a recording prints `Transcript: pending`. The file is stored immediately; the transcript is
written in the background and appears on the source once it is ready, usually within a minute or
two. To tell "still working" from "finished", read `enrichment.status` on the source, at the scope
you added it to:

```bash
GET /v1/context/{sourceId}                                            # company
GET /v1/projects/{project}/context/{sourceId}                         # project
GET /v1/projects/{project}/plans/{plan}/context/{sourceId}            # plan
```

`status` is one of `pending`, `running`, `done`, `failed` or `skipped`, and `reason` says why on
the last two ("no usage left", "no speech was found in this recording"). ⚠ Each path only answers
for a source at ITS scope, so poll the one matching where you added the file.

`context download` writes a zip of everything that lives at the scope you name: a plan with
`--plan`, a project with `--project`, or your whole workspace with neither. That covers both the
documents you added and the files the agent wrote into the plan's workspace, such as imported pages
and its own research. Your original files come back in the folders you filed them under, alongside a
`manifest.json` describing every entry, including each document's real title; an agent-written file
is marked `"sourceKind": "AGENT_FILE"` so you can tell the two apart. It will not overwrite an
existing file unless you pass `--force`.

The archive is currently capped at 4 MB, which is what a single response can carry. A larger scope
is refused with a message naming the size, so download it in parts: `--folder <path>` takes one
folder and everything under it, and `--only <id,id>` takes the entries you name (both together
take the named entries in that folder). `context list` shows each entry's size (`sizeBytes` with
`--json`, the bytes it adds to a download), its SCOPE and its id, so you can pick parts under the
cap before asking. A plan's list also shows what it inherits from its project and the workspace,
while a download carries only what lives at the scope you name: pick ids whose SCOPE matches. Files
the agent wrote are in the download but not in the list; the zip's `manifest.json` names them. An
id that is not at the scope you named is refused and listed, never quietly left out of the zip. On
the API these are the `folder` and
`only` query parameters of every `…/context/download` route, and the `X-Context-Bytes` header
reports the content bytes a download carried.

`context copy` copies the context a plan owns onto another plan in the same project, keeping
folders, descriptions and file originals. That means both the documents you added and the files the
agent wrote into the plan's workspace, such as imported pages, its own research and the plan's
`AGENTS.md`, plus the images and attachments those imported pages reference, whose links are
re-pointed at the new plan so they open there. Inherited documents are not copied, because the
destination already sees those. Add `--dry-run` to see what would move before anything is written,
and re-run it safely: anything already on the destination is counted as skipped rather than
duplicated, and a file the destination has since changed is reported in `failed` rather than
overwritten. The one exception is a file this copy itself wrote and has since learned to write
differently: a destination file still byte-identical to the source is re-pointed in place.

The response counts the two stores apart, so you can always tell which is which: `copied` and
`skipped` are documents, `files` and `skippedFiles` are workspace files, and `folders` and
`fileFolders` are their respective folders. One copy carries at most 750 documents and 2,000
workspace files, counting the plan's context files and its attachments against the same budget;
past the document limit the request is refused with a `413` whose message names it, and a part of
the workspace that does not fit is reported in `failed` while the rest still copies.

## Instructions [#instructions]

`AGENTS.md` exists at four scopes, and the same command reaches all of them:

```bash
plansmith instructions show                          # the workspace (owner only to change)
plansmith instructions show --project recipe-box     # one project
plansmith instructions show --plan PLAN-12           # one plan
plansmith instructions show --me                     # your own, private to you

plansmith instructions set --file AGENTS.md --project recipe-box
plansmith instructions clear --me
```

The TIER comes from the flags and never from the project you pinned with `plansmith use`: writing
workspace instructions when you meant a project would change every member's planning turns. A flag
you typed but left empty is refused rather than widened.

Every read and write prints the scope **the server answered with**, not the one your flags asked
for:

```bash
$ plansmith instructions show --project recipe-box
  scope: project · recipe-box · 952 of 1,500 characters
```

If those two disagree, the line says so, and a `set` that landed on a different tier **exits
non-zero** rather than reporting success, because a document you did not aim at has already
changed. A CLI older than `0.3.0` dropped `--project` on this command entirely, so upgrade if you
see that warning.

`--plan PLAN-12` still resolves its PROJECT from the pin, as everywhere else in the CLI. Plan
handles are unique per project, so a stale pin lands on a real but different `PLAN-12`. Qualify it
as `recipe-box/PLAN-12` in anything scripted.

<Callout>
  `--me` names your own file, so it cannot be combined with `--project` or `--plan`. Those two work
  together, as everywhere else in the CLI: `--project recipe-box --plan PLAN-4`.
</Callout>

A save can succeed and still be longer than the agent reads. `show` counts characters against the
injection cap and says so when a document is clipped; `--json` carries the byte cap too.

### Version history and restore [#version-history-and-restore]

Needs `plansmith` **0.3.0** or newer.

```bash
plansmith instructions history --project recipe-box
plansmith instructions restore --at 4790 --project recipe-box [--yes]
```

`history` lists every version newest first. The **AT** column is what `restore` takes: the ledger's
own handle, not the friendly version number beside it, because version numbers are per-document and
the handle is workspace-wide.

`restore` puts an earlier version back. It states both sizes before it acts, and it is **itself
recorded as a new version**, so nothing ever leaves the record. Restoring version 2 over version 4
leaves you on a version 5 whose message reads `restored version 2`.

<Callout>
  `restore` refuses without `--yes` when there is no terminal to ask. It also refuses if the document
  changed after `history` read it, rather than overwriting someone else's edit. Re-run `history` and
  try again.
</Callout>

The same history and one-click restore are in **Settings → Instructions**, on the History tab.

## Unrecognised flags [#unrecognised-flags]

A flag this CLI does not know is **refused**, with the nearest match and the flags that command
accepts. It is never accepted and ignored:

```bash
$ plansmith instructions set --file AGENTS.md --projct recipe-box
✗ Unknown flag --projct for "instructions set". Did you mean --project?
  Accepted here: --file, --help, --json, --me, --no-color, --plan, --project
  Nothing was sent.
```

Flags belong to a specific verb, so a sibling cannot lend one: `instructions show --file x.md` is
refused too, and says `--file` belongs to `instructions set`.

<Callout>
  Upgrading from `0.2.x`: a script passing a flag the old CLI silently ignored now exits non-zero.
  That flag was never doing anything, and the exit code is the first time you are being told.
</Callout>

## Usage [#usage]

`plansmith usage` shows what each person in the workspace has used this month, in dollars of their
monthly total, and how much of it they can still move. `MOVABLE` is a share of that person's own
allowance; for the shared pool, which has no allowance to be a share of, it is money.

```bash
plansmith usage
```

```
PERSON              USED                      MOVABLE   RESETS
ana@example.com     $40 of $200 (20%)         80%       2026-10-03
bruno@example.com   $195.40 of $200 (97.7%)   2.3%      2026-10-03
shared pool         -                         $48       2027-04-18
```

Movable excludes usage that a plan currently running has already reserved.

### Moving usage between people [#moving-usage-between-people]

A workspace owner can move part of one person's remaining usage to another person, or to and from
the shared pool. `--from` and `--to` take an email, `me`, or `pool`. Say how much in dollars with
`--amount`, or as a share of what `--from` can move with `--percentage`; exactly one of the two.

```bash
plansmith usage move --from ana@example.com --to bruno@example.com \
  --amount 80 --reason "Bruno is finishing the migration plan"
```

It **quotes first and stops**: you see what each person has left before and after, when the moved
usage expires, and whether part of it clears a shortfall. Nothing moves until you add `--yes`.

```
Moving $80.00 from ana@example.com to bruno@example.com
  ana@example.com     $160.00 left  ->  $80.00 left
  bruno@example.com   $4.60 left  ->  $84.60 left
  expires 2026-10-03
Add --yes to move it. Re-run with --operation-key 8f0b… to retry.
```

An amount larger than `--from` can move is refused with the most it can move, in dollars. Monthly
usage cannot go to the shared pool, so only purchased usage moves there.

Moved usage keeps the lifetime it already had. A slice of someone's monthly allowance still expires
at the end of that period, so it does not become permanent by moving.

`--operation-key` is what makes a retry safe. The CLI prints one on every quote; pass it back and a
re-run lands on the original move instead of making a second one, including when the first
attempt committed and the response never arrived.

```bash
plansmith usage move --from ana@example.com --to pool \
  --percentage 25 --reason "Parking the remainder" \
  --operation-key 8f0b1f4e-… --yes
```

Owners only. `--amount` takes dollars and cents (`88.33`); `--percentage` takes a number between 0
and 100 with at most one decimal.

<Callout>
  Upgrading from `0.4.x`: `usage move --credits <n>` is gone. Use `--amount` (dollars) or
  `--percentage` instead. The CLI refuses the old flag, so nothing moves by accident.
</Callout>

## Scripting [#scripting]

Every command takes `--json` and prints the API response unchanged, so your script depends on the
API rather than on how the CLI formats things. Human-facing output goes to stderr, so redirecting
stdout gives you clean data.

```bash
plansmith plan tree PLAN-12 --json | jq '.specs[] | .shortId + " " + .title'
```

## CI [#ci]

```bash
PLANSMITH_KEY=psk_read_… plansmith plan list -p recipe-box --json
```

A static key skips the saved session entirely and never refreshes, so CI behaves identically on
every run. Mint one with `plansmith keys create ci --scope read`.

## Sharing [#sharing]

Sharing lives on the **top-level plan** and covers every sub-plan under it. Address a sub-plan and
the change still lands on the plan above it, so `visibility`, `add` and `rm` read the plan first and
print which plan that is before they change anything.

```bash
plansmith share status PLAN-12                    # visibility, people, live public links
plansmith share visibility PLAN-12 TEAM           # or PRIVATE
plansmith share visibility PLAN-12 PRIVATE --yes  # skip the confirmation in a script
plansmith share add PLAN-12 sam@acme.co --role EDITOR
plansmith share rm PLAN-12 <userId>
plansmith share links list|mint|revoke PLAN-12    # public prototype links
```

`share links list|mint|revoke` has no pre-flight and prints no scope sentence. ⚠ `links revoke`
closes the public prototype links on the plan you name, not across the whole tree, while the count
in `share status` counts the tree, so a sub-plan's link can survive a revoke run on its root.

<Callout>
  **`--yes` is required on a sub-plan in a script.** When the plan you named is a sub-plan, those
  three commands ask before changing the whole tree. With no terminal to ask (a cron job, CI),
  `--yes` is how you say yes in advance; without it the command refuses and exits non-zero rather
  than guessing. A top-level plan is unaffected, so existing scripts keep working.

  `--json` selects a format and does **not** imply `--yes`. Adding it to get a parseable log must not
  quietly remove the confirmation.
</Callout>

Every share command takes `--json` (`status`, `visibility`, `add`, `rm` and `links revoke`) and
prints the API response unchanged. Under `--json` nothing else reaches stdout, so a refusal goes to
stderr and leaves your parser reading an empty stream rather than half an object.

Going PRIVATE does **not** kill a public prototype link that is already out there; the response
says so, and `share links revoke` is what closes them, one plan at a time, per the note above.

## Prototypes [#prototypes]

Read a plan's prototype from the terminal, with the CLI's own sign-in: no browser session needed.

```bash
plansmith prototype status PLAN-12 --project recipe-box     # status, size, and if it is stale
plansmith prototype pull PLAN-12 --project recipe-box       # saves PLAN-12-prototype.html here
plansmith prototype pull PLAN-12 --out mock.html --force    # another name; --force overwrites
plansmith prototype pull PLAN-12 --stdout > mock.html       # the document on stdout
plansmith prototype open PLAN-12 --project recipe-box       # opens it in your browser
plansmith prototype open PLAN-12 --no-browser               # saves it and prints the path
```

`pull` gives the stored document exactly and refuses to overwrite a file without `--force`. `open`
saves a page that runs the prototype in the same sandbox the web app uses, in a folder only you can
read (on Linux under `~/Downloads`, because snap browsers cannot read `/tmp`), and always prints its
path so an agent can open it with its own browser tool. A plan you cannot read answers the same as a
plan with no prototype.

## Prototype template [#prototype-template]

The chrome every prototype is assembled into, per workspace or per project. `set` validates the
document before anything is stored and prints the findings on a refusal; `reset` prints the tier now
in force, which after a project reset is the company's when one exists.

```bash
plansmith prototype template get [--project recipe-box]
plansmith prototype template set --file template.html [--project recipe-box]
plansmith prototype template reset [--project recipe-box]
plansmith prototype rebuild PLAN-12 --project recipe-box     # re-render in the current template
```

The workspace template is owner-only, and a valueless `--project` is refused rather than widened
to the workspace.

## Environment [#environment]

```text
PLANSMITH_KEY          static API key (overrides the saved session)
PLANSMITH_API_URL      default https://api.plansmith.co
PLANSMITH_AUTH_URL     default https://www.plansmith.co
PLANSMITH_MCP_URL      default https://mcp.plansmith.co
PLANSMITH_CONFIG_DIR   default ~/.config/plansmith
```

<Callout>
  Pointing the CLI at a local server needs the `/api` segment:
  `PLANSMITH_API_URL=http://localhost:3000/api`. The `api.plansmith.co` host rewrites `/v1` onto it
  for you, and nothing else does.
</Callout>
