{"id":"070f6394-bd73-4f0d-8fa1-699f99280765","slug":"bb-cli","kind":"skill","name":"bb-cli","summary":"Bitbucket Cloud from the command line","owner":"jonasporto","repoUrl":"https://github.com/jonasporto/bb-cli","license":"MIT","licenseTier":"permissive","trust":"community","stars":1,"match":null,"fewStars":true,"format":"skill","createdAt":"2026-09-04T17:54:31.000Z","addedAt":"2026-10-21T11:32:59.857Z","logoUrl":"https://avatars.githubusercontent.com/u/2723477?u=40c46c550f838abd3289924619848a7c651f78cd&v=4","linkOnly":false,"collection":null,"skillPath":null,"categories":["developer-tools"],"tags":["bash","bitbucket","bitbucket-api","bitbucket-cloud","bitbucket-pipelines","ci-cd","cli","code-review"],"runtime":"binary","local":true,"cloudCapable":false,"cloudCapability":"unknown","cloudReadyOnPlatform":false,"needsApiKey":false,"reviewed":false,"syncedAt":"2026-10-27T19:42:59.232Z","sample":false,"url":"https://conduits.dev/tools/bb-cli","language":"Shell","transports":["stdio"],"install":{},"requirements":{"env":[],"upstreamMayCharge":false},"tools":null,"readme":"# bb-cli\n\n[![Tests](https://github.com/jonasporto/bb-cli/actions/workflows/test.yml/badge.svg)](https://github.com/jonasporto/bb-cli/actions/workflows/test.yml)\n\n**Bitbucket Cloud from the command line.**\n\nWhat `gh` is to GitHub. Pull requests, pipelines, branches and build statuses in\na terminal, plus `bb-cli api`, an authenticated passthrough for everything\nelse, so a missing command is an inconvenience rather than a blocker.\n\nOne bash script. No build step, no runtime, no daemon.\n\n```bash\nbb-cli pr                                    # open pull requests\nbb-cli pipeline 1234 failures               # why did the build fail\nnpm test && bb-cli signoff                   # local CI: publish a green check\nbb-cli api repositories/:repo -i             # anything else\n```\n\n## Install\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/jonasporto/bb-cli/main/install.sh | bash\nbb-cli login\n```\n\nThat clones into `~/.bb-cli` and installs from there, or reuses a checkout\nyou already have, so re-running it never leaves a second copy behind.\n`BB_CLI_HOME` puts it somewhere else:\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/jonasporto/bb-cli/main/install.sh \\\n  | BB_CLI_HOME=~/src/bb-cli bash\n```\n\nOr clone it yourself and run the script in place. The same thing, one step at a\ntime:\n\n```bash\ngit clone https://github.com/jonasporto/bb-cli.git ~/.bb-cli\n~/.bb-cli/install.sh\nbb-cli login\n```\n\n### Just the script\n\nIf you only want the binary on your PATH, that works too:\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/jonasporto/bb-cli/main/bin/bb-cli \\\n  -o ~/.local/bin/bb-cli && chmod +x ~/.local/bin/bb-cli\n```\n\nEvery command works. `bb-cli upgrade` works too, by replacing the script from\nthe same URL. What you give up is the material that lives beside the script and\ncannot fit inside it: `bb-cli docs`, `man bb-cli` and `bb-cli skill install`.\nThose three say so plainly, and name the one line that gets them back.\n\n`install.sh` checks for `curl`, `jq` and `git`, symlinks the executable into\n`~/.local/bin` and the man page into `~/.local/share/man/man1`, and tells you if\neither is outside your `PATH` or `MANPATH`. Symlinks, not copies, so `git pull`\nupgrades the installed tool. `--prefix`, `--bin-dir` and `--uninstall` are\nthere too.\n\nFull walkthrough, including which five token scopes to check and why:\n\n```bash\nbb-cli docs setup\n```\n\n## Documentation travels with the tool\n\nNo wiki to find, no network needed.\n\n| | |\n|---|---|\n| `bb-cli --help`, `bb-cli help <cmd>` | every command has one, kept accurate |\n| `man bb-cli` | the man page (or `bb-cli man`, which works before install) |\n| `bb-cli docs` | list the topics |\n| `bb-cli docs setup` | install, token creation, the credentials file |\n| `bb-cli docs scopes` | the five scopes, what to leave unchecked, how to prove what a token holds |\n| `bb-cli docs api` | `bb-cli api` reference and a curl-replacement cookbook |\n| `bb-cli docs pipelines` | reading and running pipelines, log slicing, trigger etiquette |\n| `bb-cli docs signoff` | local CI, field-by-field against `gh signoff` |\n\n## Using it with an AI agent\n\nA skill ships in this repository, so an agent that has never seen this tool\nknows it exists, when to reach for it, and the traps that would otherwise cost\nit a dozen turns.\n\n```bash\nnpx skills add jonasporto/bb-cli\n```\n\nThat is [`vercel-labs/skills`](https://github.com/vercel-labs/skills), which\ninstalls into whichever agent you use: Claude Code, Codex, Cursor, Copilot and\naround eighty others. `-g` installs globally, `-p` per project. It installs the\nskill, not the tool: the skill's first instruction is to check for `bb-cli` and\ninstall it if missing.\n\nIt needs `git` on the machine, and it asks which agents to install into. In a\nscript or a container, name them:\n\n```bash\nnpx skills add jonasporto/bb-cli -p -y --agent claude-code\nnpx skills add jonasporto/bb-cli --all      # every skill, every agent\n```\n\n`bb-cli skill install` does a smaller version of the same thing, for when you\nalready have this checkout and would rather not involve node, or you are\noffline:\n\n```bash\nbb-cli skill install              # links into ~/.claude/skills/bb-cli\nbb-cli skill install --project    # ./.claude/skills, commits with the repo\nbb-cli skill print                # stdout, installs nothing\n```\n\nIt links rather than copies, so `git pull` updates the skill too. It is **not**\nequivalent to `npx skills add`:\n\n| | `npx skills add` | `bb-cli skill install` |\n|---|---|---|\n| agents | ~80 | Claude Code |\n| needs this checkout | no, clones it | yes |\n| needs node | yes | no |\n| lockfile + content hash | yes | no |\n| `update` / `list` / `remove` | yes | no |\n\n`AGENTS.md` at the repository root covers the same ground for agents that read\nthat convention instead.\n\n## Commands\n\n### Pull requests\n\n| Command | Does |\n|---|---|\n| `bb-cli pr` | list open pull requests |\n| `bb-cli pr <n>` | one PR: state, reviewers, **description**, comments summary, activity |\n| `bb-cli pr <n> -j` | the same as JSON |\n| `bb-cli pr <n> -c` | with comment bodies |\n| `bb-cli pr <string>` | filter by source branch |\n| `bb-cli pr --target master` | filter by destination branch |\n| `bb-cli pr --author X` | filter by author |\n| `bb-cli pr merged --limit 5` | last five merged |\n\n```bash\nbb-cli pr edit 42 --body-file body.md      # title and/or description\nbb-cli pr ready 42                          # draft -> ready for review\nbb-cli pr comment 42 --body-file note.md\n```\n\n**Always `--body-file`, not `--body \"$(cat f)\"`.** Descriptions are multi-line\nmarkdown, and two trailing spaces are markdown's hard line break, and command\nsubstitution strips them, which is exactly what turns a list of stacked issue\nlinks into one run-on paragraph. `--body` exists for one-liners.\n\n`pr edit` reads the pull request first and sends both fields, so changing only\nthe title cannot blank the description.\n\nCreating, merging and reviewing still go through `api`. See the cookbook in\n`bb-cli docs api`.\n\n### Pipelines\n\n| Command | Does |\n|---|---|\n| `bb-cli pipeline` | recent pipelines with status, duration, actor, commit, branch, trigger |\n| `bb-cli pipeline <n>` | one build with its steps |\n| `bb-cli pipeline <n> failures` | failed steps and test-report summary. **Start here** |\n| `bb-cli pipeline <n> steps` | every step |\n| `bb-cli pipeline <n> step <uuid> --log` | a step's raw log |\n| `bb-cli pipeline trigger --branch <b> [--custom \"<name>\"]` | new build (needs Pipelines: Write) |\n| `bb-cli pipeline rerun <n>` | post a **new** build for the same target/selector |\n| `bb-cli pipeline watch <n> [--exit-status]` | poll until it finishes |\n\nFlags may appear **before or after** the subcommand: `pipeline --repo X rerun 1240`\nand `pipeline rerun 1240 --repo X` are the same thing.\n\n### Signoff: local CI\n\n```bash\nnpm test && bb-cli signoff --name \"vitest 461 passed, 23 skipped, 8s\"\nbb-cli signoff status\n```\n\nPublishes a build status on the commit, so the pull request shows a check from a\nsuite that ran on your machine. The Bitbucket counterpart of\n[`basecamp/gh-signoff`](https://github.com/basecamp/gh-signoff).\n\n**It needs no extra permission.** `read:repository:bitbucket` is enough to\npublish, which is counterintuitive and was measured rather than assumed.\n\nIt refuses on a dirty working tree or an unpushed commit, because a signoff is a\nclaim that the suite ran green against *that* commit. `--force` overrides.\n\nThere is no `bb-cli signoff install`. Requiring the status for merge is a branch\nrestriction needing `admin:repository:bitbucket` to read as well as write, and it\nedits a policy every contributor lives under. `bb-cli docs signoff` explains.\n\n### `bb-cli api`: the escape hatch\n\n`gh` is not sufficient because it has a command for everything; it is sufficient\nbecause it has `gh api`.\n\n```bash\nbb-cli api repositories/:repo/pullrequests/10 -q .description\nbb-cli api repositories/:repo/pullrequests/10 -X PUT --input body.json\nbb-cli api repositories/:repo -i | grep -i oauth\nbb-cli api repositories/:repo/pullrequests --paginate -q '.values[].title'\n```\n\n`:repo` expands to `workspace/slug` from the git remote, so a command pasted\ninto a document works in anyone's checkout. `-q` runs jq. `-i` prints headers,\nwhich is how you find out what a token can actually do. `--input` is byte-exact,\nwhich matters more than it sounds: trailing spaces are markdown's hard line\nbreak, and command substitution eats them.\n\n**One deliberate difference from `gh`:** `-f`/`-F` alone do **not** imply POST\nhere. On Bitbucket, `POST /pullrequests` creates a pull request, so a read that\nsilently became a write would be expensive. Say `-X POST` when you mean it.\n\n### Caches\n\n```bash\nbb-cli cache list                 # uuid, name, size, age\nbb-cli cache delete <uuid>\n```\n\nBy uuid only, deliberately. Bitbucket's API also accepts `?name=`, and that\nremoves **every** cache sharing that name (they are distinguished by path as\nwell), so one call takes out other branches' caches too.\n\n### Other\n\n`bb-cli branch [filter]`, `bb-cli repo`, `bb-cli status`, `bb-cli login`,\n`bb-cli logout`, `bb-cli version`.\n\n## Coming from `gh`\n\nThe command surface tracks `gh` wherever Bitbucket allows it, so muscle memory\ncarries over. `gh`'s nouns are accepted as aliases even where Bitbucket's own\nvocabulary is the primary name.\n\n| `gh` | `bb-cli` | |\n|---|---|---|\n| `gh auth login` / `logout` / `status` | same, and also at the top level | ✅ |\n| `gh auth login --with-token` | `bb-cli login --with-token` | ✅ |\n| `gh api` | `bb-cli api` | ✅ |\n| `gh pr list` | `bb-cli pr` / `bb-cli pr list` | ✅ |\n| `gh pr view <n>` | `bb-cli pr <n>` / `bb-cli pr view <n>` | ✅ |\n| `gh pr checks <n>` | `bb-cli pr checks <n>` | ✅ |\n| `gh pr edit <n>` | `bb-cli pr edit <n>` | ✅ |\n| `gh pr ready <n>` | `bb-cli pr ready <n>` (`--undo`) | ✅ |\n| `gh pr comment <n>` | `bb-cli pr comment <n>` | ✅ |\n| `gh run list` | `bb-cli pipeline` / `bb-cli run` | ✅ |\n| `gh run view <n>` | `bb-cli pipeline <n>` / `view <n>` | ✅ |\n| `gh run view --log` | `bb-cli pipeline <n> step <uuid> --log` | ✅ |\n| `gh run rerun <n>` | `bb-cli pipeline rerun <n>` | ✅ |\n| `gh run watch <n>` | `bb-cli pipeline watch <n>` | ✅ |\n| `gh run cancel <n>` | `bb-cli pipeline cancel <n>` | ✅ |\n| `gh workflow run` | `bb-cli pipeline trigger` | ✅ |\n| `gh cache list` / `delete` | `bb-cli cache list` / `delete` | ✅ |\n| `gh repo view` | `bb-cli repo` | ✅ |\n| `gh version` | `bb-cli version` | ✅ |\n| `gh run rerun --failed` | prints the limitation | ⛔ no Bitbucket endpoint |\n| `gh pr create` | `bb-cli api` for now | ⏳ roadmap |\n| `gh pr merge` / `close` / `review` | `bb-cli api` | ⏳ |\n| `gh browse` | none yet | ⏳ |\n| none | `bb-cli signoff` | Bitbucket build status, mirrors the `gh-signoff` extension |\n| none | `bb-cli docs` / `man` / `skill` | no `gh` equivalent |\n\nWhere the names differ, Bitbucket's noun wins and `gh`'s is an alias: a\n*pipeline* is what Bitbucket calls it everywhere else (its UI, its docs, its\nAPI), so a tool that called it a *run* would be lying about the system it talks\nto. `bb-cli run` works anyway.\n\nTwo divergences are deliberate rather than missing, and both are in\n`bb-cli docs api`: `-f`/`-F` do not imply `POST`, and `--paginate` merges\n`.values` instead of concatenating pages.\n\n## Credentials\n\n`~/.config/bb-cli/credentials`, mode `600` in a `700` directory:\n\n```\nBITBUCKET_USERNAME=<atlassian email>\nBITBUCKET_API_TOKEN=<token>\n```\n\nThe environment wins over the file, so `BITBUCKET_API_TOKEN=xxx bb-cli pr 10`\ntries a token without touching anything, and CI can supply one directly.\n\n`bb-cli login` is interactive. Where there is no terminal (a script, CI, an\nagent), pass the token on stdin instead, the way `gh auth login --with-token`\ndoes:\n\n```bash\nprintf %s \"$TOKEN\" | bb-cli login --with-token --email you@example.com\n```\n\nFive scopes, and two traps: **`write` does not imply `read`**, and scopes are\n**fixed at creation**. `bb-cli docs scopes` has the full table, including which\nscopes look necessary and are not, and why `admin:repository` is deliberately\nleft off.\n\n## Tests\n\n```bash\ntests/run.sh          # every suite\ntests/run.sh api      # one suite\n```\n\nOffline by design: a fake `curl` on `PATH` records the argv and answers from a\nfixture, so the whole suite runs in about the time one real request would take,\nwith no token and no network. The suites assert on **what would go over the\nwire** (verb, URL, request body) rather than on formatted output.\n\n## Known limits\n\n- **`pipeline rerun --failed` is not supported by Bitbucket's public API.** The\n  web UI has it; there is no endpoint (the UI calls an undocumented internal\n  API). bb-cli prints the limitation rather than quietly doing something else. A\n  full `rerun` runs everything, on the branch head.\n- **Requiring a signoff for merge needs repo admin** and is out of scope by\n  design.\n- **A Bitbucket token cannot reach Jira.** Atlassian scopes a token to one\n  product at creation.\n\n## License\n\nMIT. See [LICENSE](LICENSE).\n"}