@plany/cli
v1.0.0
Published
Plany CLI: browser-based login, project picker, document and work item commands, memory MCP server
Maintainers
Readme
@plany/cli
The Plany command line. It signs you in through the browser, binds a directory to a Project, and reads and changes Work Items, note documents, Drive files and project memory. It also runs the memory MCP server for coding agents.
Bins
plany: the CLI. The command reference below is generated from the command table insrc/command-table.ts, the same tableplany helpprints.plany-mcp: the memory MCP server on stdio, for Cursor, Claude Code and OpenCode. Editor configs point at it directly.
Install
npm i -g @plany/cli
# or: pnpm add -g @plany/cliThe package is a self-contained bundle with no runtime dependencies.
First-time setup
plany login # opens the web app; you click Authorize and the CLI stores the token
plany init # pick a Project for this directory
plany status # show the stored credentials and the bindingplany login checks the new token with the deployment before it stores it in
~/.plany/credentials.json.
Choosing a Project
Every command that works inside a Project takes --project SELECTOR. A
selector is a Project ID, an exact name, or a unique part of a name. The
deployment resolves it across all your workspaces, so a command works from any
directory. Without --project, a command uses the Project that plany init
bound to the current directory. With neither, it fails and says how to pick
one.
Work Item keys (BIE-42), document IDs and file IDs are found in any of your
workspaces. A binding only supplies the default Project. With --project, the
item must be in that Project.
Output and exit codes
--json prints the deployment's answer as JSON, unchanged. Use it whenever a
program reads the output.
Exit codes: 0 on success, 1 when the command failed (including an edit
conflict), 2 when the command line was wrong. Errors print as
error: <message>.
Setup
plany login # Sign in through the browser. Stores the token in ~/.plany/credentials.json.
plany logout # Forget the stored token.
plany whoami [--json] # Show the signed-in user and their workspaces.
plany init [--project SELECTOR] [--json] # Bind this directory to a Project and pull its memory.
plany status # Show the stored credentials, this directory's binding, and the last memory sync.
plany skill # Install the Plany agent skill into .claude/skills/.
plany update # Update the CLI to the latest version.
plany version # Print the CLI version.
plany help [COMMAND] # Show help for every command, or for one command or group.init: Without--project,initlists your Projects and asks you to pick one.
Workspace
plany projects [--json] # List the Projects you can access, across workspaces.
plany members [--project SELECTOR] [--json] # List the Project team's members and the workspace's pending invites.
plany teams [--project SELECTOR] [--json] # List the teams you can see in the Project's workspace.
plany labels [--project SELECTOR] [--json] # List the labels in the Project's workspace.members: These are the peopleitems assignaccepts, by email or display name.labels: These are the namesitems labelanditems create --labelaccept.
members and labels list the values items assign and items label
accept. Look them up first instead of guessing.
Work Items
plany items list [--status STATUS] [--project SELECTOR] [--json] # List the Project's Work Items.
plany items mine [--limit N] [--project SELECTOR] [--json] # List open Work Items assigned to you and open items in your personal space.
plany items show KEY [--project SELECTOR] [--json] # Show a Work Item and its description as Markdown.
plany items search QUERY [--limit N] [--project SELECTOR] [--json] # Search Work Item titles in the Project.
plany items create TITLE [--type TYPE] [--status STATUS] [--priority P0..P4] [--description TEXT | --description-file PATH|-] [--assignee USER]... [--label LABEL]... [--due DATE[ TIME]] [--parent KEY] [--project SELECTOR] [--json] # Create a Work Item in the Project.
plany items update KEY [--title TITLE] [--type TYPE] [--project SELECTOR] [--json] # Change a Work Item's title, type, or both.
plany items edit KEY [--file PATH|-] [--base-version N | --overwrite] [--allow-lossy] [--dry-run] [--project SELECTOR] [--json] # Edit a Work Item description as Markdown.
plany items set KEY STATUS [--project SELECTOR] [--json] # Set a Work Item's status.
plany items assign KEY (--to USER... | --me | --clear) [--project SELECTOR] [--json] # Replace a Work Item's assignees.
plany items priority KEY P0..P4 [--project SELECTOR] [--json] # Set a Work Item's priority.
plany items due KEY (DATE [TIME] | --clear) [--project SELECTOR] [--json] # Set or clear a Work Item's due date, optionally with a time.
plany items label KEY [--add LABEL]... [--remove LABEL]... [--project SELECTOR] [--json] # Add or remove Work Item labels.
plany items parent KEY (PARENT_KEY | --clear) [--project SELECTOR] [--json] # Put a Work Item under a parent in the same Project, or detach it.
plany items move KEY --to PROJECT [--project SELECTOR] [--json] # Move a Work Item to another Project.
plany items comments KEY [--limit N] [--project SELECTOR] [--json] # List a Work Item's comments.
plany items comment KEY (BODY | --file PATH|-) [--project SELECTOR] [--json] # Comment on a Work Item.items mine:items minecovers every workspace;--projectlimits it to that Project's workspace.items create: The description is Markdown.items edit: Pass--file PATH(or--file -for stdin) with--base-version Nor--overwrite.items edit: Without--filethe command opens$VISUALor$EDITORon the current text and merges against the version it opened.items edit:--base-version Nis theversionfromitems show KEY --json. The deployment merges your text with every change made since that version, block by block.items edit: A block changed both by you and by someone else since your base is a conflict: nothing is written and the command exits 1. Re-read, reapply your change, and retry against the new version.items edit:--overwritereplaces the current text without merging.items edit: An edit that replaces a block holding formatting Markdown cannot express is refused unless--allow-lossyis passed. Blocks you leave alone keep their formatting.items edit:--dry-runreports the merge without writing it.items edit: Markdown input is limited to 500000 characters.items assign:--toand--mecombine. USER is an email or display name fromplany members.items due: DATE is YYYY-MM-DD and TIME is HH:MM, read in this machine's time zone like the web picker. A date alone means the whole day.items move:--totakes a Project selector, like--project.
CLI edits write the same activity events as web edits, so they show in the activity feed, and new assignees get the usual inbox notification.
Documents
plany docs list [--limit N] [--cursor CURSOR] [--project SELECTOR] [--json] # List one page of the Project's documents.
plany docs show DOCUMENT [--project SELECTOR] [--json] # Print a note document as Markdown.
plany docs create TITLE [--file PATH|-] [--personal] [--project SELECTOR] [--json] # Create a note document, optionally from Markdown.
plany docs edit DOCUMENT [--file PATH|-] [--base-version N | --overwrite] [--allow-lossy] [--dry-run] [--project SELECTOR] [--json] # Edit a note document as Markdown.docs list: PassnextCursorfrom--jsonoutput to--cursorto read the next page.docs show: DOCUMENT is a document ID, or an exact title in the Project. An ID works from any directory.docs create:--personalmakes the document visible only to you.docs edit: Pass--file PATH(or--file -for stdin) with--base-version Nor--overwrite.docs edit: Without--filethe command opens$VISUALor$EDITORon the current text and merges against the version it opened.docs edit:--base-version Nis theversionfromdocs show DOCUMENT --json. The deployment merges your text with every change made since that version, block by block.docs edit: A block changed both by you and by someone else since your base is a conflict: nothing is written and the command exits 1. Re-read, reapply your change, and retry against the new version.docs edit:--overwritereplaces the current text without merging.docs edit: An edit that replaces a block holding formatting Markdown cannot express is refused unless--allow-lossyis passed. Blocks you leave alone keep their formatting.docs edit:--dry-runreports the merge without writing it.docs edit: Markdown input is limited to 500000 characters.
Files
plany files list [--folder PATH] [--limit N] [--cursor CURSOR] [--project SELECTOR] [--json] # List one page of the Project's Drive files.
plany files search QUERY [--limit N] [--project SELECTOR] [--json] # Search Drive file names in the Project.
plany files show FILE [--project SELECTOR] [--json] # Show a file's metadata and download URL.
plany files download FILE [--output PATH] [--project SELECTOR] [--json] # Save a file's current version. Never overwrites a local file.
plany files upload PATH [--folder FOLDER] [--replace FILE] [--project SELECTOR] [--json] # Upload a local file to the Project's Drive.
plany files mkdir PATH [--project SELECTOR] [--json] # Create a Drive folder and any missing parents.
plany files move FILE --to FOLDER [--project SELECTOR] [--json] # Move a Drive file to another folder.
plany files link FILE KEY [--project SELECTOR] [--json] # Attach a Drive file to a Work Item.
plany files unlink FILE KEY [--project SELECTOR] [--json] # Detach a Drive file from a Work Item.files list: Without--folderit lists every file in the Project; with it, the files directly in that folder. Folder paths look like/or/Specs/2026.files list: PassnextCursorfrom--jsonoutput to--cursorto read the next page.files show: FILE is a file ID fromfiles list, or the file's path in the Project (/Specs/plan.pdf). An ID works from any directory.files download: The file lands in the current directory under its own name unless--outputis given.files upload:--foldercreates any missing folders.files upload:--replace FILEstores the upload as a new version of that file, in its folder.
Drive files are separate from note documents and from Work Item attachments.
Memory
plany memory pull [--project SELECTOR] [--json] # Write the Project's memory to the mirror file your editor loads.
plany memory mcp # Run the memory MCP server on stdio. Same as `plany-mcp`.Editor config (Claude Code, Cursor and OpenCode use this shape):
{
"mcpServers": {
"plany-memory": { "command": "plany-mcp" }
}
}The server runs in a bound directory and works on that Project. Its tools:
create_memory { title, body } # Create a private memory in the bound Project. The user promotes it to shared in the Plany web app.
update_memory { key, title?, body? } # Change the title or body of a memory you wrote. `key` is its publicKey, such as MEM-Kabc1-001.
search_memories { query, limit? } # Search memory bodies. Use it when the auto-loaded mirror file does not hold what you need.The mirror file is the agent's main read surface. Claude Code, Cursor rules and OpenCode load it on their own. The tools are for writing back and for searching past the mirror.
Agent skill
The package ships an agent skill (skills/plany/SKILL.md) that teaches coding
agents these commands. plany init installs it into .claude/skills/ when the
repo has .claude/ or CLAUDE.md. plany skill installs or refreshes it.
The skills/ directory follows the skills.sh layout, so
other agents (Cursor, Codex, OpenCode) can install it from the package:
npx skills add ./node_modules/@plany/cli # or the global install pathUpdating
Commands read a cached latest version from ~/.plany/version.json and never
wait on the registry. A detached background process refreshes a stale cache.
The notice shows in TTY sessions only; --json and MCP runs stay silent. Set
PLANY_NO_UPDATE_CHECK=1 to turn it off.
A deployment that has moved to a newer protocol answers upgrade_required,
and the CLI tells you to run plany update.
Protocol
Each command is one POST {deployment}/cli/v2/<command id> with a bearer
token and the command's input as JSON. The contract lives in
packages/backend/src/cli-contract and is bundled into the CLI. The CLI checks
every answer against the command's output validator, so a CLI and a deployment
that disagree fail on the first call.
Environment
| Variable | Default | Purpose |
| ----------------------- | ------------------------------- | --------------------------------------------------- |
| PLANY_WEB_URL | https://plany.bielcrystal.app | Web app that plany login opens. Override for dev. |
| PLANY_NO_UPDATE_CHECK | unset | Any value turns off the update notice. |
Local files
~/.plany/credentials.json {"apiUrl": "...", "token": "plany_pat_..."}
<cwd>/.plany/config.json Project binding (projectId, workspaceId, mirrorTarget).
<cwd>/.plany/memory.md Memory mirror (or wherever mirrorTarget points).The credentials file has mode 0600. Revoke a device's token in the Plany web
app under Settings → API Tokens.
