The CLI for blackcode issues. Designed for both humans at a terminal and LLM agents driving the platform end-to-end.
This is the only supported interface. The HTTP routes behind it are private plumbing with no public contract — there is no OpenAPI spec, and calling them directly is unsupported.
bkhandles auth, JSON-body encoding, pagination, file upload+embed and stable exit codes, and it carries its own documentation.Agents start here:
npm install -g @blackcode_sa/bc-issues bk login bk skill install # writes an agent skill file that stays current bk guide # the complete usage guide, embedded in this binary
bk guideworks offline and always describes the exact version you are running.bk metasupplies everything that can change without a release — vocabularies, limits, your workspaces. Neither can go stale.
This README is the build/quick-reference for maintainers. For the full command
reference, conventions, and internals, see ../docs/cli.md.
cd cli
go build -o bk ./cmd/bk # or: make buildThe binary lands at cli/bk. Drop it on your $PATH if you like. make install
installs it to $GOBIN (default ~/go/bin); make all / make dist
cross-compile into dist/.
./bk login --server http://localhost:3000bk login opens your browser to /cli/authorize, captures the minted token via
a loopback HTTP server, validates it against /api/me, and saves
credentials to ~/.config/bk/config.json (mode 0600). Revoke any time from
Settings → API Tokens.
For headless / CI / agent use, paste a token from stdin instead:
echo "$MY_TOKEN" | ./bk login --server "$SERVER_URL" --tokenThere are no BK_SERVER / BK_TOKEN env vars — the server and token live in the
config file, chosen at login time.
Projects, issues, tasks, labels, members, and invitations are scoped to a workspace. Pick one once:
./bk workspace list # active row marked with *
./bk workspace use acme # by slug or numeric idWorkspace-scoped groups (label, member, invite) require an active
workspace; workspace paths accept the slug or the numeric id.
Every read command supports a global output flag, so you can pipe into jq,
yq, scripts, or feed structured output back into another LLM call.
-o, --output table|json|yaml|yml (default: table)
--json shortcut for -o json
--yaml / --yml shortcut for -o yaml
./bk project list --json | jq '.[].name'
./bk issue list --project 6 --yaml > issues.yamlStable across releases so scripts/agents can branch on outcome:
| Code | Meaning |
|---|---|
| 0 | success |
| 1 | generic error |
| 2 | bad usage (missing flag, invalid id, …) |
| 3 | not authenticated (401, or no config) |
| 4 | permission denied (403) |
| 5 | not found (404) |
| 6 | validation error (400/422) |
| 7 | user aborted at a confirm prompt |
Destructive commands that prompt (project delete, project remove-member,
issue delete, issue detach, undo) ask before acting. Skip the prompt with:
--yes/-yon the command,- the env var
BK_NO_PROMPT=1(set this for agents/CI), or - non-TTY stdin (the prompt is auto-skipped when not running in a terminal).
Long bodies (descriptions, comments) accept three forms so agents can pipe
markdown without quoting it (the *-file form wins if both are given):
--description "literal string"/--body "literal"--description -/--body -(read from stdin)--description-file FILE/--body-file FILE
printf '## Plan\n- item\n' | ./bk issue create --project 6 --title "..." --description -
./bk issue comment 42 --body-file ./review.md--file uploads a local file and embeds it inline in the description/comment
body — images preview, video/audio get players, everything else gets a download
card (the same result as web drag-and-drop). It's repeatable and works on
issue/task/project create and issue comment:
./bk issue create --project 6 --title "Crash" --file ./screenshot.png --file ./trace.log
./bk issue comment 42 --body "see clip" --file ./demo.mp4
./bk project create --name "Q3 brief" --file ./brief.pdf--file appends to the body. For a structured doc (files under specific
headings), reference local file paths directly in --description /
--description-file (or --body) and the CLI uploads + rewrites them in place:
cat > doc.md <<'MD'
## Screenshot

