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-app uses slugs; commands that create things generally want the UUID, which prr ... view and prr ... list print.
  • Issues can be addressed by number with --product and --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 from prr org assignees.
  • Finished work is hidden unless you ask for it. prr issue mine and prr issue list both leave out anything in a done or closed status until you pass --all — see Core concepts.
  • Flags take either form. --product=ecosmart and --product ecosmart are the same thing.
  • Org scope comes from --org=<slug> or the default set by prr org use.
  • Config lives at ~/.projectrr/config.json, 0600 in a 0700 directory. PRR_API_KEY overrides 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, so set -e scripts stop rather than carrying on. Asking for help (prr epic, prr epic help) exits 0.