Plansmith CLI
Plan, spec, and push from your terminal. Zero dependencies, every command scriptable.
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 for an example
request and retry behavior.
Install and sign in
npm install -g plansmith.co
plansmith authSigning 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.
The package is published as plansmith.co; the binary it installs is plansmith.
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.coThat 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:
plansmith --version
curl -s https://api.plansmith.co/v1 | jq -r .cli.latestAnything on this page that needs a particular version says so.
Everyday commands
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 linearA 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
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 markdownPNG, 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  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.
Pulling a plan to your machine
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 planplan 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".
Building a repeatable coding-agent workflow from these files? Build a skill around a plan covers the package layout, context and prototype pulls, coverage checks, fresh review loop, publication and readback.
Reading and maintaining formats
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.
Moving context around
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-runcontext 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:
GET /v1/context/{sourceId} # company
GET /v1/projects/{project}/context/{sourceId} # project
GET /v1/projects/{project}/plans/{plan}/context/{sourceId} # planstatus 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
AGENTS.md exists at four scopes, and the same command reaches all of them:
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 --meThe 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:
$ plansmith instructions show --project recipe-box
scope: project · recipe-box · 952 of 1,500 charactersIf 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.
--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.
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
Needs plansmith 0.3.0 or newer.
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.
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.
The same history and one-click restore are in Settings → Instructions, on the History tab.
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:
$ 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.
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.
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.
plansmith usagePERSON 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-18Movable excludes usage that a plan currently running has already reserved.
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.
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.
plansmith usage move --from ana@example.com --to pool \
--percentage 25 --reason "Parking the remainder" \
--operation-key 8f0b1f4e-… --yesOwners only. --amount takes dollars and cents (88.33); --percentage takes a number between 0
and 100 with at most one decimal.
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.
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.
plansmith plan tree PLAN-12 --json | jq '.specs[] | .shortId + " " + .title'CI
PLANSMITH_KEY=psk_read_… plansmith plan list -p recipe-box --jsonA 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 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.
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 linksshare 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.
--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.
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
Read a plan's prototype from the terminal, with the CLI's own sign-in: no browser session needed.
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 pathpull 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
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.
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 templateThe workspace template is owner-only, and a valueless --project is refused rather than widened
to the workspace.
Environment
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/plansmithPointing 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.