## Recording
[](<~/clips/screen recording (1).mov>)
MD
./bk issue create --project 6 --title "Bug" --description-file doc.mdA path is only uploaded when it has no http(s):// scheme and exists on disk;
empty link text is auto-filled from the filename. Paths with spaces or
parentheses must be angle-bracketed — [](</abs/my file (2).mp4>) — because
plain Markdown stops the link destination at the first ).
Need just a url (e.g. for scripting)? bk upload:
URL=$(./bk upload ./diagram.png --json | jq -r '.[0].url')bk upload and the local-path method create no sidebar attachment record.
bk issue attach is the opposite: it adds a file to the issue's attachments
list (sidebar), not the body.
bk login [--server URL] [--token] authorize via browser (or paste token)
bk logout remove credentials
bk whoami print authenticated user
bk version print CLI version / commit / build date
bk workspace list
bk workspace show [slug|id] defaults to active
bk workspace create --name N [--use] --use sets active (default true)
bk workspace use <slug|id> set active workspace
bk move --to <ws> [--project N ...] [--task N ...] [--issue N ...] copy then bin the source
bk copy --to <ws> [--project N ...] [--task N ...] [--issue N ...] copy, leave source in place
[--cascade-tasks=false] move a project WITHOUT its tasks
[--cascade-issues=false] move a project/task WITHOUT its issues
One atomic transfer (no data loss on failure); items get fresh #numbers in the
target, labels are matched/created by name, and non-member user refs are dropped
into an adjustments report. --project/--task/--issue take #numbers and
are repeatable.
bk project list
bk project view <id>
bk project members <id>
bk project issues <id> [--status S] [--assignee <id|email|name|me>]
bk project tasks <id>
bk project create --name N [--description D | --description-file F]
bk project edit <id> [--name N] [--description D | --description-file F] [--status S]
bk project delete <id> [--yes] [--cascade | --detach] move to Trash
bk project add-member <id> --email E [--role owner|admin|member|viewer]
bk project remove-member <id> --user <id|email|name> [--yes]
bk issue list [--project N] [--status S]
[--assignee <id|email|name|me>] [--mine] [--search TEXT]
bk issue view <id>
bk issue create --project N --title "..."
[--description D | --description-file F]
[--priority 1-5] [--status S]
[--assignee <id|email|name|me>] [--task N]
[--start-date YYYY-MM-DD] [--due-date YYYY-MM-DD]
[--attach FILE] add to the attachments list (sidebar)
[--file FILE ...] upload + embed inline in the description
bk issue edit <id> [--title T] [--description D | --description-file F]
[--status S] [--priority N]
[--assignee <id|email|name|me|none>]
[--task <N|none>]
[--start-date <YYYY-MM-DD|none>]
[--due-date <YYYY-MM-DD|none>]
bk issue assign <id> <user> set assignee (id, email, name, or me)
bk issue unassign <id> clear assignee
bk issue delete <id> [--yes] move to Trash (restore with `bk trash`)
bk issue comment <id> --body "..." | --body-file F | --body -
[--reply-to COMMENT_ID] threaded reply
[--file FILE ...] upload + embed inline
bk issue comments <id>
bk issue activity <id> comments + change history
bk issue attach <id> --file F add to attachments list (sidebar; not the body)
bk issue attachments <id>
bk issue detach <issue-id> <attachment-id> [--yes]
Canonical issue statuses: backlog, todo, in_progress, done, cancelled.
--assignee accepts a numeric id, an email, a display name, or me. Pass none
(or null/unset/clear) on issue edit to clear --assignee, --task,
--start-date, or --due-date; omit the flag to leave it unchanged.
bk task list [--project N]
bk task view <id> [--include-issues]
bk task create --project N --name M
[--description D | --description-file F] [--due-date YYYY-MM-DD]
bk task edit <id> [--name M] [--description D | --description-file F]
[--due-date <YYYY-MM-DD|none>]
bk task delete <id> [--yes] [--cascade | --detach] move to Trash
Deleting an issue, project, or task moves it to the Trash instead of removing it permanently. Restore items individually, in bulk, or as a delete group. Purging (permanent delete) is owner-only.
bk trash list [--type issue|project|task]
bk trash restore <type:id>... e.g. bk trash restore issue:42 project:3
bk trash restore --batch <id> restore a whole delete group
[--restore-parents | --standalone] force how dangling parents resolve
bk trash purge <type:id>... [--yes] permanent delete (owner only)
bk trash purge --batch <id> [--yes]
bk trash empty [--yes] permanently delete everything (owner only)
When a project/task is deleted with --cascade, its attached issues (and
a project's tasks) go to the Trash with it as one batch, so restoring the
batch brings the whole group back, re-linked. With --detach (the default) only
the parent is binned and the children stay active, unlinked. On restore, if an
item's parent is itself still in the Trash, the items it was deleted with
restore as a group; items deleted alone restore standalone — override per item in
the UI, or force it CLI-wide with --restore-parents / --standalone.
bk label list
bk label create --name N [--color #rrggbb] [--description D]
bk label delete <id>
bk label attach <issue-id> <label-id>
bk label detach <issue-id> <label-id>
bk member list
bk member remove <user-id> (owner only)
bk member leave (owner cannot leave)
bk invite send <email> prints a share link if the invitee has no account
bk invite list [--all] owner only; --all includes accepted/revoked/expired
bk invite revoke <id>
bk invite accept <token>
bk invite decline <token>
bk invite pending invitations pending for your email
bk inbox list [--unread] prints an unread count to stderr
bk inbox read [id ...] | --all mark messages read
bk inbox archive <id> [id ...] archive messages
bk user list list every user on the server
bk user view <id|email> show one user (filtered client-side)
bk upload <file> [<file> ...] upload file(s), print url(s) (no sidebar record)
bk activity [--limit N] [--cursor N] global change feed (keyset-paginated)
bk analytics [flags] workspace analytics (summary + filters)
bk undo [--count N] [--yes] roll back your last N writes (1-10)
bk analytics mirrors the web dashboard. Flags (all optional): --view
workspace|project|task|member, --id, --ws <slug|id>, --from/--to,
--interval day|week, and the --status/--priority/--label/--assignee
filters (repeatable or comma-separated). Default output is a readable summary;
--json/--yaml emit the full payload.
bk super-admin users list every member on the platform
bk super-admin whitelist list list allowed domains + emails
bk super-admin whitelist add --type domain|email --value V allow a domain/email
bk super-admin whitelist remove <id> [--yes] remove a whitelist entry
bk super-admin errors list [--status open|resolved] [--level L] [--from] [--to] [--limit] [--cursor] [--stats]
bk super-admin errors view <id> full detail incl. stack + context
bk super-admin errors resolve <id> mark resolved
bk super-admin errors unresolve <id> re-open
bk super-admin errors delete <id> [id ...] [--yes] permanently delete
bk super-admin errors stats total / open / resolved counts
These require a super-admin token — an account whose email is in the
server's SUPER_ADMINS env var. Any other token gets a 403 (exit code 4).
admin is an alias for super-admin. Whitelist and error changes apply across
the whole platform, not a single workspace. bk whoami shows super: yes when
your token has access.
The CLI inherits whatever the underlying token can do. Roughly:
| Action | Required role |
|---|---|
| Read projects/issues you belong to | any project member |
| Create/edit issues, comment, attach | project member (non-viewer) |
| Delete issue, delete attachment, edit project | project admin/owner |
| Delete project | project owner |
| Manage project members | project admin/owner |
| Remove workspace member, list invitations | workspace owner |
bk analytics |
any workspace member |
bk super-admin … (users, whitelist, errors) |
super admin (email in SUPER_ADMINS) |
A 403 on any command means the API rejected it for permissions; check your role
with bk project members <id>, bk member list, and bk whoami.
The main list commands (bk issue list, bk project list, bk task list)
return every matching item in one response — no pagination. bk issue list
includes a server-side total in --json / --yaml output.
Only the keyset-paginated feeds accept --limit / --cursor: bk activity,
bk trash list, and bk super-admin errors list. Their envelope is
{ "data": [...], "next_cursor": <id|null> }, and in table output the next
cursor is printed to stderr as next page: --cursor=X when more rows remain.
BK_CONFIG_DIR— override the config directory (default~/.config/bk).BK_NO_PROMPT=1— skip every interactive confirmation prompt (recommended for agents).
bk issue create --attach FILE and bk issue attach <id> --file FILE upload via
POST /api/upload (Vercel Blob), then attach the result. The server must have
BLOB_READ_WRITE_TOKEN configured. Allowed types: JPEG, PNG, GIF, WebP, PDF,
plain text, JSON, Markdown.