planka-cli
AGENTS.md18 starsChanged 7 months agoMITView on GitHub
Rust CLI (plnk), SDK (plnk-core), and experimental TUI (plnk-tui) for Planka kanban project management. Deterministic, scriptable, hierarchy-aware, machine-readable. Usable by humans, shell scripts, CI/CD, and AI agents. clap (derive), reqwest, tokio, serde/serde_json, thiserror, tracing, toml, dirs, comfy-table, async-trait, url, rpassword. Planka uses an X-API-Key header (not Bearer, not Authorization). Credential precedence: Set credentials interactively: <TOKEN> is a positional arg and will appear in shell history; read it from a secret store or prefix the command with a space if your…
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,774 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
The file
We split this AGENTS.md into the rules it gives an agent
2 rules. About 0 are common, 2 are distinctive, and 0 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 37
Stdout is data; stderr is logs and errors: Always.
- Distinctive: line 96
All verified against a live server on 2026-08-10.
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
planka-cli
Rust CLI (plnk), SDK (plnk-core), and experimental TUI (plnk-tui) for Planka kanban project management. Deterministic, scriptable, hierarchy-aware, machine-readable. Usable by humans, shell scripts, CI/CD, and AI agents.
Workspace
crates/
plnk-core/ library: HTTP client, domain models, API traits, auth, typed errors. Standalone SDK.
plnk-cli/ binary `plnk`: clap command tree, output rendering (table/JSON/markdown), input handling.
plnk-tui/ binary `plnk-tui`: experimental terminal UI with live websocket sync.Build & Test
cargo check # compile check
cargo clippy -- -D warnings # lint (zero-warnings policy)
cargo fmt --check # format check
cargo test # all tests
cargo run -- --help # run the CLI
cargo run -p plnk-tui # run the TUICore Design Rules
- Grammar:
plnk <resource> <action> [target] [flags] - Hierarchy:
project → board → list → card → task/comment - IDs are opaque strings:
type ResourceId = String;— no parsing, noi64, nou32. getis ID-only,findis scoped search — never mix.getwith a non-ID fails validation; it does not fall back to searching.- All searches must be scoped.
findrequires--list,--board, or--project. Sole exception:project findis unscoped (projects are root). - Three-tier matching: exact case-sensitive → case-insensitive → substring. Stop at the first tier with results.
- Traits define API capabilities.
ProjectApi,BoardApi,CardApi, etc. The CLI depends on traits, not implementations. Today's impl isPlankaClientV1. - Domain models own the truth. Private wire response structs map to public domain models; wire-format changes stay isolated.
- JSON output is a strict projection of serde. Trimmed JSON is a subset of the full serde representation with identical keys, types, and nulls. Never translate field names or coerce types for output.
Tabular::trimmed_columnsreturns(serde_field, display_label)pairs; labels exist for tables/markdown only and must never leak into JSON. - Errors are data.
PlankaErrorenum with typed variants, exit codes, and JSON-renderable error types. Nounwrap()in library code. Nopanic!outside tests. - Stdout is data; stderr is logs and errors. Always.
- No
anyhow. Typed errors are required for exit codes and structured JSON output.
Exit Codes
| Code | Meaning |
| 0 | success |
| 2 | invalid arguments / validation |
| 3 | auth failure |
| 4 | not found |
| 5 | remote API / server error |
Key Dependencies
clap (derive), reqwest, tokio, serde/serde_json, thiserror, tracing, toml, dirs, comfy-table, async-trait, url, rpassword.
Auth
Planka uses an X-API-Key header (not Bearer, not Authorization).
Credential precedence:
- CLI flags:
--server,--token - Environment:
PLANKA_SERVER,PLANKA_TOKEN - Config file:
~/.config/plnk/config.toml— same on every OS, honoringXDG_CONFIG_HOMEandPLANKA_CONFIGoverrides (mode0600)
Set credentials interactively:
plnk auth login # email + password → stored token
plnk auth token set <TOKEN> --server <URL> # store an existing API token
plnk auth status # show active credential source
plnk auth whoami # verify against the server
plnk auth logout # remove stored credentials<TOKEN> is a positional arg and will appear in shell history; read it from a secret store or prefix the command with a space if your shell honors HIST_IGNORE_SPACE.
Planka API Quirks
Real-world wire format differences worth knowing when contributing:
- List creation requires
"type": "active". - Card creation requires
"type": "project". - Board creation requires
"type": "kanban". - Position values are powers of 2 starting at
65536. GET /api/boards/{id}returns a nestedincludedobject with lists, cards, tasks, labels, memberships, and users.- Due queries exclude closed cards.
card list/card findwith--due-afteror--due-beforedrop cards withisClosed: trueunless--include-closedis passed.--closedkeeps only closed cards, and conflicts with--include-closed. A listing without a due bound still returns closed cards. - There is no server-side filter by card due date.
card find --due-after/--due-beforefilter client-side over the fetched snapshot, comparing thedueDateISO 8601 string as a UTC instant (strict<, nulls excluded when bounded). Do not invent a query parameter for it. - Tasks live inside task lists (
card → task list → task).POST /api/task-lists/{id}/tasksis the only way to create one.GET /api/task-lists/{id}exists and returnsitemplusincluded.tasks. A task list'sshowOnFrontOfCarddefaults totrueserver-side andhideCompletedTaskstofalse; omit them on create unless you want the non-default. Verified 2026-09-12.
Custom fields
All verified against a live server on 2026-08-10. Several contradict Planka's published OpenAPI spec — believe this list, not the spec.
- Values are addressed by a composite path segment, not ordinary path params:
/api/cards/{c}/custom-field-values/customFieldGroupId:{g}:customFieldId:{f} - The published spec writes
customFieldId:${customFieldId}with a literal$. That is a documentation bug. Sending the$returns400 E_MISSING_OR_INVALID_PARAMS— the server reads it as part of the id and fails a length check. - The published spec spells the DELETE route singular (
custom-field-value). The live route is plural, same as PATCH. The singular path returns a bare{"code":"E_NOT_FOUND"}with no message — Sails for "no such route" — as opposed to the plural route's{"code":"E_NOT_FOUND","message":"Custom field value not found"}. contentis capped at 512 characters. 512 returns200, 513 returns400. Validate client-side so callers get exit2instead of a server400.- An empty string is rejected.
PATCH {"content": ""}returns400 … Cannot use '' (empty string) for a required input. Clearing a value must use DELETE, never a PATCH to empty. - DELETE returns the deleted value in an
itemenvelope, not204. - The server may rewrite a card group's
position. A create sent65536came back as81920because a sibling already held the slot. Never assert the echo. - There is no endpoint that filters cards by custom field value. Filtering is client-side over a snapshot. Do not invent a query parameter for it.
- There is no
GET /api/base-custom-field-groups/{id}. That path falls through to the SPA and returns HTML with200— worse than a 404, because a JSON client fails with a confusing parse error.PATCHandDELETEon the same prefix do exist. Base groups are readable only throughGET /api/projectsorGET /api/projects/{id}, underincluded.baseCustomFieldGroups. - A base group's fields are not in the board snapshot.
GET /api/boards/{id}→included.customFieldscarries only board- and card-level group fields. Base group fields appear only under the projects endpoints. - A card group adopted from a base group has
name: nulland no fields of its own. The display name lives on the base group viabaseCustomFieldGroupId, andGET /api/custom-field-groups/{adoptedId}returnsincluded.customFields: []. Any name-based resolution must fall back through the base group for both, or it will match nothing for every template-adopted group — which is the common case. POST /api/projects/{id}/base-custom-field-groupsacceptspositionbut omits it from the response. The base-group model must not require the field.showOnFrontOfCardis honoured on create and is what makes a value visible on the card face in the web UI. Planka defaults it tofalse.
Project Management
Task tracking lives in a Planka instance on the planka-cli project. Board and list names are not fixed — inspect actual state before acting. Do not assume milestone boards or canonical columns (Backlog, In Progress, etc.) exist.
Agents operating on Planka via plnk should use skills/plnk-cli/SKILL.md as the canonical guide. It covers scope rules, name-to-ID resolution, workflow-intent mapping, and safe mutation etiquette.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

