@pilatos/bitbucket-cli 0.3.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +53 -231
  2. package/dist/index.js +450 -70
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -32,56 +32,20 @@
32
32
 
33
33
  If you've used GitHub's `gh` CLI and loved it, you've probably wished for something similar for Bitbucket. **Now you have it.**
34
34
 
35
- `bb` brings the power of command-line workflows to Bitbucket Cloud, letting you:
36
-
37
35
  - **Stay in your terminal** — No more context-switching to the browser
38
- - **Automate your workflow** — Script common operations with ease
36
+ - **Automate your workflow** — Script common operations with JSON output
39
37
  - **Work faster** — Clone repos, create PRs, and manage code reviews in seconds
40
- - **Maintain consistency** — Use familiar patterns if you work across GitHub and Bitbucket
41
-
42
- ---
43
-
44
- ## Table of Contents
45
-
46
- - [Installation](#installation)
47
- - [Quick Start](#quick-start)
48
- - [Commands](#commands)
49
- - [Authentication](#authentication)
50
- - [Repositories](#repositories)
51
- - [Pull Requests](#pull-requests)
52
- - [Configuration](#configuration)
53
- - [Shell Completion](#shell-completion)
54
- - [Global Options](#global-options)
55
- - [Authentication Setup](#authentication-setup)
56
- - [Configuration](#configuration-1)
57
- - [Examples](#examples)
58
- - [Development](#development)
59
- - [Contributing](#contributing)
60
- - [License](#license)
38
+ - **Smart context detection** — Automatically detects workspace/repo from git directory
61
39
 
62
40
  ---
63
41
 
64
42
  ## Installation
65
43
 
66
- ### Using npm (Recommended)
67
-
68
44
  ```bash
69
45
  npm install -g @pilatos/bitbucket-cli
70
46
  ```
71
47
 
72
- ### Using Bun
73
-
74
- ```bash
75
- bun install -g @pilatos/bitbucket-cli
76
- ```
77
-
78
- ### Using Yarn
79
-
80
- ```bash
81
- yarn global add @pilatos/bitbucket-cli
82
- ```
83
-
84
- ### Verify Installation
48
+ Verify installation:
85
49
 
86
50
  ```bash
87
51
  bb --version
@@ -93,21 +57,19 @@ bb --version
93
57
 
94
58
  ## Quick Start
95
59
 
96
- Get up and running in under a minute:
97
-
98
60
  ```bash
99
61
  # 1. Authenticate with Bitbucket
100
62
  bb auth login
101
63
 
102
64
  # 2. Clone a repository
103
65
  bb repo clone myworkspace/myrepo
66
+ cd myrepo
104
67
 
105
68
  # 3. Create a feature branch and make changes
106
- cd myrepo
107
69
  git checkout -b feature/awesome-feature
108
70
 
109
71
  # 4. Create a pull request
110
- bb pr create --title "Add awesome feature" --source feature/awesome-feature --destination main
72
+ bb pr create --title "Add awesome feature"
111
73
 
112
74
  # 5. List open pull requests
113
75
  bb pr list
@@ -115,152 +77,53 @@ bb pr list
115
77
 
116
78
  ---
117
79
 
118
- ## Commands
119
-
120
- ### Authentication
121
-
122
- Manage your Bitbucket authentication securely.
123
-
124
- | Command | Description |
125
- |---------|-------------|
126
- | `bb auth login` | Authenticate with Bitbucket using an App Password |
127
- | `bb auth logout` | Log out and remove stored credentials |
128
- | `bb auth status` | Check your current authentication status |
129
- | `bb auth token` | Print your current access token |
80
+ ## Features
130
81
 
131
- ### Repositories
132
-
133
- Clone, create, and manage your Bitbucket repositories.
134
-
135
- | Command | Description |
136
- |---------|-------------|
137
- | `bb repo clone <repo>` | Clone a repository to your local machine |
138
- | `bb repo create <name>` | Create a new repository |
139
- | `bb repo list` | List repositories in a workspace |
140
- | `bb repo view [repo]` | View repository details and metadata |
141
- | `bb repo delete <repo>` | Delete a repository (use with caution!) |
142
-
143
- ### Pull Requests
144
-
145
- Full pull request workflow management from your terminal.
146
-
147
- | Command | Description |
148
- |---------|-------------|
149
- | `bb pr create` | Create a new pull request |
150
- | `bb pr list` | List pull requests with filtering options |
151
- | `bb pr view <id>` | View pull request details, diff, and comments |
152
- | `bb pr merge <id>` | Merge a pull request |
153
- | `bb pr approve <id>` | Approve a pull request |
154
- | `bb pr decline <id>` | Decline a pull request |
155
- | `bb pr checkout <id>` | Checkout a pull request branch locally |
156
-
157
- ### Configuration
158
-
159
- Customize your CLI experience.
160
-
161
- | Command | Description |
162
- |---------|-------------|
163
- | `bb config get <key>` | Get a configuration value |
164
- | `bb config set <key> <value>` | Set a configuration value |
165
- | `bb config list` | List all configuration values |
166
-
167
- ### Shell Completion
168
-
169
- Enable intelligent tab completion for faster command entry.
170
-
171
- | Command | Description |
172
- |---------|-------------|
173
- | `bb completion install` | Install shell completions (auto-detects shell) |
174
- | `bb completion uninstall` | Remove shell completions |
175
-
176
- **Setup:**
177
-
178
- ```bash
179
- # Install completions
180
- bb completion install
181
-
182
- # Restart your shell or source your profile
183
- source ~/.bashrc # Bash
184
- source ~/.zshrc # Zsh
185
- source ~/.config/fish/config.fish # Fish
186
- ```
82
+ | Category | Commands |
83
+ |----------|----------|
84
+ | **Authentication** | `login`, `logout`, `status`, `token` |
85
+ | **Repositories** | `clone`, `create`, `list`, `view`, `delete` |
86
+ | **Pull Requests** | `create`, `list`, `view`, `edit`, `merge`, `approve`, `decline`, `checkout`, `diff` |
87
+ | **Configuration** | `get`, `set`, `list` |
88
+ | **Shell Completion** | `install`, `uninstall` |
187
89
 
188
- **Usage:**
189
-
190
- ```bash
191
- bb re<Tab> # bb repo
192
- bb repo cl<Tab> # → bb repo clone
193
- bb pr l<Tab> # → bb pr list
194
- ```
90
+ **Global Options:**
91
+ - `--json` — Output results as JSON for scripting
92
+ - `-w, --workspace` — Specify workspace
93
+ - `-r, --repo` Specify repository
195
94
 
196
95
  ---
197
96
 
198
- ## Global Options
97
+ ## Documentation
199
98
 
200
- These options work with any command:
99
+ Full documentation is available at **[bitbucket-cli.paulvanderlei.com](https://bitbucket-cli.paulvanderlei.com)**
201
100
 
202
- | Option | Description |
203
- |--------|-------------|
204
- | `--json` | Output results as JSON for scripting |
205
- | `-w, --workspace <workspace>` | Specify the workspace (overrides default) |
206
- | `-r, --repo <repo>` | Specify the repository (overrides default) |
207
- | `-h, --help` | Show help information |
208
- | `-v, --version` | Show version number |
101
+ - [Quick Start Guide](https://bitbucket-cli.paulvanderlei.com/getting-started/quickstart/)
102
+ - [Command Reference](https://bitbucket-cli.paulvanderlei.com/commands/auth/)
103
+ - [Scripting & Automation](https://bitbucket-cli.paulvanderlei.com/guides/scripting/)
104
+ - [CI/CD Integration](https://bitbucket-cli.paulvanderlei.com/guides/cicd/)
105
+ - [Troubleshooting](https://bitbucket-cli.paulvanderlei.com/help/troubleshooting/)
106
+ - [FAQ](https://bitbucket-cli.paulvanderlei.com/help/faq/)
209
107
 
210
108
  ---
211
109
 
212
- ## Authentication Setup
213
-
214
- The CLI uses **Bitbucket App Passwords** for secure authentication. Here's how to set it up:
215
-
216
- ### Step 1: Create an App Password
217
-
218
- 1. Go to [Bitbucket App Passwords](https://bitbucket.org/account/settings/app-passwords/)
219
- 2. Click **"Create app password"**
220
- 3. Enter a descriptive label (e.g., "bb CLI")
221
- 4. Select the required permissions:
222
- - **Account:** Read
223
- - **Repositories:** Read, Write, Admin (as needed)
224
- - **Pull requests:** Read, Write
225
- 5. Click **"Create"**
226
- 6. **Copy the generated password** (you won't see it again!)
227
-
228
- ### Step 2: Authenticate
229
-
230
- ```bash
231
- bb auth login
232
- ```
233
-
234
- Enter your Bitbucket username and the App Password when prompted.
110
+ ## Authentication
235
111
 
236
- ### Step 3: Verify
112
+ The CLI uses **Bitbucket API Tokens** for secure authentication.
237
113
 
238
- ```bash
239
- bb auth status
240
- ```
114
+ > **Note**: As of September 9, 2025, Bitbucket app passwords are deprecated. Use API tokens instead.
241
115
 
242
- ---
116
+ ### Create an API Token
243
117
 
244
- ## Configuration
118
+ 1. Go to [Bitbucket API Tokens](https://bitbucket.org/account/settings/api-tokens/)
119
+ 2. Click **"Create API token"**
120
+ 3. Select required scopes (Account, Repositories, Pull requests)
121
+ 4. Copy the token
245
122
 
246
- Configuration files are stored in platform-specific locations:
247
-
248
- | Platform | Location |
249
- |----------|----------|
250
- | **macOS / Linux** | `~/.config/bb/config.json` |
251
- | **Windows** | `%APPDATA%\bb\config.json` |
252
-
253
- ### Available Settings
123
+ ### Authenticate
254
124
 
255
125
  ```bash
256
- # Set your default workspace
257
- bb config set workspace myworkspace
258
-
259
- # Set your default repository
260
- bb config set repo myrepo
261
-
262
- # View all settings
263
- bb config list
126
+ bb auth login
264
127
  ```
265
128
 
266
129
  ---
@@ -270,53 +133,38 @@ bb config list
270
133
  ### Daily Workflow
271
134
 
272
135
  ```bash
273
- # Start your day: check open PRs assigned to you
274
- bb pr list --state OPEN
136
+ # Check open PRs
137
+ bb pr list
275
138
 
276
- # Review a specific PR
139
+ # Review and merge
277
140
  bb pr view 42
278
-
279
- # Approve and merge
280
141
  bb pr approve 42
281
142
  bb pr merge 42
282
143
  ```
283
144
 
284
- ### Creating a Pull Request
145
+ ### Scripting with JSON
285
146
 
286
147
  ```bash
287
- # From your feature branch
288
- bb pr create \
289
- --title "feat: Add user notifications" \
290
- --description "Implements real-time notifications using WebSockets" \
291
- --source feature/notifications \
292
- --destination main
293
- ```
148
+ # Get all open PR titles
149
+ bb pr list --json | jq '.[].title'
294
150
 
295
- ### Scripting with JSON Output
296
-
297
- ```bash
298
- # Get all open PRs as JSON for processing
299
- bb pr list --state OPEN --json | jq '.[] | .title'
300
-
301
- # List repos and filter by name
151
+ # Filter repos by name
302
152
  bb repo list --json | jq '.[] | select(.name | contains("api"))'
303
153
  ```
304
154
 
305
- ### Quick Repository Setup
155
+ ### CI/CD Usage
306
156
 
307
157
  ```bash
308
- # Create and clone a new repo in one flow
309
- bb repo create my-new-project
310
- bb repo clone myworkspace/my-new-project
311
- cd my-new-project
158
+ export BB_USERNAME=myuser
159
+ export BB_API_TOKEN=my-token
160
+ bb auth login
161
+ bb pr list -w workspace -r repo --json
312
162
  ```
313
163
 
314
164
  ---
315
165
 
316
166
  ## Development
317
167
 
318
- Want to contribute or run locally? Here's how:
319
-
320
168
  ```bash
321
169
  # Clone the repository
322
170
  git clone https://github.com/0pilatos0/bitbucket-cli.git
@@ -333,59 +181,33 @@ bun test
333
181
 
334
182
  # Build for production
335
183
  bun run build
336
-
337
- # Generate API client from OpenAPI spec
338
- bun run generate:api
339
- ```
340
-
341
- ### Project Structure
342
-
343
- ```
344
- bitbucket-cli/
345
- ├── src/
346
- │ ├── commands/ # Command implementations
347
- │ ├── core/ # Core utilities and base classes
348
- │ └── index.ts # Entry point
349
- ├── tests/ # Test files
350
- ├── docs/ # Documentation site (Astro)
351
- └── specs/ # OpenAPI specifications
352
184
  ```
353
185
 
354
186
  ---
355
187
 
356
188
  ## Contributing
357
189
 
358
- We welcome contributions from the community! Whether it's:
359
-
360
- - Reporting bugs
361
- - Suggesting new features
362
- - Improving documentation
363
- - Submitting pull requests
364
-
365
- Please read our [Contributing Guide](CONTRIBUTING.md) to get started.
366
-
367
- ### Quick Contribution Steps
190
+ We welcome contributions! Please read our [Contributing Guide](CONTRIBUTING.md) to get started.
368
191
 
369
192
  1. Fork the repository
370
- 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
193
+ 2. Create a feature branch
371
194
  3. Make your changes
372
195
  4. Run tests (`bun test`)
373
- 5. Commit with a descriptive message
374
- 6. Push and open a Pull Request
196
+ 5. Submit a Pull Request
375
197
 
376
198
  ---
377
199
 
378
200
  ## Acknowledgments
379
201
 
380
- - Inspired by [GitHub CLI (`gh`)](https://cli.github.com/) — the gold standard for repository CLIs
381
- - Built with [Commander.js](https://github.com/tj/commander.js) for robust command parsing
202
+ - Inspired by [GitHub CLI (`gh`)](https://cli.github.com/)
203
+ - Built with [Commander.js](https://github.com/tj/commander.js)
382
204
  - Uses the [Bitbucket Cloud REST API](https://developer.atlassian.com/cloud/bitbucket/rest/)
383
205
 
384
206
  ---
385
207
 
386
208
  ## License
387
209
 
388
- This project is licensed under the **MIT License** — see the [LICENSE](LICENSE) file for details.
210
+ MIT License — see [LICENSE](LICENSE) for details.
389
211
 
390
212
  ---
391
213
 
package/dist/index.js CHANGED
@@ -27629,7 +27629,7 @@ var require_iso2022 = __commonJS((exports, module) => {
27629
27629
 
27630
27630
  // node_modules/external-editor/node_modules/chardet/index.js
27631
27631
  var require_chardet = __commonJS((exports, module) => {
27632
- var fs = __require("fs");
27632
+ var fs2 = __require("fs");
27633
27633
  var utf8 = require_utf8();
27634
27634
  var unicode = require_unicode();
27635
27635
  var mbcs = require_mbcs();
@@ -27703,29 +27703,29 @@ var require_chardet = __commonJS((exports, module) => {
27703
27703
  var fd;
27704
27704
  var handler = function(err, buffer) {
27705
27705
  if (fd) {
27706
- fs.closeSync(fd);
27706
+ fs2.closeSync(fd);
27707
27707
  }
27708
27708
  if (err)
27709
27709
  return cb(err, null);
27710
27710
  cb(null, self2.detect(buffer, opts));
27711
27711
  };
27712
27712
  if (opts && opts.sampleSize) {
27713
- fd = fs.openSync(filepath, "r"), sample = Buffer.allocUnsafe(opts.sampleSize);
27714
- fs.read(fd, sample, 0, opts.sampleSize, null, function(err) {
27713
+ fd = fs2.openSync(filepath, "r"), sample = Buffer.allocUnsafe(opts.sampleSize);
27714
+ fs2.read(fd, sample, 0, opts.sampleSize, null, function(err) {
27715
27715
  handler(err, sample);
27716
27716
  });
27717
27717
  return;
27718
27718
  }
27719
- fs.readFile(filepath, handler);
27719
+ fs2.readFile(filepath, handler);
27720
27720
  };
27721
27721
  module.exports.detectFileSync = function(filepath, opts) {
27722
27722
  if (opts && opts.sampleSize) {
27723
- var fd = fs.openSync(filepath, "r"), sample2 = Buffer.allocUnsafe(opts.sampleSize);
27724
- fs.readSync(fd, sample2, 0, opts.sampleSize);
27725
- fs.closeSync(fd);
27723
+ var fd = fs2.openSync(filepath, "r"), sample2 = Buffer.allocUnsafe(opts.sampleSize);
27724
+ fs2.readSync(fd, sample2, 0, opts.sampleSize);
27725
+ fs2.closeSync(fd);
27726
27726
  return self2.detect(sample2, opts);
27727
27727
  }
27728
- return self2.detect(fs.readFileSync(filepath), opts);
27728
+ return self2.detect(fs2.readFileSync(filepath), opts);
27729
27729
  };
27730
27730
  module.exports.detectAll = function(buffer, opts) {
27731
27731
  if (typeof opts !== "object") {
@@ -31087,7 +31087,7 @@ var require_tmp = __commonJS((exports, module) => {
31087
31087
  *
31088
31088
  * MIT Licensed
31089
31089
  */
31090
- var fs = __require("fs");
31090
+ var fs2 = __require("fs");
31091
31091
  var path = __require("path");
31092
31092
  var crypto = __require("crypto");
31093
31093
  var osTmpDir = require_os_tmpdir();
@@ -31151,7 +31151,7 @@ var require_tmp = __commonJS((exports, module) => {
31151
31151
  return cb(new Error("Invalid template provided"));
31152
31152
  (function _getUniqueName() {
31153
31153
  const name = _generateTmpName(opts);
31154
- fs.stat(name, function(err) {
31154
+ fs2.stat(name, function(err) {
31155
31155
  if (!err) {
31156
31156
  if (tries-- > 0)
31157
31157
  return _getUniqueName();
@@ -31170,7 +31170,7 @@ var require_tmp = __commonJS((exports, module) => {
31170
31170
  do {
31171
31171
  const name = _generateTmpName(opts);
31172
31172
  try {
31173
- fs.statSync(name);
31173
+ fs2.statSync(name);
31174
31174
  } catch (e) {
31175
31175
  return name;
31176
31176
  }
@@ -31183,14 +31183,14 @@ var require_tmp = __commonJS((exports, module) => {
31183
31183
  tmpName(opts, function _tmpNameCreated(err, name) {
31184
31184
  if (err)
31185
31185
  return cb(err);
31186
- fs.open(name, CREATE_FLAGS, opts.mode || FILE_MODE, function _fileCreated(err2, fd) {
31186
+ fs2.open(name, CREATE_FLAGS, opts.mode || FILE_MODE, function _fileCreated(err2, fd) {
31187
31187
  if (err2)
31188
31188
  return cb(err2);
31189
31189
  if (opts.discardDescriptor) {
31190
- return fs.close(fd, function _discardCallback(err3) {
31190
+ return fs2.close(fd, function _discardCallback(err3) {
31191
31191
  if (err3) {
31192
31192
  try {
31193
- fs.unlinkSync(name);
31193
+ fs2.unlinkSync(name);
31194
31194
  } catch (e) {
31195
31195
  if (!isENOENT(e)) {
31196
31196
  err3 = e;
@@ -31213,9 +31213,9 @@ var require_tmp = __commonJS((exports, module) => {
31213
31213
  opts.postfix = opts.postfix || ".tmp";
31214
31214
  const discardOrDetachDescriptor = opts.discardDescriptor || opts.detachDescriptor;
31215
31215
  const name = tmpNameSync(opts);
31216
- var fd = fs.openSync(name, CREATE_FLAGS, opts.mode || FILE_MODE);
31216
+ var fd = fs2.openSync(name, CREATE_FLAGS, opts.mode || FILE_MODE);
31217
31217
  if (opts.discardDescriptor) {
31218
- fs.closeSync(fd);
31218
+ fs2.closeSync(fd);
31219
31219
  fd = undefined;
31220
31220
  }
31221
31221
  return {
@@ -31227,9 +31227,9 @@ var require_tmp = __commonJS((exports, module) => {
31227
31227
  function _rmdirRecursiveSync(root) {
31228
31228
  const dirs = [root];
31229
31229
  do {
31230
- var dir2 = dirs.pop(), deferred = false, files = fs.readdirSync(dir2);
31230
+ var dir2 = dirs.pop(), deferred = false, files = fs2.readdirSync(dir2);
31231
31231
  for (var i = 0, length = files.length;i < length; i++) {
31232
- var file2 = path.join(dir2, files[i]), stat = fs.lstatSync(file2);
31232
+ var file2 = path.join(dir2, files[i]), stat = fs2.lstatSync(file2);
31233
31233
  if (stat.isDirectory()) {
31234
31234
  if (!deferred) {
31235
31235
  deferred = true;
@@ -31237,11 +31237,11 @@ var require_tmp = __commonJS((exports, module) => {
31237
31237
  }
31238
31238
  dirs.push(file2);
31239
31239
  } else {
31240
- fs.unlinkSync(file2);
31240
+ fs2.unlinkSync(file2);
31241
31241
  }
31242
31242
  }
31243
31243
  if (!deferred) {
31244
- fs.rmdirSync(dir2);
31244
+ fs2.rmdirSync(dir2);
31245
31245
  }
31246
31246
  } while (dirs.length !== 0);
31247
31247
  }
@@ -31250,7 +31250,7 @@ var require_tmp = __commonJS((exports, module) => {
31250
31250
  tmpName(opts, function _tmpNameCreated(err, name) {
31251
31251
  if (err)
31252
31252
  return cb(err);
31253
- fs.mkdir(name, opts.mode || DIR_MODE, function _dirCreated(err2) {
31253
+ fs2.mkdir(name, opts.mode || DIR_MODE, function _dirCreated(err2) {
31254
31254
  if (err2)
31255
31255
  return cb(err2);
31256
31256
  cb(null, name, _prepareTmpDirRemoveCallback(name, opts));
@@ -31260,7 +31260,7 @@ var require_tmp = __commonJS((exports, module) => {
31260
31260
  function dirSync(options) {
31261
31261
  var args = _parseArguments(options), opts = args[0];
31262
31262
  const name = tmpNameSync(opts);
31263
- fs.mkdirSync(name, opts.mode || DIR_MODE);
31263
+ fs2.mkdirSync(name, opts.mode || DIR_MODE);
31264
31264
  return {
31265
31265
  name,
31266
31266
  removeCallback: _prepareTmpDirRemoveCallback(name, opts)
@@ -31270,7 +31270,7 @@ var require_tmp = __commonJS((exports, module) => {
31270
31270
  const removeCallback = _prepareRemoveCallback(function _removeCallback(fdPath) {
31271
31271
  try {
31272
31272
  if (0 <= fdPath[0]) {
31273
- fs.closeSync(fdPath[0]);
31273
+ fs2.closeSync(fdPath[0]);
31274
31274
  }
31275
31275
  } catch (e) {
31276
31276
  if (!isEBADF(e) && !isENOENT(e)) {
@@ -31278,7 +31278,7 @@ var require_tmp = __commonJS((exports, module) => {
31278
31278
  }
31279
31279
  }
31280
31280
  try {
31281
- fs.unlinkSync(fdPath[1]);
31281
+ fs2.unlinkSync(fdPath[1]);
31282
31282
  } catch (e) {
31283
31283
  if (!isENOENT(e)) {
31284
31284
  throw e;
@@ -31291,7 +31291,7 @@ var require_tmp = __commonJS((exports, module) => {
31291
31291
  return removeCallback;
31292
31292
  }
31293
31293
  function _prepareTmpDirRemoveCallback(name, opts) {
31294
- const removeFunction = opts.unsafeCleanup ? _rmdirRecursiveSync : fs.rmdirSync.bind(fs);
31294
+ const removeFunction = opts.unsafeCleanup ? _rmdirRecursiveSync : fs2.rmdirSync.bind(fs2);
31295
31295
  const removeCallback = _prepareRemoveCallback(removeFunction, name);
31296
31296
  if (!opts.keep) {
31297
31297
  _removeObjects.unshift(removeCallback);
@@ -32550,13 +32550,13 @@ var require_src = __commonJS((exports, module) => {
32550
32550
 
32551
32551
  // node_modules/tabtab/lib/utils/tabtabDebug.js
32552
32552
  var require_tabtabDebug = __commonJS((exports, module) => {
32553
- var fs = __require("fs");
32553
+ var fs2 = __require("fs");
32554
32554
  var util = __require("util");
32555
32555
  var tabtabDebug = (name) => {
32556
32556
  let debug = require_src()(name);
32557
32557
  if (process.env.TABTAB_DEBUG) {
32558
32558
  const file = process.env.TABTAB_DEBUG;
32559
- const stream = fs.createWriteStream(file, {
32559
+ const stream = fs2.createWriteStream(file, {
32560
32560
  flags: "a+"
32561
32561
  });
32562
32562
  const log = (...args) => {
@@ -32653,14 +32653,14 @@ var require_promisify = __commonJS((exports) => {
32653
32653
  Object.defineProperty(exports, "__esModule", {
32654
32654
  value: true
32655
32655
  });
32656
- exports.promisify = promisify;
32656
+ exports.promisify = promisify2;
32657
32657
  var customArgumentsToken = "__ES6-PROMISIFY--CUSTOM-ARGUMENTS__";
32658
- function promisify(original) {
32658
+ function promisify2(original) {
32659
32659
  if (typeof original !== "function") {
32660
32660
  throw new TypeError("Argument to promisify must be a function");
32661
32661
  }
32662
32662
  var argumentNames = original[customArgumentsToken];
32663
- var ES6Promise = promisify.Promise || Promise;
32663
+ var ES6Promise = promisify2.Promise || Promise;
32664
32664
  if (typeof ES6Promise !== "function") {
32665
32665
  throw new Error("No Promise implementation found; do you need a polyfill?");
32666
32666
  }
@@ -32693,14 +32693,14 @@ var require_promisify = __commonJS((exports) => {
32693
32693
  });
32694
32694
  };
32695
32695
  }
32696
- promisify.argumentNames = customArgumentsToken;
32697
- promisify.Promise = undefined;
32696
+ promisify2.argumentNames = customArgumentsToken;
32697
+ promisify2.Promise = undefined;
32698
32698
  });
32699
32699
 
32700
32700
  // node_modules/mkdirp/index.js
32701
32701
  var require_mkdirp = __commonJS((exports, module) => {
32702
32702
  var path = __require("path");
32703
- var fs = __require("fs");
32703
+ var fs2 = __require("fs");
32704
32704
  var _0777 = parseInt("0777", 8);
32705
32705
  module.exports = mkdirP.mkdirp = mkdirP.mkdirP = mkdirP;
32706
32706
  function mkdirP(p, opts, f, made) {
@@ -32711,7 +32711,7 @@ var require_mkdirp = __commonJS((exports, module) => {
32711
32711
  opts = { mode: opts };
32712
32712
  }
32713
32713
  var mode = opts.mode;
32714
- var xfs = opts.fs || fs;
32714
+ var xfs = opts.fs || fs2;
32715
32715
  if (mode === undefined) {
32716
32716
  mode = _0777;
32717
32717
  }
@@ -32751,7 +32751,7 @@ var require_mkdirp = __commonJS((exports, module) => {
32751
32751
  opts = { mode: opts };
32752
32752
  }
32753
32753
  var mode = opts.mode;
32754
- var xfs = opts.fs || fs;
32754
+ var xfs = opts.fs || fs2;
32755
32755
  if (mode === undefined) {
32756
32756
  mode = _0777;
32757
32757
  }
@@ -32791,10 +32791,10 @@ var require_systemShell = __commonJS((exports, module) => {
32791
32791
 
32792
32792
  // node_modules/tabtab/lib/utils/exists.js
32793
32793
  var require_exists = __commonJS((exports, module) => {
32794
- var fs = __require("fs");
32794
+ var fs2 = __require("fs");
32795
32795
  var untildify = require_untildify();
32796
- var { promisify } = require_promisify();
32797
- var readFile = promisify(fs.readFile);
32796
+ var { promisify: promisify2 } = require_promisify();
32797
+ var readFile = promisify2(fs2.readFile);
32798
32798
  module.exports = async (file) => {
32799
32799
  let fileExists;
32800
32800
  try {
@@ -32838,16 +32838,16 @@ var require_constants = __commonJS((exports, module) => {
32838
32838
  // node_modules/tabtab/lib/installer.js
32839
32839
  var require_installer = __commonJS((exports, module) => {
32840
32840
  var __dirname = "/home/runner/work/bitbucket-cli/bitbucket-cli/node_modules/tabtab/lib";
32841
- var fs = __require("fs");
32841
+ var fs2 = __require("fs");
32842
32842
  var path = __require("path");
32843
32843
  var untildify = require_untildify();
32844
- var { promisify } = require_promisify();
32845
- var mkdirp = promisify(require_mkdirp());
32844
+ var { promisify: promisify2 } = require_promisify();
32845
+ var mkdirp = promisify2(require_mkdirp());
32846
32846
  var { tabtabDebug, systemShell, exists } = require_utils2();
32847
32847
  var debug = tabtabDebug("tabtab:installer");
32848
- var readFile = promisify(fs.readFile);
32849
- var writeFile = promisify(fs.writeFile);
32850
- var unlink = promisify(fs.unlink);
32848
+ var readFile = promisify2(fs2.readFile);
32849
+ var writeFile = promisify2(fs2.writeFile);
32850
+ var unlink = promisify2(fs2.unlink);
32851
32851
  var {
32852
32852
  BASH_LOCATION,
32853
32853
  FISH_LOCATION,
@@ -32907,7 +32907,7 @@ var require_installer = __commonJS((exports, module) => {
32907
32907
  const filepath = untildify(filename);
32908
32908
  debug("Creating directory for %s file", filepath);
32909
32909
  mkdirp(path.dirname(filepath)).then(() => {
32910
- const stream = fs.createWriteStream(filepath, { flags: "a" });
32910
+ const stream = fs2.createWriteStream(filepath, { flags: "a" });
32911
32911
  stream.on("error", reject);
32912
32912
  stream.on("finish", () => resolve());
32913
32913
  debug("Writing to shell configuration file (%s)", filename);
@@ -33258,10 +33258,12 @@ var ServiceTokens = {
33258
33258
  CreatePRCommand: "CreatePRCommand",
33259
33259
  ListPRsCommand: "ListPRsCommand",
33260
33260
  ViewPRCommand: "ViewPRCommand",
33261
+ EditPRCommand: "EditPRCommand",
33261
33262
  MergePRCommand: "MergePRCommand",
33262
33263
  ApprovePRCommand: "ApprovePRCommand",
33263
33264
  DeclinePRCommand: "DeclinePRCommand",
33264
33265
  CheckoutPRCommand: "CheckoutPRCommand",
33266
+ DiffPRCommand: "DiffPRCommand",
33265
33267
  GetConfigCommand: "GetConfigCommand",
33266
33268
  SetConfigCommand: "SetConfigCommand",
33267
33269
  ListConfigCommand: "ListConfigCommand",
@@ -33465,14 +33467,14 @@ class ConfigService {
33465
33467
  if (!configResult.success) {
33466
33468
  return configResult;
33467
33469
  }
33468
- const { username, appPassword } = configResult.value;
33469
- if (!username || !appPassword) {
33470
+ const { username, apiToken } = configResult.value;
33471
+ if (!username || !apiToken) {
33470
33472
  return Result.err(new BBError({
33471
33473
  code: 1001 /* AUTH_REQUIRED */,
33472
33474
  message: "Authentication required. Run 'bb auth login' to authenticate."
33473
33475
  }));
33474
33476
  }
33475
- return Result.ok({ username, appPassword });
33477
+ return Result.ok({ username, apiToken });
33476
33478
  }
33477
33479
  async setCredentials(credentials) {
33478
33480
  const configResult = await this.getConfig();
@@ -33482,7 +33484,7 @@ class ConfigService {
33482
33484
  return this.setConfig({
33483
33485
  ...configResult.value,
33484
33486
  username: credentials.username,
33485
- appPassword: credentials.appPassword
33487
+ apiToken: credentials.apiToken
33486
33488
  });
33487
33489
  }
33488
33490
  async clearConfig() {
@@ -34253,11 +34255,11 @@ class HttpClient {
34253
34255
  if (!credentialsResult.success) {
34254
34256
  return credentialsResult;
34255
34257
  }
34256
- const { username, appPassword } = credentialsResult.value;
34257
- const encoded = Buffer.from(`${username}:${appPassword}`).toString("base64");
34258
+ const { username, apiToken } = credentialsResult.value;
34259
+ const encoded = Buffer.from(`${username}:${apiToken}`).toString("base64");
34258
34260
  return Result.ok(`Basic ${encoded}`);
34259
34261
  }
34260
- async request(method, path, body) {
34262
+ async request(method, path, body, acceptText = false) {
34261
34263
  const authResult = await this.getAuthHeader();
34262
34264
  if (!authResult.success) {
34263
34265
  return authResult;
@@ -34265,7 +34267,7 @@ class HttpClient {
34265
34267
  const headers = {
34266
34268
  Authorization: authResult.value,
34267
34269
  "Content-Type": "application/json",
34268
- Accept: "application/json"
34270
+ Accept: acceptText ? "text/plain" : "application/json"
34269
34271
  };
34270
34272
  const url = `${this.baseUrl}${path}`;
34271
34273
  try {
@@ -34278,7 +34280,7 @@ class HttpClient {
34278
34280
  signal: controller.signal
34279
34281
  });
34280
34282
  clearTimeout(timeoutId);
34281
- return this.handleResponse(response);
34283
+ return acceptText ? this.handleTextResponse(response) : this.handleResponse(response);
34282
34284
  } catch (error) {
34283
34285
  if (error instanceof Error && error.name === "AbortError") {
34284
34286
  return Result.err(new BBError({
@@ -34297,11 +34299,12 @@ class HttpClient {
34297
34299
  }
34298
34300
  async handleResponse(response) {
34299
34301
  if (!response.ok) {
34302
+ const textBody = await response.text();
34300
34303
  let errorBody;
34301
34304
  try {
34302
- errorBody = await response.json();
34305
+ errorBody = JSON.parse(textBody);
34303
34306
  } catch {
34304
- errorBody = await response.text();
34307
+ errorBody = textBody;
34305
34308
  }
34306
34309
  const message = this.extractErrorMessage(errorBody, response.statusText);
34307
34310
  return Result.err(new APIError(message, response.status, errorBody, {
@@ -34322,6 +34325,34 @@ class HttpClient {
34322
34325
  }));
34323
34326
  }
34324
34327
  }
34328
+ async handleTextResponse(response) {
34329
+ if (!response.ok) {
34330
+ const textBody = await response.text();
34331
+ let errorBody;
34332
+ try {
34333
+ errorBody = JSON.parse(textBody);
34334
+ } catch {
34335
+ errorBody = textBody;
34336
+ }
34337
+ const message = this.extractErrorMessage(errorBody, response.statusText);
34338
+ return Result.err(new APIError(message, response.status, errorBody, {
34339
+ url: response.url
34340
+ }));
34341
+ }
34342
+ if (response.status === 204) {
34343
+ return Result.ok(undefined);
34344
+ }
34345
+ try {
34346
+ const data = await response.text();
34347
+ return Result.ok(data);
34348
+ } catch (error) {
34349
+ return Result.err(new BBError({
34350
+ code: 2001 /* API_REQUEST_FAILED */,
34351
+ message: "Failed to read response text",
34352
+ cause: error instanceof Error ? error : undefined
34353
+ }));
34354
+ }
34355
+ }
34325
34356
  extractErrorMessage(body, fallback) {
34326
34357
  if (typeof body === "object" && body !== null) {
34327
34358
  const obj = body;
@@ -34340,6 +34371,9 @@ class HttpClient {
34340
34371
  async get(path) {
34341
34372
  return this.request("GET", path);
34342
34373
  }
34374
+ async getText(path) {
34375
+ return this.request("GET", path, undefined, true);
34376
+ }
34343
34377
  async post(path, body) {
34344
34378
  return this.request("POST", path, body);
34345
34379
  }
@@ -34399,6 +34433,9 @@ class PullRequestRepository {
34399
34433
  async create(workspace, repoSlug, request) {
34400
34434
  return this.httpClient.post(this.buildPath(workspace, repoSlug), request);
34401
34435
  }
34436
+ async update(workspace, repoSlug, id, request) {
34437
+ return this.httpClient.put(this.buildPath(workspace, repoSlug, `/${id}`), request);
34438
+ }
34402
34439
  async merge(workspace, repoSlug, id, request) {
34403
34440
  return this.httpClient.post(this.buildPath(workspace, repoSlug, `/${id}/merge`), request);
34404
34441
  }
@@ -34408,6 +34445,12 @@ class PullRequestRepository {
34408
34445
  async decline(workspace, repoSlug, id) {
34409
34446
  return this.httpClient.post(this.buildPath(workspace, repoSlug, `/${id}/decline`));
34410
34447
  }
34448
+ async getDiff(workspace, repoSlug, id) {
34449
+ return this.httpClient.getText(this.buildPath(workspace, repoSlug, `/${id}/diff`));
34450
+ }
34451
+ async getDiffstat(workspace, repoSlug, id) {
34452
+ return this.httpClient.get(this.buildPath(workspace, repoSlug, `/${id}/diffstat`));
34453
+ }
34411
34454
  }
34412
34455
  // src/core/base-command.ts
34413
34456
  class BaseCommand {
@@ -34446,7 +34489,7 @@ class LoginCommand extends BaseCommand {
34446
34489
  configService;
34447
34490
  userRepositoryFactory;
34448
34491
  name = "login";
34449
- description = "Authenticate with Bitbucket using an app password";
34492
+ description = "Authenticate with Bitbucket using an API token";
34450
34493
  constructor(configService, userRepositoryFactory, output) {
34451
34494
  super(output);
34452
34495
  this.configService = configService;
@@ -34454,18 +34497,18 @@ class LoginCommand extends BaseCommand {
34454
34497
  }
34455
34498
  async execute(options, context) {
34456
34499
  const username = options.username || process.env.BB_USERNAME;
34457
- const appPassword = options.password || process.env.BB_APP_PASSWORD;
34500
+ const apiToken = options.password || process.env.BB_API_TOKEN;
34458
34501
  if (!username) {
34459
34502
  const error = Result.err(new ValidationError("username", "Username is required. Use --username option or set BB_USERNAME environment variable."));
34460
34503
  this.handleResult(error, context);
34461
34504
  return error;
34462
34505
  }
34463
- if (!appPassword) {
34464
- const error = Result.err(new ValidationError("password", "App password is required. Use --password option or set BB_APP_PASSWORD environment variable."));
34506
+ if (!apiToken) {
34507
+ const error = Result.err(new ValidationError("password", "API token is required. Use --password option or set BB_API_TOKEN environment variable."));
34465
34508
  this.handleResult(error, context);
34466
34509
  return error;
34467
34510
  }
34468
- const setResult = await this.configService.setCredentials({ username, appPassword });
34511
+ const setResult = await this.configService.setCredentials({ username, apiToken });
34469
34512
  if (!setResult.success) {
34470
34513
  this.handleResult(setResult, context);
34471
34514
  return setResult;
@@ -34520,7 +34563,7 @@ class StatusCommand extends BaseCommand {
34520
34563
  return configResult;
34521
34564
  }
34522
34565
  const config = configResult.value;
34523
- if (!config.username || !config.appPassword) {
34566
+ if (!config.username || !config.apiToken) {
34524
34567
  const status2 = { authenticated: false };
34525
34568
  this.handleResult(Result.ok(status2), context, () => {
34526
34569
  this.output.info("Not logged in");
@@ -34574,8 +34617,8 @@ class TokenCommand extends BaseCommand {
34574
34617
  }
34575
34618
  return credentialsResult;
34576
34619
  }
34577
- const { username, appPassword } = credentialsResult.value;
34578
- const token = Buffer.from(`${username}:${appPassword}`).toString("base64");
34620
+ const { username, apiToken } = credentialsResult.value;
34621
+ const token = Buffer.from(`${username}:${apiToken}`).toString("base64");
34579
34622
  this.output.text(token);
34580
34623
  return Result.ok(token);
34581
34624
  }
@@ -35032,6 +35075,98 @@ class ViewPRCommand extends BaseCommand {
35032
35075
  }
35033
35076
  }
35034
35077
 
35078
+ // src/commands/pr/edit.command.ts
35079
+ import * as fs from "fs";
35080
+ class EditPRCommand extends BaseCommand {
35081
+ prRepository;
35082
+ contextService;
35083
+ gitService;
35084
+ name = "edit";
35085
+ description = "Edit a pull request";
35086
+ constructor(prRepository, contextService, gitService, output) {
35087
+ super(output);
35088
+ this.prRepository = prRepository;
35089
+ this.contextService = contextService;
35090
+ this.gitService = gitService;
35091
+ }
35092
+ async execute(options, context) {
35093
+ const repoContextResult = await this.contextService.requireRepoContext({
35094
+ ...context.globalOptions,
35095
+ ...options
35096
+ });
35097
+ if (!repoContextResult.success) {
35098
+ this.handleResult(repoContextResult, context);
35099
+ return repoContextResult;
35100
+ }
35101
+ const { workspace, repoSlug } = repoContextResult.value;
35102
+ let prId;
35103
+ if (options.id) {
35104
+ prId = parseInt(options.id, 10);
35105
+ } else {
35106
+ const branchResult = await this.gitService.getCurrentBranch();
35107
+ if (!branchResult.success) {
35108
+ this.handleResult(branchResult, context);
35109
+ return branchResult;
35110
+ }
35111
+ const currentBranch = branchResult.value;
35112
+ const prsResult = await this.prRepository.list(workspace, repoSlug, "OPEN");
35113
+ if (!prsResult.success) {
35114
+ this.handleResult(prsResult, context);
35115
+ return prsResult;
35116
+ }
35117
+ const matchingPR = prsResult.value.values.find((pr) => pr.source.branch.name === currentBranch);
35118
+ if (!matchingPR) {
35119
+ const error = new ValidationError("id", `No open pull request found for current branch '${currentBranch}'. Specify a PR ID explicitly.`);
35120
+ this.output.error(error.message);
35121
+ if (true) {
35122
+ process.exitCode = 1;
35123
+ }
35124
+ return Result.err(error);
35125
+ }
35126
+ prId = matchingPR.id;
35127
+ }
35128
+ let body = options.body;
35129
+ if (options.bodyFile) {
35130
+ try {
35131
+ body = fs.readFileSync(options.bodyFile, "utf-8");
35132
+ } catch (err) {
35133
+ const error = new ValidationError("bodyFile", `Failed to read file '${options.bodyFile}': ${err instanceof Error ? err.message : "Unknown error"}`);
35134
+ this.output.error(error.message);
35135
+ if (true) {
35136
+ process.exitCode = 1;
35137
+ }
35138
+ return Result.err(error);
35139
+ }
35140
+ }
35141
+ if (!options.title && !body) {
35142
+ const error = new ValidationError("title", "At least one of --title or --body (or --body-file) is required.");
35143
+ this.output.error(error.message);
35144
+ if (true) {
35145
+ process.exitCode = 1;
35146
+ }
35147
+ return Result.err(error);
35148
+ }
35149
+ const request = {};
35150
+ if (options.title) {
35151
+ request.title = options.title;
35152
+ }
35153
+ if (body) {
35154
+ request.description = body;
35155
+ }
35156
+ const result = await this.prRepository.update(workspace, repoSlug, prId, request);
35157
+ this.handleResult(result, context, (pr) => {
35158
+ this.output.success(`Updated pull request #${pr.id}`);
35159
+ this.output.text(` ${source_default.dim("Title:")} ${pr.title}`);
35160
+ if (pr.description) {
35161
+ const truncatedDesc = pr.description.length > 100 ? pr.description.substring(0, 100) + "..." : pr.description;
35162
+ this.output.text(` ${source_default.dim("Description:")} ${truncatedDesc}`);
35163
+ }
35164
+ this.output.text(` ${source_default.dim("URL:")} ${pr.links.html.href}`);
35165
+ });
35166
+ return result;
35167
+ }
35168
+ }
35169
+
35035
35170
  // src/commands/pr/merge.command.ts
35036
35171
  class MergePRCommand extends BaseCommand {
35037
35172
  prRepository;
@@ -35193,6 +35328,221 @@ class CheckoutPRCommand extends BaseCommand {
35193
35328
  }
35194
35329
  }
35195
35330
 
35331
+ // src/commands/pr/diff.command.ts
35332
+ import { exec } from "child_process";
35333
+ import { promisify } from "util";
35334
+ var execAsync = promisify(exec);
35335
+
35336
+ class DiffPRCommand extends BaseCommand {
35337
+ prRepository;
35338
+ contextService;
35339
+ gitService;
35340
+ name = "diff";
35341
+ description = "View pull request diff";
35342
+ constructor(prRepository, contextService, gitService, output) {
35343
+ super(output);
35344
+ this.prRepository = prRepository;
35345
+ this.contextService = contextService;
35346
+ this.gitService = gitService;
35347
+ }
35348
+ async execute(options, context) {
35349
+ const repoContextResult = await this.contextService.requireRepoContext({
35350
+ ...context.globalOptions,
35351
+ ...options
35352
+ });
35353
+ if (!repoContextResult.success) {
35354
+ this.handleResult(repoContextResult, context);
35355
+ return repoContextResult;
35356
+ }
35357
+ const { workspace, repoSlug } = repoContextResult.value;
35358
+ let prId;
35359
+ if (options.id) {
35360
+ prId = parseInt(options.id, 10);
35361
+ if (isNaN(prId)) {
35362
+ const error = new BBError({
35363
+ code: 5002 /* VALIDATION_INVALID */,
35364
+ message: "Invalid PR ID"
35365
+ });
35366
+ this.handleResult(Result.err(error), context);
35367
+ return Result.err(error);
35368
+ }
35369
+ } else {
35370
+ const currentBranchResult = await this.gitService.getCurrentBranch();
35371
+ if (!currentBranchResult.success) {
35372
+ const error = new BBError({
35373
+ code: 5002 /* VALIDATION_INVALID */,
35374
+ message: "No PR ID provided and could not determine current branch"
35375
+ });
35376
+ this.handleResult(Result.err(error), context);
35377
+ return Result.err(error);
35378
+ }
35379
+ const currentBranch = currentBranchResult.value;
35380
+ const prsResult = await this.prRepository.list(workspace, repoSlug, "OPEN", 100);
35381
+ if (!prsResult.success) {
35382
+ this.handleResult(prsResult, context);
35383
+ return prsResult;
35384
+ }
35385
+ const pr = prsResult.value.values.find((p) => p.source.branch.name === currentBranch);
35386
+ if (!pr) {
35387
+ const error = new BBError({
35388
+ code: 5002 /* VALIDATION_INVALID */,
35389
+ message: `No open pull request found for branch "${currentBranch}"`
35390
+ });
35391
+ this.handleResult(Result.err(error), context);
35392
+ return Result.err(error);
35393
+ }
35394
+ prId = pr.id;
35395
+ }
35396
+ if (options.web) {
35397
+ const prResult = await this.prRepository.get(workspace, repoSlug, prId);
35398
+ if (!prResult.success) {
35399
+ this.handleResult(prResult, context);
35400
+ return prResult;
35401
+ }
35402
+ const diffUrl = prResult.value.links.diff.href;
35403
+ const webUrl = diffUrl.replace(/api\.bitbucket\.org\/2\.0\/repositories\/(.*?)\/pullrequests\/(\d+)\/diff/, "bitbucket.org/$1/pull-requests/$2/diff");
35404
+ return this.openInBrowser(webUrl, context);
35405
+ }
35406
+ if (options.stat) {
35407
+ return this.showStat(workspace, repoSlug, prId, context);
35408
+ }
35409
+ if (options.nameOnly) {
35410
+ return this.showNameOnly(workspace, repoSlug, prId, context);
35411
+ }
35412
+ return this.showDiff(workspace, repoSlug, prId, options, context);
35413
+ }
35414
+ async openInBrowser(url, context) {
35415
+ if (context.globalOptions.json) {
35416
+ this.output.json({ url });
35417
+ return Result.ok({ diff: url });
35418
+ }
35419
+ this.output.info(`Opening ${url} in your browser...`);
35420
+ try {
35421
+ const platform = process.platform;
35422
+ let command;
35423
+ if (platform === "darwin") {
35424
+ command = `open "${url}"`;
35425
+ } else if (platform === "win32") {
35426
+ command = `start "" "${url}"`;
35427
+ } else {
35428
+ command = `xdg-open "${url}"`;
35429
+ }
35430
+ await execAsync(command);
35431
+ return Result.ok({ diff: url });
35432
+ } catch (error) {
35433
+ const bbError = new BBError({
35434
+ code: 3002 /* GIT_COMMAND_FAILED */,
35435
+ message: "Failed to open browser",
35436
+ cause: error instanceof Error ? error : undefined
35437
+ });
35438
+ this.handleResult(Result.err(bbError), context);
35439
+ return Result.err(bbError);
35440
+ }
35441
+ }
35442
+ async showStat(workspace, repoSlug, prId, context) {
35443
+ const diffstatResult = await this.prRepository.getDiffstat(workspace, repoSlug, prId);
35444
+ if (!diffstatResult.success) {
35445
+ this.handleResult(diffstatResult, context);
35446
+ return diffstatResult;
35447
+ }
35448
+ const diffstat = diffstatResult.value;
35449
+ const files = diffstat.values.map((file) => {
35450
+ const path = file.new?.path || file.old?.path || "unknown";
35451
+ return {
35452
+ path,
35453
+ additions: file.lines_added,
35454
+ deletions: file.lines_removed
35455
+ };
35456
+ });
35457
+ const totalAdditions = files.reduce((sum, f) => sum + f.additions, 0);
35458
+ const totalDeletions = files.reduce((sum, f) => sum + f.deletions, 0);
35459
+ const filesChanged = files.length;
35460
+ const result = {
35461
+ stat: {
35462
+ filesChanged,
35463
+ insertions: totalAdditions,
35464
+ deletions: totalDeletions,
35465
+ files
35466
+ }
35467
+ };
35468
+ this.handleResult(Result.ok(result), context, () => {
35469
+ for (const file of files) {
35470
+ const additions = file.additions > 0 ? source_default.green(`+${file.additions}`) : "";
35471
+ const deletions = file.deletions > 0 ? source_default.red(`-${file.deletions}`) : "";
35472
+ const stats = [additions, deletions].filter(Boolean).join(" ");
35473
+ this.output.text(`${file.path} ${stats ? `| ${stats}` : ""}`);
35474
+ }
35475
+ this.output.text("");
35476
+ const summary = [
35477
+ `${filesChanged} file${filesChanged !== 1 ? "s" : ""} changed`,
35478
+ totalAdditions > 0 ? source_default.green(`${totalAdditions} insertion${totalAdditions !== 1 ? "s" : ""}(+)`) : null,
35479
+ totalDeletions > 0 ? source_default.red(`${totalDeletions} deletion${totalDeletions !== 1 ? "s" : ""}(-)`) : null
35480
+ ].filter(Boolean).join(", ");
35481
+ this.output.text(summary);
35482
+ });
35483
+ return Result.ok(result);
35484
+ }
35485
+ async showNameOnly(workspace, repoSlug, prId, context) {
35486
+ const diffstatResult = await this.prRepository.getDiffstat(workspace, repoSlug, prId);
35487
+ if (!diffstatResult.success) {
35488
+ this.handleResult(diffstatResult, context);
35489
+ return diffstatResult;
35490
+ }
35491
+ const diffstat = diffstatResult.value;
35492
+ const fileNames = diffstat.values.map((file) => file.new?.path || file.old?.path || "unknown");
35493
+ const result = {
35494
+ diff: fileNames.join(`
35495
+ `)
35496
+ };
35497
+ this.handleResult(Result.ok(result), context, () => {
35498
+ for (const fileName of fileNames) {
35499
+ this.output.text(fileName);
35500
+ }
35501
+ });
35502
+ return Result.ok(result);
35503
+ }
35504
+ async showDiff(workspace, repoSlug, prId, options, context) {
35505
+ const diffResult = await this.prRepository.getDiff(workspace, repoSlug, prId);
35506
+ if (!diffResult.success) {
35507
+ this.handleResult(diffResult, context);
35508
+ return diffResult;
35509
+ }
35510
+ const diff = diffResult.value;
35511
+ const result = { diff };
35512
+ this.handleResult(Result.ok(result), context, () => {
35513
+ const shouldColorize = this.shouldColorize(options.color);
35514
+ const colorizedDiff = shouldColorize ? this.colorizeDiff(diff) : diff;
35515
+ this.output.text(colorizedDiff);
35516
+ });
35517
+ return Result.ok(result);
35518
+ }
35519
+ shouldColorize(colorOption) {
35520
+ if (!colorOption || colorOption === "auto") {
35521
+ return process.stdout.isTTY ?? false;
35522
+ }
35523
+ return colorOption === "always";
35524
+ }
35525
+ colorizeDiff(diff) {
35526
+ const lines = diff.split(`
35527
+ `);
35528
+ return lines.map((line) => {
35529
+ if (line.startsWith("+") && !line.startsWith("+++")) {
35530
+ return source_default.green(line);
35531
+ } else if (line.startsWith("-") && !line.startsWith("---")) {
35532
+ return source_default.red(line);
35533
+ } else if (line.startsWith("@@")) {
35534
+ return source_default.cyan(line);
35535
+ } else if (line.startsWith("diff --git")) {
35536
+ return source_default.bold(line);
35537
+ } else if (line.startsWith("index ") || line.startsWith("---") || line.startsWith("+++")) {
35538
+ return source_default.dim(line);
35539
+ }
35540
+ return line;
35541
+ }).join(`
35542
+ `);
35543
+ }
35544
+ }
35545
+
35196
35546
  // src/types/config.ts
35197
35547
  var SETTABLE_CONFIG_KEYS = ["defaultWorkspace"];
35198
35548
  var READABLE_CONFIG_KEYS = ["username", "defaultWorkspace"];
@@ -35208,7 +35558,7 @@ class GetConfigCommand extends BaseCommand {
35208
35558
  configService;
35209
35559
  name = "get";
35210
35560
  description = "Get a configuration value";
35211
- static HIDDEN_KEYS = ["appPassword"];
35561
+ static HIDDEN_KEYS = ["apiToken"];
35212
35562
  constructor(configService, output) {
35213
35563
  super(output);
35214
35564
  this.configService = configService;
@@ -35253,7 +35603,7 @@ class SetConfigCommand extends BaseCommand {
35253
35603
  configService;
35254
35604
  name = "set";
35255
35605
  description = "Set a configuration value";
35256
- static PROTECTED_KEYS = ["username", "appPassword"];
35606
+ static PROTECTED_KEYS = ["username", "apiToken"];
35257
35607
  constructor(configService, output) {
35258
35608
  super(output);
35259
35609
  this.configService = configService;
@@ -35309,7 +35659,7 @@ class ListConfigCommand extends BaseCommand {
35309
35659
  const displayConfig = {
35310
35660
  username: config.username || "",
35311
35661
  defaultWorkspace: config.defaultWorkspace || "",
35312
- appPassword: config.appPassword ? "********" : ""
35662
+ apiToken: config.apiToken ? "********" : ""
35313
35663
  };
35314
35664
  this.handleResult(Result.ok(displayConfig), context, (data) => {
35315
35665
  this.output.text(source_default.dim(`Config file: ${this.configService.getConfigPath()}`));
@@ -35470,6 +35820,13 @@ function bootstrap() {
35470
35820
  const output = container.resolve(ServiceTokens.OutputService);
35471
35821
  return new ViewPRCommand(prRepo, contextService, output);
35472
35822
  });
35823
+ container.register(ServiceTokens.EditPRCommand, () => {
35824
+ const prRepo = container.resolve(ServiceTokens.PullRequestRepository);
35825
+ const contextService = container.resolve(ServiceTokens.ContextService);
35826
+ const gitService = container.resolve(ServiceTokens.GitService);
35827
+ const output = container.resolve(ServiceTokens.OutputService);
35828
+ return new EditPRCommand(prRepo, contextService, gitService, output);
35829
+ });
35473
35830
  container.register(ServiceTokens.MergePRCommand, () => {
35474
35831
  const prRepo = container.resolve(ServiceTokens.PullRequestRepository);
35475
35832
  const contextService = container.resolve(ServiceTokens.ContextService);
@@ -35495,6 +35852,13 @@ function bootstrap() {
35495
35852
  const output = container.resolve(ServiceTokens.OutputService);
35496
35853
  return new CheckoutPRCommand(prRepo, contextService, gitService, output);
35497
35854
  });
35855
+ container.register(ServiceTokens.DiffPRCommand, () => {
35856
+ const prRepo = container.resolve(ServiceTokens.PullRequestRepository);
35857
+ const contextService = container.resolve(ServiceTokens.ContextService);
35858
+ const gitService = container.resolve(ServiceTokens.GitService);
35859
+ const output = container.resolve(ServiceTokens.OutputService);
35860
+ return new DiffPRCommand(prRepo, contextService, gitService, output);
35861
+ });
35498
35862
  container.register(ServiceTokens.GetConfigCommand, () => {
35499
35863
  const configService = container.resolve(ServiceTokens.ConfigService);
35500
35864
  const output = container.resolve(ServiceTokens.OutputService);
@@ -35545,7 +35909,7 @@ if (process.argv.includes("--get-yargs-completions") || process.env.COMP_LINE) {
35545
35909
  } else if (env2.prev === "repo") {
35546
35910
  completions.push("clone", "create", "list", "view", "delete");
35547
35911
  } else if (env2.prev === "pr") {
35548
- completions.push("create", "list", "view", "merge", "approve", "decline", "checkout");
35912
+ completions.push("create", "list", "view", "edit", "merge", "approve", "decline", "checkout", "diff");
35549
35913
  } else if (env2.prev === "config") {
35550
35914
  completions.push("get", "set", "list");
35551
35915
  } else if (env2.prev === "completion") {
@@ -35576,7 +35940,7 @@ function withGlobalOptions(options, context) {
35576
35940
  var cli = new Command;
35577
35941
  cli.name("bb").description("A command-line interface for Bitbucket Cloud").version(pkg.version).option("--json", "Output as JSON").option("-w, --workspace <workspace>", "Specify workspace").option("-r, --repo <repo>", "Specify repository");
35578
35942
  var authCmd = new Command("auth").description("Authenticate with Bitbucket");
35579
- authCmd.command("login").description("Authenticate with Bitbucket using an app password").option("-u, --username <username>", "Bitbucket username").option("-p, --password <password>", "Bitbucket app password").action(async (options) => {
35943
+ authCmd.command("login").description("Authenticate with Bitbucket using an API token").option("-u, --username <username>", "Bitbucket username").option("-p, --password <password>", "Bitbucket API token").action(async (options) => {
35580
35944
  const cmd = container.resolve(ServiceTokens.LoginCommand);
35581
35945
  const result = await cmd.execute(options, createContext(cli));
35582
35946
  if (!result.success) {
@@ -35671,6 +36035,14 @@ prCmd.command("view <id>").description("View pull request details").action(async
35671
36035
  process.exit(1);
35672
36036
  }
35673
36037
  });
36038
+ prCmd.command("edit [id]").description("Edit a pull request").option("-t, --title <title>", "New pull request title").option("-b, --body <body>", "New pull request description").option("-F, --body-file <file>", "Read description from file").action(async (id, options) => {
36039
+ const cmd = container.resolve(ServiceTokens.EditPRCommand);
36040
+ const context = createContext(cli);
36041
+ const result = await cmd.execute(withGlobalOptions({ id, ...options }, context), context);
36042
+ if (!result.success) {
36043
+ process.exit(1);
36044
+ }
36045
+ });
35674
36046
  prCmd.command("merge <id>").description("Merge a pull request").option("-m, --message <message>", "Merge commit message").option("--close-source-branch", "Delete the source branch after merging").option("--strategy <strategy>", "Merge strategy (merge_commit, squash, fast_forward)").action(async (id, options) => {
35675
36047
  const cmd = container.resolve(ServiceTokens.MergePRCommand);
35676
36048
  const context = createContext(cli);
@@ -35703,6 +36075,14 @@ prCmd.command("checkout <id>").description("Checkout a pull request locally").ac
35703
36075
  process.exit(1);
35704
36076
  }
35705
36077
  });
36078
+ prCmd.command("diff [id]").description("View pull request diff").option("--color <when>", "Colorize output (auto, always, never)", "auto").option("--name-only", "Show only names of changed files").option("--stat", "Show diffstat").option("-w, --web", "Open diff in web browser").action(async (id, options) => {
36079
+ const cmd = container.resolve(ServiceTokens.DiffPRCommand);
36080
+ const context = createContext(cli);
36081
+ const result = await cmd.execute(withGlobalOptions({ id, ...options }, context), context);
36082
+ if (!result.success) {
36083
+ process.exit(1);
36084
+ }
36085
+ });
35706
36086
  cli.addCommand(prCmd);
35707
36087
  var configCmd = new Command("config").description("Manage configuration");
35708
36088
  configCmd.command("get <key>").description("Get a configuration value").action(async (key) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pilatos/bitbucket-cli",
3
- "version": "0.3.2",
3
+ "version": "1.1.0",
4
4
  "description": "A command-line interface for Bitbucket Cloud",
5
5
  "author": "",
6
6
  "license": "MIT",