Skip to content

MaudeCode/plane-cli

Repository files navigation

plane-cli

Machine-friendly TypeScript CLI for Plane.so, designed for LLM agents and automation scripts.

The CLI binary is plane-cli.

This project is not affiliated with Plane.

Note

This project is built for LLM agents and was developed with substantial LLM assistance. Its primary interface is stable JSON for automation, not interactive human CLI ergonomics.

Install

bun install
bun run build

Run locally after building:

./dist/plane-cli --help
./dist/plane-cli project list --json

During development:

bun src/index.ts project list --json

Plane API Model

Plane's public API is REST. Plane Cloud uses:

https://api.plane.so/

Self-hosted Plane instances can use a custom baseUrl per workspace. The CLI authenticates with X-API-Key for personal access tokens or OAuth bearer tokens from a Plane app. Secrets are kept out of JSON output.

Multi-Workspace Auth

The CLI searches these config files, in order:

.plane-cli.yaml
.plane-cli.json
~/.config/plane-cli/config.yaml
~/.config/plane-cli/config.json

YAML example:

defaultWorkspace: personal

workspaces:
  personal:
    workspaceSlug: my-team
    apiKey: plane_api_xxx

  selfhosted:
    workspaceSlug: engineering
    baseUrl: https://plane.example.com
    apiKey: plane_api_yyy

  enterprise-oauth:
    workspaceSlug: engineering
    baseUrl: https://plane.example.com
    auth:
      type: oauth
      flow: client_credentials
      clientId: plane_oauth_client_id
      clientSecret: plane_oauth_client_secret
      appInstallationId: plane_app_installation_id
      accessToken: plane_oauth_access_token

Namespaced environment variables may override config API keys:

export PLANE_API_KEY_PERSONAL="plane_api_xxx"
export PLANE_WORKSPACE="personal"

Workspace names are uppercased and non-alphanumeric characters become underscores. For example, engineering-prod maps to PLANE_API_KEY_ENGINEERING_PROD.

Repo Workspace Hint

To bind a repository checkout to a configured Plane workspace, add a dotfile at the repo root:

printf "personal\n" > .plane-cli-workspace

To also bind issue commands to a default Plane project, use YAML:

workspace: personal
project: Web

The project hint is used as the default --project for project-scoped commands. An explicit --project flag always wins.

Login With API Key

Add or update a workspace API key:

plane-cli auth api-key \
  --workspace personal \
  --api-key "$PLANE_API_KEY" \
  --default \
  --json

For self-hosted Plane:

plane-cli auth api-key \
  --workspace selfhosted \
  --base-url https://plane.example.com \
  --api-key "$PLANE_API_KEY" \
  --json

Login With a Plane OAuth App

For self-hosted Plane Enterprise, create a Plane app in the workspace integrations UI and use the instance URL as --base-url.

For LLM agents and automation, use the bot/app-installation flow:

plane-cli auth oauth bot \
  --workspace zoo \
  --base-url https://plane.thezoo.house \
  --client-id "$PLANE_OAUTH_CLIENT_ID" \
  --client-secret "$PLANE_OAUTH_CLIENT_SECRET" \
  --app-installation-id "$PLANE_APP_INSTALLATION_ID" \
  --default \
  --json

For a Linear-style browser login as your user, register this redirect URI in the Plane app first:

http://127.0.0.1:8717/callback

Then run:

plane-cli auth oauth login \
  --workspace zoo \
  --base-url https://plane.thezoo.house \
  --client-id "$PLANE_OAUTH_CLIENT_ID" \
  --client-secret "$PLANE_OAUTH_CLIENT_SECRET" \
  --default \
  --json

If you need a different callback URL, pass --redirect-uri and make sure the same URL is configured in the Plane app. The CLI discovers the Plane workspaceSlug during auth setup and stores it in config. --workspace-slug exists only as an override for unusual server responses.

JSON Contract

Successful JSON output:

{
  "ok": true,
  "workspace": "personal",
  "data": {}
}

Error JSON output:

{
  "ok": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Missing required flag --project",
    "details": {}
  }
}

Commands

auth api-key --workspace name --api-key plane_api_... [--base-url url] [--workspace-slug slug] [--default]
auth oauth bot --workspace name --base-url url --client-id id --client-secret secret --app-installation-id id [--workspace-slug slug] [--default]
auth oauth login --workspace name --base-url url --client-id id --client-secret secret [--redirect-port 8717] [--redirect-uri uri] [--scope scope] [--workspace-slug slug] [--default]
auth oauth code --workspace name --base-url url --client-id id --client-secret secret --redirect-uri uri --code code [--workspace-slug slug] [--default]
auth oauth url --base-url url --client-id id --redirect-uri uri [--scope scope] [--state state]
config show
config path
workspace current [--workspace name]
workspace validate [--workspace name]
user me
member list
project list [--fields fields] [--per-page 100] [--order-by field]
project get <project>
project create --name name [--identifier KEY] [--description text]
project update <project> [--name name] [--description text] [--identifier KEY]
project archive <project> --confirm
project unarchive <project>
project delete <project> --confirm
issue list --project project [--state id] [--assignee id] [--label id] [--fields fields] [--per-page 100]
issue search --query text [--project project] [--limit 10] [--workspace-search]
issue advanced-search [--query text] [--project project] [--filters-json json | --filters-file file] [--limit 10] [--workspace-search]
issue get <KEY-123 | issue-id> [--project project]
issue create --title title [--project project] [--description markdown] [--priority urgent|high|medium|low|none] [--state state-id] [--assignee user-id] [--label label-id] [--parent issue-id]
issue update <issue> --project project [--title title] [--description markdown] [--priority priority] [--state state-id] [--assignee user-id] [--label label-id]
issue delete <issue> --project project --confirm
issue type-schema --project project [--include members,labels] [--type-id type-id]
issue type-list --project project
issue link list <issue> --project project
issue link get <link-id> <issue> --project project
issue link create <issue> --project project --url url [--title title]
issue link update <link-id> <issue> --project project [--url url] [--title title]
issue link delete <link-id> <issue> --project project --confirm
issue attachment list <issue> --project project
issue attachment get <attachment-id> <issue> --project project
issue attachment request-upload <issue> --project project --name filename --size bytes [--type mime]
issue attachment complete <attachment-id> <issue> --project project
issue attachment upload <issue> --project project --file path [--name filename] [--type mime]
issue attachment update <attachment-id> <issue> --project project [--name filename] [--type mime]
issue attachment delete <attachment-id> <issue> --project project --confirm
state|label|module|cycle|page list --project project
state|label|module|cycle|page create --project project --name name [resource flags]
state|label|module|cycle|page update <id> --project project [resource flags]
state|label|module|cycle|page delete <id> --project project --confirm
cycle|module add-item <container-id> <issue-id> --project project
cycle|module remove-item <container-id> <issue-id> --project project
cycle|module items <container-id> --project project
comment list <issue> --project project
comment create <issue> --project project --body markdown
comment update <comment-id> <issue> --project project --body markdown
comment delete <comment-id> <issue> --project project --confirm

Aliases include issue, issues, wi, work-item; project, projects; state, states; label, labels; module, modules; cycle, cycles; and list/ls.

Development

bun run test
bun run build
bun run lint
npm pack --dry-run

About

Machine-friendly TypeScript CLI for Plane.so.

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors