CLI
prr does everything the web app does, from a terminal. It’s a single
executable that runs under Node or Bun.
Install
Build it from the repository:
cd packages/cli
bun run build # → dist/prr.mjs
npm link # makes `prr` available everywhere
Or build a standalone binary with the runtime embedded:
bun run build:compile # → dist/prr
Sign in
prr login
This uses the device flow: it prints a code, opens your browser, and once you
approve it mints a long-lived API key and saves it to
~/.projectrr/config.json, written 0600 inside a 0700 directory so only
your account can read it. prr logout clears the key and revokes it
server-side using the web URL recorded at login, so a copied config file stops
working too. A key supplied through PRR_API_KEY belongs to your CI, not to
the config file — prr logout leaves it alone.
Set a default organization so you can stop typing --org everywhere:
prr org use greengage
In CI
Set PRR_API_KEY instead of signing in. It overrides anything stored in the
config file, and is never written to disk — commands that do save config (such
as prr org use) strip it back out:
export PRR_API_KEY="$PROJECTRR_API_KEY" # from Settings → API Keys
prr issue list --product=ecosmart --project=web-app
Mint the key from Settings → API Keys in the web app and keep it in your
runner’s secret store. prr login --api-key prompts for your Projectrr URLs and
a key and saves them to the config file instead, which is the wrong trade on a
shared runner. PRR_API_URL overrides the API endpoint if you’re pointing at a
self-hosted instance.
Everyday commands
# What's on the board
prr status --product=ecosmart --project=web-app
prr issue list --product=ecosmart --project=web-app
prr issue list --product=ecosmart --project=web-app --all # including finished work
# What's yours, across the whole organization
prr issue mine # assigned to you
prr issue mine --relation=created # raised by you
prr issue mine --relation=either --all # both, including finished work
# Who raised what, on one project
prr issue list --product=ecosmart --project=web-app --created-by=<userId>
# Create and move work
prr issue create "Fix the login redirect" --product=ecosmart --project=web-app
prr issue view 142 --product=ecosmart --project=web-app
prr issue update 142 --product=ecosmart --project=web-app --status="In Progress"
# Specs are markdown documents, not comments
prr issue spec set 142 --product=ecosmart --project=web-app --spec-file=./spec.md
prr issue spec get 142 --product=ecosmart --project=web-app
# What is stuck, across the whole organization
prr attention
# Find things
prr search "login redirect"
prr activity --limit=20
prr report
Epics and the journal
prr epic list --product=ecosmart
prr epic view passwordless-auth --product=ecosmart
# Read only what is still outstanding
prr epic view passwordless-auth --product=ecosmart --open
# Write back what you decided
prr epic note add passwordless-auth --product=ecosmart \
--kind=decision --body="Magic link + passkey only. No passwords."
# Close a blocker with an explanation — never edit the original
prr epic note resolve <noteId> --body="Registered client_id up front; device flow returns 200."
See Epics for what the note kinds mean.
Issue comments, blockers and risks
An issue’s comments carry the same kinds as an epic’s journal, so a blocked issue is something the tooling can count rather than prose someone has to read:
prr comment list 142 --product=ecosmart --project=web-app
prr comment list 142 --product=ecosmart --project=web-app --open # just what's outstanding
prr comment list 142 --product=ecosmart --project=web-app --kind=decision
# Say it out loud, in the thread
prr comment create 142 --product=ecosmart --project=web-app \
--kind=blocker --body="Waiting on the DNS migration."
# ...and say what it is waiting on, as links
prr comment create 142 --product=ecosmart --project=web-app \
--kind=blocker --body="Blocked until the auth rewrite lands." \
--blocked-by=issue:<issueId>,epic:<epicId>,https://vendor.example/ticket/88
# Reply to a comment rather than starting a new thread
prr comment create 142 --product=ecosmart --project=web-app \
--reply-to=<commentId> --body="Ticket raised with the registrar."
# Close it with an explanation — recorded as a reply, never an edit
prr comment resolve <commentId> --body="DNS cut over last night."
prr comment reopen <commentId>
--kind takes comment (the default), update, decision, blocker,
context or risk.
--blocked-by takes a comma-separated list of issue:<uuid>, epic:<uuid> and
http(s) URLs, and works on prr comment create/update and prr epic note add/update. It replaces the note’s whole list, so send everything you want to
keep — and --blocked-by= with nothing after it clears the links. Referenced
issues and epics must be in the same organization. See
Epics.
prr comment list prints each comment’s kind and whether
it is resolved, indents replies under their parent, appends
[blocked on: ...] to any note carrying links, and — when anything is still
open — ends with Outstanding: N blocker(s), N risk(s), counted across the whole
issue so a filtered read still tells you what’s outstanding. resolve takes
--body or --body-file like create and update do, and prr comment resolve is always the right move over prr comment delete: deleting a comment
takes its replies with it.
What is stuck
prr attention is the organization-wide version of that question: one list of
every epic and issue carrying an unresolved blocker or risk, worst first —
the same rollup as the dashboard’s Needs attention panel.
prr attention # the ten worst, in your default org
prr attention --org=greengage --limit=25 # up to 50
Each row prints a ref (WEB-142, or epic:passwordless-auth), the title, where
it lives (ecosmart/web-app, or just the product for an epic), how many blockers
and risks are open, and what they are blocked on — enough to type the prr issue view or prr epic view that comes next. The blocked-on cell is deduped
and shows at most four, with +N for the rest. It counts only unresolved, top-level blockers and risks,
and leaves out finished work: done or cancelled epics, and issues in a done
or closed status. The list is capped, and says so when it comes back full
rather than pretending to be everything.
Command groups
| Group | What it covers |
|---|---|
prr org |
Organizations, members, roles, invitations |
prr product |
Products |
prr project |
Projects |
prr status |
Board overview, statuses, templates, ordering |
prr issue |
Issues, specs, watchers |
prr comment |
Issue comments, including blockers and risks |
prr epic |
Epics, members, issue links, the note journal |
prr rules |
Workflow rules and their execution log |
prr repo |
Linked GitHub repositories |
prr release |
Releases |
prr trello |
Trello credentials, board links, list mappings, import |
prr attention |
Everything stuck: open blockers and risks, org-wide |
prr search, report, activity |
Reading across everything |
prr help prints the full list, and each group has its own help
(prr epic help, prr org help).
Conventions
- Reads take slugs, writes take IDs.
prr issue list --product=ecosmart --project=web-appuses slugs; commands that create things generally want the UUID, whichprr ... viewandprr ... listprint. - Issues can be addressed by number with
--productand--project, or by UUID on its own. - Creator, assignee and owner are different people.
--created-by=<userId>filters a list to who raised an issue, not who is doing it or who is accountable for it — see Core concepts. User IDs come fromprr org assignees. - Finished work is hidden unless you ask for it.
prr issue mineandprr issue listboth leave out anything in adoneorclosedstatus until you pass--all— see Core concepts. - Flags take either form.
--product=ecosmartand--product ecosmartare the same thing. - Org scope comes from
--org=<slug>or the default set byprr org use. - Config lives at
~/.projectrr/config.json,0600in a0700directory.PRR_API_KEYoverrides the key stored there. - Exit codes are meaningful. Anything that fails — a missing required flag,
an unknown command, or an unknown subcommand like
prr issue lst— prints the relevant help and exits non-zero, soset -escripts stop rather than carrying on. Asking for help (prr epic,prr epic help) exits 0.