- Config fully from env (PLANE_API_KEY + PLANE_BASE required); remove the baked-in ludique.dev / colorcatchers / state-UUID defaults. - Fetch each project's workflow states live; cache per project per shell. Fixes the buggy group->key mapping (unstarted now maps to 'todo', not 'backlog'). Verified the dynamic map matches the old hard-coded one. - Persist the active project to $PLANE_CLI_HOME/active so `pluse` is remembered across shells; auto-resolve project name from UUID. - plsync resolves its "Everything" target dynamically (PLANE_EVERYTHING_* or by name) and builds its state map from the target's live states. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
114 lines
4.8 KiB
Markdown
114 lines
4.8 KiB
Markdown
# plane-cli
|
|
|
|
A lightweight **zsh + curl** CLI for [Plane](https://plane.so) (works with self-hosted
|
|
instances). No build step, no dependencies to install beyond what you already have — it's
|
|
a single sourced shell file that wraps the Plane REST API with `fzf` pickers.
|
|
|
|
> Personal tool, built for a self-hosted Plane instance. Inspired by
|
|
> [`cpatrickalves/plane-cli`](https://github.com/cpatrickalves/plane-cli), but deliberately
|
|
> tiny: just functions in your shell.
|
|
|
|
## Requirements
|
|
|
|
- `zsh`
|
|
- `curl`
|
|
- `python3` (used for JSON shaping — stdlib only)
|
|
- `fzf` (interactive pickers)
|
|
|
|
## Install
|
|
|
|
```sh
|
|
git clone https://forgejo.ludique.dev/theo/plane-cli.git ~/Developer/plane-cli
|
|
```
|
|
|
|
Add to your `~/.zshrc`:
|
|
|
|
```sh
|
|
export PLANE_API_KEY="plane_api_xxxxxxxxxxxxxxxx" # keep this out of version control
|
|
export PLANE_BASE="https://your-plane.example.com/api/v1/workspaces/your-workspace"
|
|
source ~/Developer/plane-cli/plane.zsh
|
|
```
|
|
|
|
Reload (`source ~/.zshrc`), then pick your project once:
|
|
|
|
```sh
|
|
pluse # fzf-pick a project — remembered across shells
|
|
```
|
|
|
|
### Configuration
|
|
|
|
Nothing instance-specific is hard-coded. The CLI reads everything from the environment and
|
|
fetches each project's workflow states live, so it works against any Plane instance.
|
|
|
|
| Env var | Required | Purpose |
|
|
|-----------------------------|----------|---------------------------------------------------------------------|
|
|
| `PLANE_API_KEY` | **yes** | Your API token (Plane → *Settings → API tokens*). |
|
|
| `PLANE_BASE` | **yes** | `https://<host>/api/v1/workspaces/<workspace-slug>`. |
|
|
| `PLANE_PROJECT` | no | Pin the active project (UUID). Otherwise use `pluse`. |
|
|
| `PLANE_PROJECT_NAME` | no | Display name for a pinned project (auto-resolved if omitted). |
|
|
| `PLANE_EVERYTHING_PROJECT` | no | Target project UUID for `plsync` (see below). |
|
|
| `PLANE_EVERYTHING_NAME` | no | Name `plsync` looks up if the UUID isn't set (default `Everything`).|
|
|
| `PLANE_CLI_HOME` | no | Config dir (default `${XDG_CONFIG_HOME:-~/.config}/plane-cli`). |
|
|
|
|
**Active project** — `pluse` (no args) opens an `fzf` picker, fetches that project's states,
|
|
and persists the choice to `$PLANE_CLI_HOME/active`, so every new shell remembers it. You can
|
|
also pass `pluse <name|identifier|id>`. State keys (`backlog/todo/progress/done/cancelled`)
|
|
are derived from Plane's state *groups*, so they map correctly on any project.
|
|
|
|
## Commands
|
|
|
|
```
|
|
pl list open issues (shows labels)
|
|
pl -a list ALL issues (incl. done/cancelled)
|
|
pl -s backlog|todo|... filter by state
|
|
pl -p urgent|high|... filter by priority
|
|
pl /text search titles containing 'text'
|
|
pl -j raw JSON output (pipe to jq); honours filters
|
|
pl -h full help
|
|
|
|
pladd create an issue (interactive wizard)
|
|
pladd "title" [opts] create inline
|
|
-p priority urgent|high|medium|low (default: none)
|
|
-s state backlog|todo|progress (default: backlog)
|
|
-d YYYY-MM-DD due date
|
|
-l a,b,c attach labels (must already exist)
|
|
|
|
plstate [#] change state — fzf picker if no id
|
|
pldone [#] mark done — fzf picker if no id
|
|
plshow fzf-pick and inspect an issue (shows labels)
|
|
pledit [#] edit title/priority/due/labels
|
|
plrm [# ...] delete — fzf multi-select if no ids
|
|
|
|
plcomment [#] [text] list comments on an issue, or add one
|
|
pllabels [add "n" [#hex]] list project labels, or create one
|
|
|
|
plprojects list all projects
|
|
pluse [name|id] switch active project (fzf if no arg)
|
|
plsync [--dry-run] mirror open issues from all projects into an "Everything" project
|
|
```
|
|
|
|
### Examples
|
|
|
|
```sh
|
|
pl -s todo # only todo issues
|
|
pl -p high # only high priority
|
|
pl /login # titles containing "login"
|
|
pl -j | jq -r '.[].name' # scriptable JSON
|
|
|
|
pladd "Fix export button" -p high -s todo -l bug,urgent
|
|
plcomment 7 "fixed in latest deploy"
|
|
pllabels add "bug" "#FF0000"
|
|
```
|
|
|
|
## Notes
|
|
|
|
- `pl -j` emits the filtered issue list as JSON for piping to `jq`.
|
|
- Comments are created via Plane's `comment_html` field (the API stores HTML).
|
|
- `plsync` mirrors every project's open issues into one "Everything" project. It resolves the
|
|
target from `PLANE_EVERYTHING_PROJECT`, or a project named `Everything` (override the name
|
|
with `PLANE_EVERYTHING_NAME`). It's idempotent — items are keyed by `[IDENTIFIER] title` and
|
|
ones closed at the source are removed. Run `plsync --dry-run` first to preview.
|
|
|
|
## License
|
|
|
|
MIT
|