@solaqua/gji 0.8.1 → 0.9.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 (49) hide show
  1. package/README.md +24 -30
  2. package/dist/browser.d.ts +7 -0
  3. package/dist/browser.js +37 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +59 -4
  6. package/dist/doctor.d.ts +28 -0
  7. package/dist/doctor.js +454 -0
  8. package/dist/gji-bundle.mjs +2453 -350
  9. package/dist/go.d.ts +2 -1
  10. package/dist/go.js +4 -1
  11. package/dist/index.js +12 -6
  12. package/dist/init.d.ts +15 -0
  13. package/dist/init.js +232 -16
  14. package/dist/open.d.ts +2 -1
  15. package/dist/open.js +2 -1
  16. package/dist/pr-open.d.ts +19 -0
  17. package/dist/pr-open.js +290 -0
  18. package/dist/pull-requests.d.ts +33 -0
  19. package/dist/pull-requests.js +384 -0
  20. package/dist/repo-registry.d.ts +5 -0
  21. package/dist/repo-registry.js +87 -14
  22. package/dist/shell-completion.js +278 -10
  23. package/dist/shell-setup.d.ts +9 -0
  24. package/dist/shell-setup.js +56 -0
  25. package/dist/warp.d.ts +3 -0
  26. package/dist/warp.js +8 -3
  27. package/dist/worktree-picker.d.ts +12 -1
  28. package/dist/worktree-picker.js +84 -6
  29. package/man/man1/gji-back.1 +1 -1
  30. package/man/man1/gji-clean.1 +1 -1
  31. package/man/man1/gji-completion.1 +1 -1
  32. package/man/man1/gji-config.1 +1 -1
  33. package/man/man1/gji-doctor.1 +19 -0
  34. package/man/man1/gji-go.1 +1 -1
  35. package/man/man1/gji-history.1 +1 -1
  36. package/man/man1/gji-init.1 +6 -3
  37. package/man/man1/gji-ls.1 +1 -1
  38. package/man/man1/gji-new.1 +1 -1
  39. package/man/man1/gji-open.1 +1 -1
  40. package/man/man1/gji-pr.1 +5 -1
  41. package/man/man1/gji-remove.1 +1 -1
  42. package/man/man1/gji-root.1 +1 -1
  43. package/man/man1/gji-run-hook.1 +1 -1
  44. package/man/man1/gji-status.1 +1 -1
  45. package/man/man1/gji-sync-files.1 +1 -1
  46. package/man/man1/gji-sync.1 +1 -1
  47. package/man/man1/gji-warp.1 +1 -1
  48. package/man/man1/gji.1 +6 -2
  49. package/package.json +1 -1
@@ -3462,10 +3462,10 @@ var require_commander = __commonJS({
3462
3462
  // node_modules/.pnpm/graceful-fs@4.2.11/node_modules/graceful-fs/polyfills.js
3463
3463
  var require_polyfills = __commonJS({
3464
3464
  "node_modules/.pnpm/graceful-fs@4.2.11/node_modules/graceful-fs/polyfills.js"(exports, module) {
3465
- var constants2 = __require("constants");
3465
+ var constants6 = __require("constants");
3466
3466
  var origCwd = process.cwd;
3467
3467
  var cwd = null;
3468
- var platform2 = process.env.GRACEFUL_FS_PLATFORM || process.platform;
3468
+ var platform3 = process.env.GRACEFUL_FS_PLATFORM || process.platform;
3469
3469
  process.cwd = function() {
3470
3470
  if (!cwd)
3471
3471
  cwd = origCwd.call(process);
@@ -3486,7 +3486,7 @@ var require_polyfills = __commonJS({
3486
3486
  var chdir;
3487
3487
  module.exports = patch;
3488
3488
  function patch(fs5) {
3489
- if (constants2.hasOwnProperty("O_SYMLINK") && process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) {
3489
+ if (constants6.hasOwnProperty("O_SYMLINK") && process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) {
3490
3490
  patchLchmod(fs5);
3491
3491
  }
3492
3492
  if (!fs5.lutimes) {
@@ -3524,9 +3524,9 @@ var require_polyfills = __commonJS({
3524
3524
  fs5.lchownSync = function() {
3525
3525
  };
3526
3526
  }
3527
- if (platform2 === "win32") {
3527
+ if (platform3 === "win32") {
3528
3528
  fs5.rename = typeof fs5.rename !== "function" ? fs5.rename : (function(fs$rename) {
3529
- function rename(from, to, cb) {
3529
+ function rename2(from, to, cb) {
3530
3530
  var start = Date.now();
3531
3531
  var backoff = 0;
3532
3532
  fs$rename(from, to, function CB(er) {
@@ -3546,8 +3546,8 @@ var require_polyfills = __commonJS({
3546
3546
  if (cb) cb(er);
3547
3547
  });
3548
3548
  }
3549
- if (Object.setPrototypeOf) Object.setPrototypeOf(rename, fs$rename);
3550
- return rename;
3549
+ if (Object.setPrototypeOf) Object.setPrototypeOf(rename2, fs$rename);
3550
+ return rename2;
3551
3551
  })(fs5.rename);
3552
3552
  }
3553
3553
  fs5.read = typeof fs5.read !== "function" ? fs5.read : (function(fs$read) {
@@ -3588,7 +3588,7 @@ var require_polyfills = __commonJS({
3588
3588
  fs6.lchmod = function(path9, mode, callback) {
3589
3589
  fs6.open(
3590
3590
  path9,
3591
- constants2.O_WRONLY | constants2.O_SYMLINK,
3591
+ constants6.O_WRONLY | constants6.O_SYMLINK,
3592
3592
  mode,
3593
3593
  function(err, fd) {
3594
3594
  if (err) {
@@ -3604,7 +3604,7 @@ var require_polyfills = __commonJS({
3604
3604
  );
3605
3605
  };
3606
3606
  fs6.lchmodSync = function(path9, mode) {
3607
- var fd = fs6.openSync(path9, constants2.O_WRONLY | constants2.O_SYMLINK, mode);
3607
+ var fd = fs6.openSync(path9, constants6.O_WRONLY | constants6.O_SYMLINK, mode);
3608
3608
  var threw = true;
3609
3609
  var ret;
3610
3610
  try {
@@ -3624,9 +3624,9 @@ var require_polyfills = __commonJS({
3624
3624
  };
3625
3625
  }
3626
3626
  function patchLutimes(fs6) {
3627
- if (constants2.hasOwnProperty("O_SYMLINK") && fs6.futimes) {
3627
+ if (constants6.hasOwnProperty("O_SYMLINK") && fs6.futimes) {
3628
3628
  fs6.lutimes = function(path9, at, mt, cb) {
3629
- fs6.open(path9, constants2.O_SYMLINK, function(er, fd) {
3629
+ fs6.open(path9, constants6.O_SYMLINK, function(er, fd) {
3630
3630
  if (er) {
3631
3631
  if (cb) cb(er);
3632
3632
  return;
@@ -3639,7 +3639,7 @@ var require_polyfills = __commonJS({
3639
3639
  });
3640
3640
  };
3641
3641
  fs6.lutimesSync = function(path9, at, mt) {
3642
- var fd = fs6.openSync(path9, constants2.O_SYMLINK);
3642
+ var fd = fs6.openSync(path9, constants6.O_SYMLINK);
3643
3643
  var ret;
3644
3644
  var threw = true;
3645
3645
  try {
@@ -3951,8 +3951,8 @@ var require_graceful_fs = __commonJS({
3951
3951
  fs6.createReadStream = createReadStream;
3952
3952
  fs6.createWriteStream = createWriteStream;
3953
3953
  var fs$readFile = fs6.readFile;
3954
- fs6.readFile = readFile5;
3955
- function readFile5(path9, options, cb) {
3954
+ fs6.readFile = readFile6;
3955
+ function readFile6(path9, options, cb) {
3956
3956
  if (typeof options === "function")
3957
3957
  cb = options, options = null;
3958
3958
  return go$readFile(path9, options, cb);
@@ -5386,7 +5386,7 @@ var require_minimist = __commonJS({
5386
5386
  var require_rc = __commonJS({
5387
5387
  "node_modules/.pnpm/rc@1.2.8/node_modules/rc/index.js"(exports, module) {
5388
5388
  var cc = require_utils();
5389
- var join10 = __require("path").join;
5389
+ var join11 = __require("path").join;
5390
5390
  var deepExtend = require_deep_extend();
5391
5391
  var etc = "/etc";
5392
5392
  var win = process.platform === "win32";
@@ -5411,15 +5411,15 @@ var require_rc = __commonJS({
5411
5411
  }
5412
5412
  if (!win)
5413
5413
  [
5414
- join10(etc, name, "config"),
5415
- join10(etc, name + "rc")
5414
+ join11(etc, name, "config"),
5415
+ join11(etc, name + "rc")
5416
5416
  ].forEach(addConfigFile);
5417
5417
  if (home)
5418
5418
  [
5419
- join10(home, ".config", name, "config"),
5420
- join10(home, ".config", name),
5421
- join10(home, "." + name, "config"),
5422
- join10(home, "." + name + "rc")
5419
+ join11(home, ".config", name, "config"),
5420
+ join11(home, ".config", name),
5421
+ join11(home, "." + name, "config"),
5422
+ join11(home, "." + name + "rc")
5423
5423
  ].forEach(addConfigFile);
5424
5424
  addConfigFile(cc.find("." + name + "rc"));
5425
5425
  if (env5.config) addConfigFile(env5.config);
@@ -5436,10 +5436,10 @@ var require_rc = __commonJS({
5436
5436
  // node_modules/.pnpm/graceful-fs@4.2.10/node_modules/graceful-fs/polyfills.js
5437
5437
  var require_polyfills2 = __commonJS({
5438
5438
  "node_modules/.pnpm/graceful-fs@4.2.10/node_modules/graceful-fs/polyfills.js"(exports, module) {
5439
- var constants2 = __require("constants");
5439
+ var constants6 = __require("constants");
5440
5440
  var origCwd = process.cwd;
5441
5441
  var cwd = null;
5442
- var platform2 = process.env.GRACEFUL_FS_PLATFORM || process.platform;
5442
+ var platform3 = process.env.GRACEFUL_FS_PLATFORM || process.platform;
5443
5443
  process.cwd = function() {
5444
5444
  if (!cwd)
5445
5445
  cwd = origCwd.call(process);
@@ -5460,7 +5460,7 @@ var require_polyfills2 = __commonJS({
5460
5460
  var chdir;
5461
5461
  module.exports = patch;
5462
5462
  function patch(fs5) {
5463
- if (constants2.hasOwnProperty("O_SYMLINK") && process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) {
5463
+ if (constants6.hasOwnProperty("O_SYMLINK") && process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) {
5464
5464
  patchLchmod(fs5);
5465
5465
  }
5466
5466
  if (!fs5.lutimes) {
@@ -5498,9 +5498,9 @@ var require_polyfills2 = __commonJS({
5498
5498
  fs5.lchownSync = function() {
5499
5499
  };
5500
5500
  }
5501
- if (platform2 === "win32") {
5501
+ if (platform3 === "win32") {
5502
5502
  fs5.rename = typeof fs5.rename !== "function" ? fs5.rename : (function(fs$rename) {
5503
- function rename(from, to, cb) {
5503
+ function rename2(from, to, cb) {
5504
5504
  var start = Date.now();
5505
5505
  var backoff = 0;
5506
5506
  fs$rename(from, to, function CB(er) {
@@ -5520,8 +5520,8 @@ var require_polyfills2 = __commonJS({
5520
5520
  if (cb) cb(er);
5521
5521
  });
5522
5522
  }
5523
- if (Object.setPrototypeOf) Object.setPrototypeOf(rename, fs$rename);
5524
- return rename;
5523
+ if (Object.setPrototypeOf) Object.setPrototypeOf(rename2, fs$rename);
5524
+ return rename2;
5525
5525
  })(fs5.rename);
5526
5526
  }
5527
5527
  fs5.read = typeof fs5.read !== "function" ? fs5.read : (function(fs$read) {
@@ -5562,7 +5562,7 @@ var require_polyfills2 = __commonJS({
5562
5562
  fs6.lchmod = function(path9, mode, callback) {
5563
5563
  fs6.open(
5564
5564
  path9,
5565
- constants2.O_WRONLY | constants2.O_SYMLINK,
5565
+ constants6.O_WRONLY | constants6.O_SYMLINK,
5566
5566
  mode,
5567
5567
  function(err, fd) {
5568
5568
  if (err) {
@@ -5578,7 +5578,7 @@ var require_polyfills2 = __commonJS({
5578
5578
  );
5579
5579
  };
5580
5580
  fs6.lchmodSync = function(path9, mode) {
5581
- var fd = fs6.openSync(path9, constants2.O_WRONLY | constants2.O_SYMLINK, mode);
5581
+ var fd = fs6.openSync(path9, constants6.O_WRONLY | constants6.O_SYMLINK, mode);
5582
5582
  var threw = true;
5583
5583
  var ret;
5584
5584
  try {
@@ -5598,9 +5598,9 @@ var require_polyfills2 = __commonJS({
5598
5598
  };
5599
5599
  }
5600
5600
  function patchLutimes(fs6) {
5601
- if (constants2.hasOwnProperty("O_SYMLINK") && fs6.futimes) {
5601
+ if (constants6.hasOwnProperty("O_SYMLINK") && fs6.futimes) {
5602
5602
  fs6.lutimes = function(path9, at, mt, cb) {
5603
- fs6.open(path9, constants2.O_SYMLINK, function(er, fd) {
5603
+ fs6.open(path9, constants6.O_SYMLINK, function(er, fd) {
5604
5604
  if (er) {
5605
5605
  if (cb) cb(er);
5606
5606
  return;
@@ -5613,7 +5613,7 @@ var require_polyfills2 = __commonJS({
5613
5613
  });
5614
5614
  };
5615
5615
  fs6.lutimesSync = function(path9, at, mt) {
5616
- var fd = fs6.openSync(path9, constants2.O_SYMLINK);
5616
+ var fd = fs6.openSync(path9, constants6.O_SYMLINK);
5617
5617
  var ret;
5618
5618
  var threw = true;
5619
5619
  try {
@@ -5925,8 +5925,8 @@ var require_graceful_fs2 = __commonJS({
5925
5925
  fs6.createReadStream = createReadStream;
5926
5926
  fs6.createWriteStream = createWriteStream;
5927
5927
  var fs$readFile = fs6.readFile;
5928
- fs6.readFile = readFile5;
5929
- function readFile5(path9, options, cb) {
5928
+ fs6.readFile = readFile6;
5929
+ function readFile6(path9, options, cb) {
5930
5930
  if (typeof options === "function")
5931
5931
  cb = options, options = null;
5932
5932
  return go$readFile(path9, options, cb);
@@ -6757,11 +6757,11 @@ var require_util = __commonJS({
6757
6757
  if (files.includes("node_modules") || files.includes("package.json") || files.includes("package.json5") || files.includes("package.yaml") || files.includes("pnpm-workspace.yaml")) {
6758
6758
  return name2;
6759
6759
  }
6760
- const dirname9 = path9.dirname(name2);
6761
- if (dirname9 === name2) {
6760
+ const dirname10 = path9.dirname(name2);
6761
+ if (dirname10 === name2) {
6762
6762
  return original;
6763
6763
  }
6764
- return find(dirname9, original);
6764
+ return find(dirname10, original);
6765
6765
  } catch (error) {
6766
6766
  if (name2 === original) {
6767
6767
  if (error.code === "ENOENT") {
@@ -7950,20 +7950,20 @@ var require_range = __commonJS({
7950
7950
  };
7951
7951
  var replaceTilde = (comp, options) => {
7952
7952
  const r2 = options.loose ? re[t.TILDELOOSE] : re[t.TILDE];
7953
- return comp.replace(r2, (_3, M2, m2, p2, pr) => {
7954
- debug("tilde", comp, _3, M2, m2, p2, pr);
7953
+ return comp.replace(r2, (_3, M3, m2, p2, pr) => {
7954
+ debug("tilde", comp, _3, M3, m2, p2, pr);
7955
7955
  let ret;
7956
- if (isX(M2)) {
7956
+ if (isX(M3)) {
7957
7957
  ret = "";
7958
7958
  } else if (isX(m2)) {
7959
- ret = `>=${M2}.0.0 <${+M2 + 1}.0.0-0`;
7959
+ ret = `>=${M3}.0.0 <${+M3 + 1}.0.0-0`;
7960
7960
  } else if (isX(p2)) {
7961
- ret = `>=${M2}.${m2}.0 <${M2}.${+m2 + 1}.0-0`;
7961
+ ret = `>=${M3}.${m2}.0 <${M3}.${+m2 + 1}.0-0`;
7962
7962
  } else if (pr) {
7963
7963
  debug("replaceTilde pr", pr);
7964
- ret = `>=${M2}.${m2}.${p2}-${pr} <${M2}.${+m2 + 1}.0-0`;
7964
+ ret = `>=${M3}.${m2}.${p2}-${pr} <${M3}.${+m2 + 1}.0-0`;
7965
7965
  } else {
7966
- ret = `>=${M2}.${m2}.${p2} <${M2}.${+m2 + 1}.0-0`;
7966
+ ret = `>=${M3}.${m2}.${p2} <${M3}.${+m2 + 1}.0-0`;
7967
7967
  }
7968
7968
  debug("tilde return", ret);
7969
7969
  return ret;
@@ -7976,40 +7976,40 @@ var require_range = __commonJS({
7976
7976
  debug("caret", comp, options);
7977
7977
  const r2 = options.loose ? re[t.CARETLOOSE] : re[t.CARET];
7978
7978
  const z2 = options.includePrerelease ? "-0" : "";
7979
- return comp.replace(r2, (_3, M2, m2, p2, pr) => {
7980
- debug("caret", comp, _3, M2, m2, p2, pr);
7979
+ return comp.replace(r2, (_3, M3, m2, p2, pr) => {
7980
+ debug("caret", comp, _3, M3, m2, p2, pr);
7981
7981
  let ret;
7982
- if (isX(M2)) {
7982
+ if (isX(M3)) {
7983
7983
  ret = "";
7984
7984
  } else if (isX(m2)) {
7985
- ret = `>=${M2}.0.0${z2} <${+M2 + 1}.0.0-0`;
7985
+ ret = `>=${M3}.0.0${z2} <${+M3 + 1}.0.0-0`;
7986
7986
  } else if (isX(p2)) {
7987
- if (M2 === "0") {
7988
- ret = `>=${M2}.${m2}.0${z2} <${M2}.${+m2 + 1}.0-0`;
7987
+ if (M3 === "0") {
7988
+ ret = `>=${M3}.${m2}.0${z2} <${M3}.${+m2 + 1}.0-0`;
7989
7989
  } else {
7990
- ret = `>=${M2}.${m2}.0${z2} <${+M2 + 1}.0.0-0`;
7990
+ ret = `>=${M3}.${m2}.0${z2} <${+M3 + 1}.0.0-0`;
7991
7991
  }
7992
7992
  } else if (pr) {
7993
7993
  debug("replaceCaret pr", pr);
7994
- if (M2 === "0") {
7994
+ if (M3 === "0") {
7995
7995
  if (m2 === "0") {
7996
- ret = `>=${M2}.${m2}.${p2}-${pr} <${M2}.${m2}.${+p2 + 1}-0`;
7996
+ ret = `>=${M3}.${m2}.${p2}-${pr} <${M3}.${m2}.${+p2 + 1}-0`;
7997
7997
  } else {
7998
- ret = `>=${M2}.${m2}.${p2}-${pr} <${M2}.${+m2 + 1}.0-0`;
7998
+ ret = `>=${M3}.${m2}.${p2}-${pr} <${M3}.${+m2 + 1}.0-0`;
7999
7999
  }
8000
8000
  } else {
8001
- ret = `>=${M2}.${m2}.${p2}-${pr} <${+M2 + 1}.0.0-0`;
8001
+ ret = `>=${M3}.${m2}.${p2}-${pr} <${+M3 + 1}.0.0-0`;
8002
8002
  }
8003
8003
  } else {
8004
8004
  debug("no pr");
8005
- if (M2 === "0") {
8005
+ if (M3 === "0") {
8006
8006
  if (m2 === "0") {
8007
- ret = `>=${M2}.${m2}.${p2}${z2} <${M2}.${m2}.${+p2 + 1}-0`;
8007
+ ret = `>=${M3}.${m2}.${p2}${z2} <${M3}.${m2}.${+p2 + 1}-0`;
8008
8008
  } else {
8009
- ret = `>=${M2}.${m2}.${p2}${z2} <${M2}.${+m2 + 1}.0-0`;
8009
+ ret = `>=${M3}.${m2}.${p2}${z2} <${M3}.${+m2 + 1}.0-0`;
8010
8010
  }
8011
8011
  } else {
8012
- ret = `>=${M2}.${m2}.${p2} <${+M2 + 1}.0.0-0`;
8012
+ ret = `>=${M3}.${m2}.${p2} <${+M3 + 1}.0.0-0`;
8013
8013
  }
8014
8014
  }
8015
8015
  debug("caret return", ret);
@@ -8023,9 +8023,9 @@ var require_range = __commonJS({
8023
8023
  var replaceXRange = (comp, options) => {
8024
8024
  comp = comp.trim();
8025
8025
  const r2 = options.loose ? re[t.XRANGELOOSE] : re[t.XRANGE];
8026
- return comp.replace(r2, (ret, gtlt, M2, m2, p2, pr) => {
8027
- debug("xRange", comp, ret, gtlt, M2, m2, p2, pr);
8028
- const xM = isX(M2);
8026
+ return comp.replace(r2, (ret, gtlt, M3, m2, p2, pr) => {
8027
+ debug("xRange", comp, ret, gtlt, M3, m2, p2, pr);
8028
+ const xM = isX(M3);
8029
8029
  const xm = xM || isX(m2);
8030
8030
  const xp = xm || isX(p2);
8031
8031
  const anyX = xp;
@@ -8047,7 +8047,7 @@ var require_range = __commonJS({
8047
8047
  if (gtlt === ">") {
8048
8048
  gtlt = ">=";
8049
8049
  if (xm) {
8050
- M2 = +M2 + 1;
8050
+ M3 = +M3 + 1;
8051
8051
  m2 = 0;
8052
8052
  p2 = 0;
8053
8053
  } else {
@@ -8057,7 +8057,7 @@ var require_range = __commonJS({
8057
8057
  } else if (gtlt === "<=") {
8058
8058
  gtlt = "<";
8059
8059
  if (xm) {
8060
- M2 = +M2 + 1;
8060
+ M3 = +M3 + 1;
8061
8061
  } else {
8062
8062
  m2 = +m2 + 1;
8063
8063
  }
@@ -8065,11 +8065,11 @@ var require_range = __commonJS({
8065
8065
  if (gtlt === "<") {
8066
8066
  pr = "-0";
8067
8067
  }
8068
- ret = `${gtlt + M2}.${m2}.${p2}${pr}`;
8068
+ ret = `${gtlt + M3}.${m2}.${p2}${pr}`;
8069
8069
  } else if (xm) {
8070
- ret = `>=${M2}.0.0${pr} <${+M2 + 1}.0.0-0`;
8070
+ ret = `>=${M3}.0.0${pr} <${+M3 + 1}.0.0-0`;
8071
8071
  } else if (xp) {
8072
- ret = `>=${M2}.${m2}.0${pr} <${M2}.${+m2 + 1}.0-0`;
8072
+ ret = `>=${M3}.${m2}.0${pr} <${M3}.${+m2 + 1}.0-0`;
8073
8073
  }
8074
8074
  debug("xRange return", ret);
8075
8075
  return ret;
@@ -8729,7 +8729,7 @@ var require_semver2 = __commonJS({
8729
8729
  "node_modules/.pnpm/semver@7.7.4/node_modules/semver/index.js"(exports, module) {
8730
8730
  "use strict";
8731
8731
  var internalRe = require_re();
8732
- var constants2 = require_constants();
8732
+ var constants6 = require_constants();
8733
8733
  var SemVer = require_semver();
8734
8734
  var identifiers = require_identifiers();
8735
8735
  var parse = require_parse();
@@ -8811,8 +8811,8 @@ var require_semver2 = __commonJS({
8811
8811
  re: internalRe.re,
8812
8812
  src: internalRe.src,
8813
8813
  tokens: internalRe.t,
8814
- SEMVER_SPEC_VERSION: constants2.SEMVER_SPEC_VERSION,
8815
- RELEASE_TYPES: constants2.RELEASE_TYPES,
8814
+ SEMVER_SPEC_VERSION: constants6.SEMVER_SPEC_VERSION,
8815
+ RELEASE_TYPES: constants6.RELEASE_TYPES,
8816
8816
  compareIdentifiers: identifiers.compareIdentifiers,
8817
8817
  rcompareIdentifiers: identifiers.rcompareIdentifiers
8818
8818
  };
@@ -9422,7 +9422,7 @@ var require_picocolors = __commonJS({
9422
9422
  });
9423
9423
 
9424
9424
  // src/index.ts
9425
- import { homedir as homedir7 } from "node:os";
9425
+ import { homedir as homedir8 } from "node:os";
9426
9426
 
9427
9427
  // src/cli.ts
9428
9428
  import { createRequire } from "node:module";
@@ -9781,14 +9781,14 @@ var Temp = {
9781
9781
  }
9782
9782
  },
9783
9783
  truncate: (filePath) => {
9784
- const basename9 = path2.basename(filePath);
9785
- if (basename9.length <= LIMIT_BASENAME_LENGTH)
9784
+ const basename10 = path2.basename(filePath);
9785
+ if (basename10.length <= LIMIT_BASENAME_LENGTH)
9786
9786
  return filePath;
9787
- const truncable = /^(\.?)(.*?)((?:\.[^.]+)?(?:\.tmp-\d{10}[a-f0-9]{6})?)$/.exec(basename9);
9787
+ const truncable = /^(\.?)(.*?)((?:\.[^.]+)?(?:\.tmp-\d{10}[a-f0-9]{6})?)$/.exec(basename10);
9788
9788
  if (!truncable)
9789
9789
  return filePath;
9790
- const truncationLength = basename9.length - LIMIT_BASENAME_LENGTH;
9791
- return `${filePath.slice(0, -basename9.length)}${truncable[1]}${truncable[2].slice(0, -truncationLength)}${truncable[3]}`;
9790
+ const truncationLength = basename10.length - LIMIT_BASENAME_LENGTH;
9791
+ return `${filePath.slice(0, -basename10.length)}${truncable[1]}${truncable[2].slice(0, -truncationLength)}${truncable[3]}`;
9792
9792
  }
9793
9793
  };
9794
9794
  node_default(Temp.purgeSyncAll);
@@ -14000,6 +14000,29 @@ ${import_picocolors2.default.gray(d2)} ${t}
14000
14000
 
14001
14001
  `);
14002
14002
  };
14003
+ var M2 = { message: (t = "", { symbol: n = import_picocolors2.default.gray(o) } = {}) => {
14004
+ const r2 = [`${import_picocolors2.default.gray(o)}`];
14005
+ if (t) {
14006
+ const [i, ...s] = t.split(`
14007
+ `);
14008
+ r2.push(`${n} ${i}`, ...s.map((c) => `${import_picocolors2.default.gray(o)} ${c}`));
14009
+ }
14010
+ process.stdout.write(`${r2.join(`
14011
+ `)}
14012
+ `);
14013
+ }, info: (t) => {
14014
+ M2.message(t, { symbol: import_picocolors2.default.blue(q) });
14015
+ }, success: (t) => {
14016
+ M2.message(t, { symbol: import_picocolors2.default.green(D) });
14017
+ }, step: (t) => {
14018
+ M2.message(t, { symbol: import_picocolors2.default.green(C) });
14019
+ }, warn: (t) => {
14020
+ M2.message(t, { symbol: import_picocolors2.default.yellow(U) });
14021
+ }, warning: (t) => {
14022
+ M2.warn(t);
14023
+ }, error: (t) => {
14024
+ M2.message(t, { symbol: import_picocolors2.default.red(K2) });
14025
+ } };
14003
14026
  var J2 = `${import_picocolors2.default.gray(o)} `;
14004
14027
 
14005
14028
  // src/headless.ts
@@ -14197,16 +14220,394 @@ function hasStderr(error) {
14197
14220
 
14198
14221
  // src/worktree-picker.ts
14199
14222
  import { env as env4, platform, stdin, stdout } from "node:process";
14200
- async function buildWorktreePromptEntries(sources) {
14201
- const [history, infos] = await Promise.all([
14223
+
14224
+ // src/pull-requests.ts
14225
+ import { execFile as execFile3 } from "node:child_process";
14226
+ import { promisify as promisify4 } from "node:util";
14227
+ var execFileAsync3 = promisify4(execFile3);
14228
+ var PULL_REQUEST_QUERY_TIMEOUT_MS = 2500;
14229
+ function parsePullRequestRemote(remoteUrl) {
14230
+ const parsed = parseRemoteUrl(remoteUrl.trim());
14231
+ if (parsed === null) return null;
14232
+ const forge = detectForge(parsed.host);
14233
+ if (forge === null) return null;
14234
+ if (forge === "bitbucket" && parsed.host !== "bitbucket.org") return null;
14235
+ const path9 = parsed.path.replace(/^\/+/, "").replace(/\.git$/, "").split("/").filter(Boolean);
14236
+ if (path9.length < 2) return null;
14237
+ if (forge === "gitlab") {
14238
+ const repository2 = path9.at(-1);
14239
+ const namespace2 = path9.slice(0, -1).join("/");
14240
+ if (repository2 === void 0 || namespace2.length === 0) return null;
14241
+ return {
14242
+ forge,
14243
+ host: parsed.host,
14244
+ namespace: namespace2,
14245
+ repository: repository2,
14246
+ webBaseUrl: `https://${parsed.host}`
14247
+ };
14248
+ }
14249
+ const namespace = path9.at(-2);
14250
+ const repository = path9.at(-1);
14251
+ if (namespace === void 0 || repository === void 0) return null;
14252
+ return {
14253
+ forge,
14254
+ host: parsed.host,
14255
+ namespace,
14256
+ repository,
14257
+ webBaseUrl: `https://${parsed.host}`
14258
+ };
14259
+ }
14260
+ function createPullRequestQuery(dependencies = {}) {
14261
+ const runCommand = dependencies.runCommand ?? defaultRunCommand;
14262
+ const fetcher = dependencies.fetch ?? fetch;
14263
+ return {
14264
+ findOpenPullRequest: (repoRoot, number) => findOpenPullRequest(repoRoot, number, runCommand, fetcher),
14265
+ listOpenPullRequests: (repoRoot, sourceBranch) => listOpenPullRequests(repoRoot, sourceBranch, runCommand, fetcher),
14266
+ listOpenPullRequestsForRepository: (repoRoot) => listOpenPullRequestsForRepository(repoRoot, runCommand, fetcher)
14267
+ };
14268
+ }
14269
+ async function listOpenPullRequests(repoRoot, sourceBranch, runCommand, fetcher) {
14270
+ const remote = await readPullRequestRemote(repoRoot, runCommand);
14271
+ const cliResult = await queryProviderCli(
14272
+ remote,
14273
+ { kind: "branch", sourceBranch },
14274
+ repoRoot,
14275
+ runCommand
14276
+ );
14277
+ if (cliResult !== null) return sortPullRequests(cliResult);
14278
+ const apiResult = await queryPublicApi(
14279
+ remote,
14280
+ { kind: "branch", sourceBranch },
14281
+ fetcher
14282
+ );
14283
+ return sortPullRequests(apiResult);
14284
+ }
14285
+ async function listOpenPullRequestsForRepository(repoRoot, runCommand, fetcher) {
14286
+ const remote = await readPullRequestRemote(repoRoot, runCommand);
14287
+ const cliResult = await queryProviderCli(
14288
+ remote,
14289
+ { kind: "repository" },
14290
+ repoRoot,
14291
+ runCommand
14292
+ );
14293
+ if (cliResult !== null) return sortPullRequests(cliResult);
14294
+ const apiResult = await queryPublicApi(
14295
+ remote,
14296
+ { kind: "repository" },
14297
+ fetcher
14298
+ );
14299
+ return sortPullRequests(apiResult);
14300
+ }
14301
+ async function findOpenPullRequest(repoRoot, number, runCommand, fetcher) {
14302
+ const remote = await readPullRequestRemote(repoRoot, runCommand);
14303
+ const cliResult = await queryProviderCli(
14304
+ remote,
14305
+ { kind: "number", number },
14306
+ repoRoot,
14307
+ runCommand
14308
+ );
14309
+ if (cliResult !== null) return cliResult[0] ?? null;
14310
+ const apiResult = await queryPublicApi(
14311
+ remote,
14312
+ { kind: "number", number },
14313
+ fetcher
14314
+ );
14315
+ return apiResult[0] ?? null;
14316
+ }
14317
+ async function readPullRequestRemote(repoRoot, runCommand) {
14318
+ const result = await runCommand(
14319
+ "git",
14320
+ ["remote", "get-url", "origin"],
14321
+ commandOptions(repoRoot)
14322
+ );
14323
+ const remote = parsePullRequestRemote(result.stdout);
14324
+ if (remote === null) {
14325
+ throw new Error("origin remote is unsupported or missing");
14326
+ }
14327
+ return remote;
14328
+ }
14329
+ async function queryProviderCli(remote, query, repoRoot, runCommand) {
14330
+ const providerCommand = providerCliCommand(remote, query);
14331
+ try {
14332
+ const result = await runCommand(
14333
+ providerCommand.command,
14334
+ providerCommand.args,
14335
+ commandOptions(repoRoot)
14336
+ );
14337
+ return parseProviderOutput(remote, result.stdout, query);
14338
+ } catch {
14339
+ return null;
14340
+ }
14341
+ }
14342
+ async function queryPublicApi(remote, query, fetcher) {
14343
+ const url = publicApiUrl(remote, query);
14344
+ const controller = new AbortController();
14345
+ const timer = setTimeout(
14346
+ () => controller.abort(),
14347
+ PULL_REQUEST_QUERY_TIMEOUT_MS
14348
+ );
14349
+ try {
14350
+ const response = await fetcher(url, {
14351
+ headers: { Accept: "application/json" },
14352
+ signal: controller.signal
14353
+ });
14354
+ if (response.status === 404 && query.kind === "number") return [];
14355
+ if (!response.ok) {
14356
+ throw new Error(`public API returned HTTP ${response.status}`);
14357
+ }
14358
+ const payload = await response.json();
14359
+ return parseApiOutput(remote, payload, query);
14360
+ } finally {
14361
+ clearTimeout(timer);
14362
+ }
14363
+ }
14364
+ function parseRemoteUrl(remoteUrl) {
14365
+ const scpMatch = remoteUrl.match(/^[^@]+@([^:]+):(.+)$/);
14366
+ if (scpMatch) {
14367
+ return { host: scpMatch[1].toLowerCase(), path: scpMatch[2] };
14368
+ }
14369
+ try {
14370
+ const url = new URL(remoteUrl);
14371
+ if (!url.hostname) return null;
14372
+ return { host: url.hostname.toLowerCase(), path: url.pathname };
14373
+ } catch {
14374
+ return null;
14375
+ }
14376
+ }
14377
+ function detectForge(host) {
14378
+ if (host === "github.com" || host.includes("github")) return "github";
14379
+ if (host === "gitlab.com" || host.includes("gitlab")) return "gitlab";
14380
+ if (host === "bitbucket.org" || host.includes("bitbucket")) {
14381
+ return "bitbucket";
14382
+ }
14383
+ return null;
14384
+ }
14385
+ function providerCliCommand(remote, query) {
14386
+ const coordinate = providerRepositoryCoordinate(remote);
14387
+ const sourceBranch = query.sourceBranch ?? "";
14388
+ switch (remote.forge) {
14389
+ case "github": {
14390
+ if (query.kind === "number") {
14391
+ return {
14392
+ args: [
14393
+ "pr",
14394
+ "view",
14395
+ String(query.number),
14396
+ "--json",
14397
+ "number,url,headRefName,state",
14398
+ "--repo",
14399
+ coordinate
14400
+ ],
14401
+ command: "gh"
14402
+ };
14403
+ }
14404
+ return {
14405
+ args: [
14406
+ "pr",
14407
+ "list",
14408
+ "--state",
14409
+ "open",
14410
+ ...query.kind === "branch" ? ["--head", sourceBranch] : [],
14411
+ "--json",
14412
+ "number,url,headRefName,state",
14413
+ "--limit",
14414
+ "100",
14415
+ "--repo",
14416
+ coordinate
14417
+ ],
14418
+ command: "gh"
14419
+ };
14420
+ }
14421
+ case "gitlab": {
14422
+ if (query.kind === "number") {
14423
+ return {
14424
+ args: [
14425
+ "mr",
14426
+ "view",
14427
+ String(query.number),
14428
+ "--output",
14429
+ "json",
14430
+ "--repo",
14431
+ coordinate
14432
+ ],
14433
+ command: "glab"
14434
+ };
14435
+ }
14436
+ return {
14437
+ args: [
14438
+ "mr",
14439
+ "list",
14440
+ "--state",
14441
+ "opened",
14442
+ ...query.kind === "branch" ? ["--source-branch", sourceBranch] : [],
14443
+ "--output",
14444
+ "json",
14445
+ "--per-page",
14446
+ "100",
14447
+ "--repo",
14448
+ coordinate
14449
+ ],
14450
+ command: "glab"
14451
+ };
14452
+ }
14453
+ case "bitbucket": {
14454
+ if (query.kind === "number") {
14455
+ return {
14456
+ args: [
14457
+ "pr",
14458
+ "view",
14459
+ String(query.number),
14460
+ "--format",
14461
+ "json",
14462
+ "--repo",
14463
+ coordinate
14464
+ ],
14465
+ command: "bb"
14466
+ };
14467
+ }
14468
+ return {
14469
+ args: [
14470
+ "pr",
14471
+ "list",
14472
+ "--state",
14473
+ "OPEN",
14474
+ ...query.kind === "branch" ? ["--source", sourceBranch] : [],
14475
+ "--format",
14476
+ "json",
14477
+ "--repo",
14478
+ coordinate
14479
+ ],
14480
+ command: "bb"
14481
+ };
14482
+ }
14483
+ }
14484
+ }
14485
+ function providerRepositoryCoordinate(remote) {
14486
+ const coordinate = `${remote.namespace}/${remote.repository}`;
14487
+ return remote.host === "github.com" || remote.host === "bitbucket.org" ? coordinate : `${remote.host}/${coordinate}`;
14488
+ }
14489
+ function parseProviderOutput(remote, stdout2, query) {
14490
+ const payload = JSON.parse(stdout2);
14491
+ const values = remote.forge === "bitbucket" && isRecord(payload) ? Array.isArray(payload.values) ? payload.values : [] : Array.isArray(payload) ? payload : [payload];
14492
+ return values.map((value) => normalizePullRequest(value, query)).filter((value) => value !== null).filter(
14493
+ (value) => query.kind === "branch" ? value.sourceBranch === query.sourceBranch : query.kind === "number" ? value.number === query.number : true
14494
+ );
14495
+ }
14496
+ function parseApiOutput(remote, payload, query) {
14497
+ const values = remote.forge === "bitbucket" && isRecord(payload) ? Array.isArray(payload.values) ? payload.values : [] : query.kind === "number" ? [payload] : Array.isArray(payload) ? payload : [];
14498
+ return values.map((value) => normalizePullRequest(value, query)).filter((value) => value !== null).filter(
14499
+ (value) => query.kind === "branch" ? value.sourceBranch === query.sourceBranch : query.kind === "number" ? value.number === query.number : true
14500
+ );
14501
+ }
14502
+ function normalizePullRequest(value, query) {
14503
+ if (!isRecord(value)) return null;
14504
+ const number = numberValue(value.number ?? value.iid ?? value.id);
14505
+ const head = isRecord(value.head) ? value.head : null;
14506
+ const source = isRecord(value.source) ? value.source : null;
14507
+ const sourceBranch = stringValue(
14508
+ value.headRefName ?? (head !== null ? head.ref : void 0) ?? value.source_branch ?? (source !== null && isRecord(source.branch) ? source.branch.name : void 0)
14509
+ );
14510
+ const url = stringValue(
14511
+ value.html_url ?? value.web_url ?? value.url ?? (isRecord(value.links) && isRecord(value.links.html) ? value.links.html.href : void 0)
14512
+ );
14513
+ const state = stringValue(value.state)?.toLowerCase();
14514
+ if (number === null || url === null) return null;
14515
+ if (query.kind !== "number" && sourceBranch === null) return null;
14516
+ if (query.kind === "number" && number !== query.number) return null;
14517
+ if (state !== void 0 && !["open", "opened"].includes(state)) return null;
14518
+ return {
14519
+ number,
14520
+ sourceBranch: sourceBranch ?? "",
14521
+ url
14522
+ };
14523
+ }
14524
+ function publicApiUrl(remote, query) {
14525
+ const branch = encodeURIComponent(query.sourceBranch ?? "");
14526
+ const number = query.number === void 0 ? "" : String(query.number);
14527
+ switch (remote.forge) {
14528
+ case "github": {
14529
+ const coordinate = `${encodeURIComponent(remote.namespace)}/${encodeURIComponent(remote.repository)}`;
14530
+ const apiBase = remote.host === "github.com" ? "https://api.github.com" : `https://${remote.host}/api/v3`;
14531
+ if (query.kind === "number") {
14532
+ return `${apiBase}/repos/${coordinate}/pulls/${number}`;
14533
+ }
14534
+ return `${apiBase}/repos/${coordinate}/pulls?state=open&per_page=100`;
14535
+ }
14536
+ case "gitlab": {
14537
+ const project = encodeURIComponent(
14538
+ `${remote.namespace}/${remote.repository}`
14539
+ );
14540
+ if (query.kind === "number") {
14541
+ return `${remote.webBaseUrl}/api/v4/projects/${project}/merge_requests/${number}`;
14542
+ }
14543
+ return query.kind === "branch" ? `${remote.webBaseUrl}/api/v4/projects/${project}/merge_requests?state=opened&source_branch=${branch}&per_page=100` : `${remote.webBaseUrl}/api/v4/projects/${project}/merge_requests?state=opened&per_page=100`;
14544
+ }
14545
+ case "bitbucket": {
14546
+ const coordinate = `${encodeURIComponent(remote.namespace)}/${encodeURIComponent(remote.repository)}`;
14547
+ if (query.kind === "number") {
14548
+ return `https://api.bitbucket.org/2.0/repositories/${coordinate}/pullrequests/${number}`;
14549
+ }
14550
+ return query.kind === "branch" ? `https://api.bitbucket.org/2.0/repositories/${coordinate}/pullrequests?state=OPEN&source.branch.name=${branch}&pagelen=100` : `https://api.bitbucket.org/2.0/repositories/${coordinate}/pullrequests?state=OPEN&pagelen=100`;
14551
+ }
14552
+ }
14553
+ }
14554
+ function sortPullRequests(pullRequests) {
14555
+ return [...pullRequests].sort((a, b3) => a.number - b3.number);
14556
+ }
14557
+ function commandOptions(cwd) {
14558
+ return {
14559
+ cwd,
14560
+ env: {
14561
+ ...process.env,
14562
+ GIT_TERMINAL_PROMPT: "0",
14563
+ GH_PROMPT_DISABLED: "1",
14564
+ GLAB_NON_INTERACTIVE: "1"
14565
+ },
14566
+ timeout: PULL_REQUEST_QUERY_TIMEOUT_MS
14567
+ };
14568
+ }
14569
+ async function defaultRunCommand(command, args, options) {
14570
+ return execFileAsync3(command, args, options);
14571
+ }
14572
+ function isRecord(value) {
14573
+ return typeof value === "object" && value !== null;
14574
+ }
14575
+ function stringValue(value) {
14576
+ return typeof value === "string" && value.length > 0 ? value : null;
14577
+ }
14578
+ function numberValue(value) {
14579
+ if (typeof value === "number" && Number.isInteger(value)) return value;
14580
+ if (typeof value === "string" && /^\d+$/.test(value)) return Number(value);
14581
+ return null;
14582
+ }
14583
+
14584
+ // src/worktree-picker.ts
14585
+ var MAX_PULL_REQUEST_REPOSITORY_QUERY_CONCURRENCY = 4;
14586
+ async function buildWorktreePromptEntries(sources, dependencies = {}) {
14587
+ const pullRequestQuery = createPullRequestQuery();
14588
+ const queryPullRequests = dependencies.queryPullRequests ?? pullRequestQuery.listOpenPullRequests;
14589
+ const queryRepositoryPullRequests = dependencies.queryRepositoryPullRequests ?? (dependencies.queryPullRequests === void 0 ? pullRequestQuery.listOpenPullRequestsForRepository : void 0);
14590
+ const repositoryPullRequests = queryRepositoryPullRequests === void 0 ? Promise.resolve(null) : readRepositoryPullRequests(sources, queryRepositoryPullRequests);
14591
+ const [history, infos, pullRequestsByRepository] = await Promise.all([
14202
14592
  loadHistory(),
14203
- readWorktreeInfos(sources.map((source) => source.worktree))
14593
+ readWorktreeInfos(sources.map((source) => source.worktree)),
14594
+ repositoryPullRequests
14204
14595
  ]);
14596
+ const pullRequests = await Promise.all(
14597
+ sources.map(
14598
+ (source) => readSourcePullRequests(
14599
+ source,
14600
+ queryPullRequests,
14601
+ pullRequestsByRepository
14602
+ )
14603
+ )
14604
+ );
14205
14605
  const historyByPath = new Map(history.map((entry) => [entry.path, entry]));
14206
14606
  const entries = sources.map(
14207
14607
  (source, index) => buildWorktreePromptEntry(
14208
14608
  source,
14209
14609
  infos[index],
14610
+ pullRequests[index],
14210
14611
  historyByPath.get(source.worktree.path)?.timestamp ?? null,
14211
14612
  Date.now()
14212
14613
  )
@@ -14304,7 +14705,10 @@ function buildSearchableWorktreeEntry(worktree) {
14304
14705
  searchText: buildPromptSearchText(worktree),
14305
14706
  value: worktree.path,
14306
14707
  worktree: {
14307
- branch,
14708
+ branch: formatWorktreeBranchLabel(
14709
+ worktree.branch,
14710
+ worktree.pullRequestNumbers
14711
+ ),
14308
14712
  metadata,
14309
14713
  path: worktree.path,
14310
14714
  repoName: worktree.repoName
@@ -14886,14 +15290,19 @@ function buildPromptSearchText(worktree) {
14886
15290
  worktree.branch ?? "detached",
14887
15291
  worktree.path,
14888
15292
  worktree.label,
14889
- `${worktree.repoName}/${worktree.branch ?? "detached"}`
15293
+ `${worktree.repoName}/${worktree.branch ?? "detached"}`,
15294
+ ...(worktree.pullRequestNumbers ?? []).map((number) => `#${number}`),
15295
+ ...worktree.pullRequestUrls ?? []
14890
15296
  ].join(" ").toLowerCase();
14891
15297
  }
14892
- function buildWorktreePromptEntry(source, info, lastUsedTimestamp, now) {
15298
+ function buildWorktreePromptEntry(source, info, pullRequests, lastUsedTimestamp, now) {
14893
15299
  const lastWorkedTimestamp = info.lastCommitTimestamp === null ? null : info.lastCommitTimestamp * 1e3;
14894
15300
  const lastActivityTimestamp = lastUsedTimestamp ?? lastWorkedTimestamp;
14895
15301
  const lastActivityType = lastUsedTimestamp !== null ? "used" : lastWorkedTimestamp !== null ? "worked" : null;
14896
- const branch = source.worktree.branch ?? "(detached)";
15302
+ const branch = formatWorktreeBranchLabel(
15303
+ source.worktree.branch,
15304
+ pullRequests.map((pullRequest) => pullRequest.number)
15305
+ );
14897
15306
  const badges = buildStatusBadges(info);
14898
15307
  const recency = formatPromptRecency(
14899
15308
  lastActivityTimestamp,
@@ -14918,9 +15327,80 @@ function buildWorktreePromptEntry(source, info, lastUsedTimestamp, now) {
14918
15327
  label,
14919
15328
  lastActivityTimestamp,
14920
15329
  metadata,
15330
+ pullRequestNumbers: pullRequests.map((pullRequest) => pullRequest.number),
15331
+ pullRequestUrls: pullRequests.map((pullRequest) => pullRequest.url),
14921
15332
  repoName: source.repoName
14922
15333
  };
14923
15334
  }
15335
+ async function readSourcePullRequests(source, queryPullRequests, pullRequestsByRepository) {
15336
+ if (source.repoRoot === void 0 || source.worktree.branch === null) {
15337
+ return [];
15338
+ }
15339
+ if (pullRequestsByRepository !== null) {
15340
+ return sortSourcePullRequests(
15341
+ pullRequestsByRepository.get(source.repoRoot)?.filter(
15342
+ (pullRequest) => pullRequest.sourceBranch === source.worktree.branch
15343
+ )
15344
+ );
15345
+ }
15346
+ try {
15347
+ const pullRequests = await queryPullRequests(
15348
+ source.repoRoot,
15349
+ source.worktree.branch
15350
+ );
15351
+ return sortSourcePullRequests(pullRequests);
15352
+ } catch {
15353
+ return [];
15354
+ }
15355
+ }
15356
+ async function readRepositoryPullRequests(sources, queryPullRequests) {
15357
+ const repoRoots = [
15358
+ ...new Set(
15359
+ sources.flatMap(
15360
+ (source) => source.repoRoot === void 0 || source.worktree.branch === null ? [] : [source.repoRoot]
15361
+ )
15362
+ )
15363
+ ];
15364
+ const results = await mapWithConcurrency2(
15365
+ repoRoots,
15366
+ MAX_PULL_REQUEST_REPOSITORY_QUERY_CONCURRENCY,
15367
+ async (repoRoot) => {
15368
+ try {
15369
+ return [
15370
+ repoRoot,
15371
+ sortSourcePullRequests(await queryPullRequests(repoRoot))
15372
+ ];
15373
+ } catch {
15374
+ return [repoRoot, []];
15375
+ }
15376
+ }
15377
+ );
15378
+ return new Map(results);
15379
+ }
15380
+ async function mapWithConcurrency2(items, limit, mapper) {
15381
+ const results = new Array(items.length);
15382
+ let nextIndex = 0;
15383
+ async function readNext() {
15384
+ for (; ; ) {
15385
+ const index = nextIndex;
15386
+ nextIndex += 1;
15387
+ if (index >= items.length) return;
15388
+ results[index] = await mapper(items[index]);
15389
+ }
15390
+ }
15391
+ await Promise.all(
15392
+ Array.from({ length: Math.min(limit, items.length) }, () => readNext())
15393
+ );
15394
+ return results;
15395
+ }
15396
+ function sortSourcePullRequests(pullRequests) {
15397
+ return [...pullRequests ?? []].sort((a, b3) => a.number - b3.number);
15398
+ }
15399
+ function formatWorktreeBranchLabel(branch, pullRequestNumbers = []) {
15400
+ const branchLabel = branch ?? "(detached)";
15401
+ if (pullRequestNumbers.length === 0) return branchLabel;
15402
+ return `${branchLabel} (${pullRequestNumbers.map((number) => `#${number}`).join(", ")})`;
15403
+ }
14924
15404
  function buildStatusBadges(info) {
14925
15405
  const badges = [];
14926
15406
  if (info.isCurrent) {
@@ -15442,7 +15922,14 @@ var TOP_LEVEL_COMMANDS = [
15442
15922
  name: "new",
15443
15923
  description: "create a new branch or detached linked worktree"
15444
15924
  },
15445
- { name: "init", description: "print or install shell integration" },
15925
+ {
15926
+ name: "init",
15927
+ description: "set up shell integration interactively or print a shell wrapper"
15928
+ },
15929
+ {
15930
+ name: "doctor",
15931
+ description: "check gji installation and configuration health"
15932
+ },
15446
15933
  { name: "completion", description: "print shell completion definitions" },
15447
15934
  { name: "pr", description: "fetch a pull request into a linked worktree" },
15448
15935
  { name: "back", description: "navigate to the previously visited worktree" },
@@ -15495,6 +15982,70 @@ function renderBashCompletion() {
15495
15982
  command gji ls --compact 2>/dev/null | awk 'NR > 1 { branch = ($1 == "*" ? $2 : $1); if (branch != "(detached)") print branch }'
15496
15983
  }
15497
15984
 
15985
+ __gji_pr_targets_cache=""
15986
+ __gji_pr_targets_cache_at=0
15987
+ __gji_pr_targets_cache_loaded=0
15988
+ __gji_pr_targets_cache_repo=""
15989
+
15990
+ __gji_run_bounded() {
15991
+ local output_file pid status attempt
15992
+ output_file=$(mktemp "\${TMPDIR:-/tmp}/gji-completion.XXXXXX") || return 124
15993
+ "$@" >"$output_file" 2>/dev/null &
15994
+ pid=$!
15995
+ for ((attempt = 0; attempt < 20; attempt++)); do
15996
+ if ! kill -0 "$pid" 2>/dev/null; then
15997
+ wait "$pid"
15998
+ status=$?
15999
+ cat "$output_file"
16000
+ rm -f "$output_file"
16001
+ return "$status"
16002
+ fi
16003
+ sleep 0.1
16004
+ done
16005
+ kill "$pid" 2>/dev/null
16006
+ wait "$pid" 2>/dev/null
16007
+ rm -f "$output_file"
16008
+ return 124
16009
+ }
16010
+
16011
+ __gji_pr_targets() {
16012
+ local now repo_key branches targets
16013
+ now=$(date +%s)
16014
+ repo_key=$(command git rev-parse --show-toplevel 2>/dev/null)
16015
+ if [ "$__gji_pr_targets_cache_loaded" -eq 1 ] && [ "$repo_key" = "$__gji_pr_targets_cache_repo" ] && [ "$((now - __gji_pr_targets_cache_at))" -lt 30 ]; then
16016
+ printf '%s
16017
+ ' "$__gji_pr_targets_cache"
16018
+ return
16019
+ fi
16020
+
16021
+ branches="$(__gji_worktree_branches)"
16022
+ targets=""
16023
+ if [ -n "$repo_key" ] && command -v gh >/dev/null 2>&1; then
16024
+ targets=$(__gji_run_bounded env GH_PROMPT_DISABLED=1 gh pr list --state open --json number --limit 100 |
16025
+ tr '{},' '\\n' | sed -n 's/.*"number":[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p')
16026
+ elif [ -n "$repo_key" ] && command -v glab >/dev/null 2>&1; then
16027
+ targets=$(__gji_run_bounded env GLAB_NON_INTERACTIVE=1 glab mr list --state opened --output json --per-page 100 |
16028
+ tr '{},' '\\n' | sed -n 's/.*"iid":[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p')
16029
+ elif [ -n "$repo_key" ] && command -v bb >/dev/null 2>&1; then
16030
+ targets=$(__gji_run_bounded bb pr list --state OPEN --format json |
16031
+ tr '{},' '\\n' | sed -n 's/.*"id":[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p')
16032
+ fi
16033
+
16034
+ __gji_pr_targets_cache="$branches"
16035
+ if [ -n "$targets" ]; then
16036
+ if [ -n "$__gji_pr_targets_cache" ]; then
16037
+ __gji_pr_targets_cache+=$'
16038
+ '
16039
+ fi
16040
+ __gji_pr_targets_cache+="$targets"
16041
+ fi
16042
+ __gji_pr_targets_cache_at="$now"
16043
+ __gji_pr_targets_cache_loaded=1
16044
+ __gji_pr_targets_cache_repo="$repo_key"
16045
+ printf '%s
16046
+ ' "$__gji_pr_targets_cache"
16047
+ }
16048
+
15498
16049
  _gji_completion() {
15499
16050
  local cur command_name
15500
16051
  COMPREPLY=()
@@ -15512,13 +16063,37 @@ _gji_completion() {
15512
16063
  COMPREPLY=( $(compgen -W "--detached --force --open --editor --dry-run --json --help" -- "$cur") )
15513
16064
  ;;
15514
16065
  init)
15515
- COMPREPLY=( $(compgen -W "${shells} --write --help" -- "$cur") )
16066
+ COMPREPLY=( $(compgen -W "${shells} --write --json --help" -- "$cur") )
16067
+ ;;
16068
+ doctor)
16069
+ COMPREPLY=( $(compgen -W "--fix --yes --json --help" -- "$cur") )
15516
16070
  ;;
15517
16071
  completion)
15518
16072
  COMPREPLY=( $(compgen -W "${shells} --help" -- "$cur") )
15519
16073
  ;;
15520
16074
  pr)
15521
- COMPREPLY=( $(compgen -W "--dry-run --json --help" -- "$cur") )
16075
+ if [ "$COMP_CWORD" -eq 2 ]; then
16076
+ COMPREPLY=( $(compgen -W "open --dry-run --json --help" -- "$cur") )
16077
+ elif [ "\${COMP_WORDS[2]}" = "open" ]; then
16078
+ local has_select=0 has_target=0 word
16079
+ for word in "\${COMP_WORDS[@]:3:$((COMP_CWORD - 3))}"; do
16080
+ if [ "$word" = "--select" ]; then has_select=1; fi
16081
+ if [[ "$word" != -* && "$word" != "" ]]; then has_target=1; fi
16082
+ done
16083
+ if [[ "$cur" == -* ]]; then
16084
+ if [[ "$has_select" -eq 1 || "$has_target" -eq 1 ]]; then
16085
+ COMPREPLY=( $(compgen -W "--help" -- "$cur") )
16086
+ else
16087
+ COMPREPLY=( $(compgen -W "--select --help" -- "$cur") )
16088
+ fi
16089
+ elif [[ "$has_select" -eq 1 || "$has_target" -eq 1 ]]; then
16090
+ COMPREPLY=()
16091
+ else
16092
+ COMPREPLY=( $(compgen -W "$(__gji_pr_targets) --select --help" -- "$cur") )
16093
+ fi
16094
+ else
16095
+ COMPREPLY=( $(compgen -W "--dry-run --json --help" -- "$cur") )
16096
+ fi
15522
16097
  ;;
15523
16098
  back)
15524
16099
  COMPREPLY=( $(compgen -W "--print --help" -- "$cur") )
@@ -15603,6 +16178,103 @@ function renderFishCompletion() {
15603
16178
  command gji ls --compact 2>/dev/null | awk 'NR > 1 { branch = ($1 == "*" ? $2 : $1); if (branch != "(detached)") print branch }'
15604
16179
  end
15605
16180
 
16181
+ set -g __gji_pr_targets_cache
16182
+ set -g __gji_pr_targets_cache_at 0
16183
+ set -g __gji_pr_targets_cache_loaded 0
16184
+ set -g __gji_pr_targets_cache_repo
16185
+
16186
+ function __gji_run_bounded
16187
+ set -l output_file (mktemp "$TMPDIR/gji-completion.XXXXXX" 2>/dev/null)
16188
+ if test -z "$output_file"
16189
+ set output_file (mktemp "/tmp/gji-completion.XXXXXX" 2>/dev/null)
16190
+ end
16191
+ if test -z "$output_file"
16192
+ return 124
16193
+ end
16194
+
16195
+ command $argv >$output_file 2>/dev/null &
16196
+ set -l pid $last_pid
16197
+ for attempt in (seq 1 20)
16198
+ if not kill -0 $pid 2>/dev/null
16199
+ wait $pid
16200
+ set -l exit_status $status
16201
+ command cat $output_file
16202
+ command rm -f $output_file
16203
+ return $exit_status
16204
+ end
16205
+ sleep 0.1
16206
+ end
16207
+ kill $pid 2>/dev/null
16208
+ wait $pid 2>/dev/null
16209
+ command rm -f $output_file
16210
+ return 124
16211
+ end
16212
+
16213
+ function __gji_pr_targets
16214
+ set -l now (date +%s)
16215
+ set -l repo_key (command git rev-parse --show-toplevel 2>/dev/null)
16216
+ if test $__gji_pr_targets_cache_loaded -eq 1; and test "$repo_key" = "$__gji_pr_targets_cache_repo"; and test (math $now - $__gji_pr_targets_cache_at) -lt 30
16217
+ printf '%s
16218
+ ' $__gji_pr_targets_cache
16219
+ return
16220
+ end
16221
+
16222
+ set -l branches (__gji_worktree_branches)
16223
+ set -l targets
16224
+ if test -n "$repo_key"; and command -q gh
16225
+ set targets (__gji_run_bounded env GH_PROMPT_DISABLED=1 gh pr list --state open --json number --limit 100 |
16226
+ string replace -a -r '[{},]' '\\n' |
16227
+ string match -r '"number"[[:space:]]*:[[:space:]]*[0-9]+' | string replace -r '.*:[[:space:]]*' '')
16228
+ else if test -n "$repo_key"; and command -q glab
16229
+ set targets (__gji_run_bounded env GLAB_NON_INTERACTIVE=1 glab mr list --state opened --output json --per-page 100 |
16230
+ string replace -a -r '[{},]' '\\n' |
16231
+ string match -r '"iid"[[:space:]]*:[[:space:]]*[0-9]+' | string replace -r '.*:[[:space:]]*' '')
16232
+ else if test -n "$repo_key"; and command -q bb
16233
+ set targets (__gji_run_bounded bb pr list --state OPEN --format json |
16234
+ string replace -a -r '[{},]' '\\n' |
16235
+ string match -r '"id"[[:space:]]*:[[:space:]]*[0-9]+' | string replace -r '.*:[[:space:]]*' '')
16236
+ end
16237
+
16238
+ set -g __gji_pr_targets_cache $branches $targets
16239
+ set -g __gji_pr_targets_cache_at $now
16240
+ set -g __gji_pr_targets_cache_loaded 1
16241
+ set -g __gji_pr_targets_cache_repo $repo_key
16242
+ printf '%s
16243
+ ' $__gji_pr_targets_cache
16244
+ end
16245
+
16246
+ function __gji_should_complete_pr_select
16247
+ set -l tokens (commandline -opc)
16248
+ if test (count $tokens) -lt 3; or test $tokens[2] != pr; or test $tokens[3] != open
16249
+ return 1
16250
+ end
16251
+ for token in $tokens[4..-1]
16252
+ if test $token = --select; or not string match -q -- '-*' $token
16253
+ return 1
16254
+ end
16255
+ end
16256
+ return 0
16257
+ end
16258
+
16259
+ function __gji_should_complete_pr_target
16260
+ set -l tokens (commandline -opc)
16261
+ if test (count $tokens) -lt 3; or test $tokens[2] != pr; or test $tokens[3] != open
16262
+ return 1
16263
+ end
16264
+ if string match -q -- '-*' (commandline -ct)
16265
+ return 1
16266
+ end
16267
+ for token in $tokens[4..-1]
16268
+ if test $token = --select
16269
+ return 1
16270
+ end
16271
+ if not string match -q -- '-*' $token
16272
+ return 1
16273
+ end
16274
+ end
16275
+ return 0
16276
+ end
16277
+
15606
16278
  function __gji_should_complete_config_action
15607
16279
  set -l tokens (commandline -opc)
15608
16280
  test (count $tokens) -eq 2
@@ -15632,14 +16304,22 @@ complete -c gji -n '__fish_seen_subcommand_from new' -l dry-run -d 'show what wo
15632
16304
  complete -c gji -n '__fish_seen_subcommand_from new' -l json -d 'emit JSON on success or error instead of human-readable output'
15633
16305
 
15634
16306
  complete -c gji -n '__fish_seen_subcommand_from init' -l write -d 'write the integration to the shell config file'
16307
+ complete -c gji -n '__fish_seen_subcommand_from init' -l json -d 'emit a JSON error in non-interactive mode'
15635
16308
  ${shellLines}
15636
16309
 
16310
+ complete -c gji -n '__fish_seen_subcommand_from doctor' -l fix -d 'apply safe automatic fixes after showing the plan'
16311
+ complete -c gji -n '__fish_seen_subcommand_from doctor' -l yes -d 'apply --fix without prompting'
16312
+ complete -c gji -n '__fish_seen_subcommand_from doctor' -l json -d 'emit diagnostic checks as JSON'
16313
+
15637
16314
  complete -c gji -n '__fish_seen_subcommand_from completion' -a 'bash' -d 'shell'
15638
16315
  complete -c gji -n '__fish_seen_subcommand_from completion' -a 'fish' -d 'shell'
15639
16316
  complete -c gji -n '__fish_seen_subcommand_from completion' -a 'zsh' -d 'shell'
15640
16317
 
15641
- complete -c gji -n '__fish_seen_subcommand_from pr' -l dry-run -d 'show what would be created without executing any git commands or writing files'
15642
- complete -c gji -n '__fish_seen_subcommand_from pr' -l json -d 'emit JSON on success or error instead of human-readable output'
16318
+ complete -c gji -n '__fish_seen_subcommand_from pr; and test (commandline -opc)[3] != open' -l dry-run -d 'show what would be created without executing any git commands or writing files'
16319
+ complete -c gji -n '__fish_seen_subcommand_from pr; and test (commandline -opc)[3] != open' -l json -d 'emit JSON on success or error instead of human-readable output'
16320
+ complete -c gji -n '__fish_seen_subcommand_from pr' -a 'open' -d 'open a pull request in the default browser'
16321
+ complete -c gji -n '__gji_should_complete_pr_select' -l select -d 'choose a pull request from any linked worktree'
16322
+ complete -c gji -n '__gji_should_complete_pr_target' -a '(__gji_pr_targets)' -d 'branch or PR number'
15643
16323
 
15644
16324
  complete -c gji -n '__fish_seen_subcommand_from back' -l print -d 'print the resolved worktree path explicitly'
15645
16325
 
@@ -15701,15 +16381,71 @@ __gji_worktree_branches() {
15701
16381
  command gji ls --compact 2>/dev/null | awk 'NR > 1 { branch = ($1 == "*" ? $2 : $1); if (branch != "(detached)") print branch }'
15702
16382
  }
15703
16383
 
16384
+ typeset -g __gji_pr_targets_cache=()
16385
+ typeset -g __gji_pr_targets_cache_at=0
16386
+ typeset -g __gji_pr_targets_cache_loaded=0
16387
+ typeset -g __gji_pr_targets_cache_repo=""
16388
+
16389
+ __gji_run_bounded() {
16390
+ local output_file pid status attempt
16391
+ output_file=$(mktemp "\${TMPDIR:-/tmp}/gji-completion.XXXXXX") || return 124
16392
+ "$@" >"$output_file" 2>/dev/null &
16393
+ pid=$!
16394
+ for ((attempt = 0; attempt < 20; attempt++)); do
16395
+ if ! kill -0 "$pid" 2>/dev/null; then
16396
+ wait "$pid"
16397
+ status=$?
16398
+ cat "$output_file"
16399
+ rm -f "$output_file"
16400
+ return "$status"
16401
+ fi
16402
+ sleep 0.1
16403
+ done
16404
+ kill "$pid" 2>/dev/null
16405
+ wait "$pid" 2>/dev/null
16406
+ rm -f "$output_file"
16407
+ return 124
16408
+ }
16409
+
16410
+ __gji_pr_targets() {
16411
+ local now repo_key
16412
+ local -a branches targets
16413
+ now=$(date +%s)
16414
+ repo_key=$(command git rev-parse --show-toplevel 2>/dev/null)
16415
+ if (( __gji_pr_targets_cache_loaded == 1 && "\${repo_key}" == "\${__gji_pr_targets_cache_repo}" && now - __gji_pr_targets_cache_at < 30 )); then
16416
+ print -rl -- "\${__gji_pr_targets_cache[@]}"
16417
+ return
16418
+ fi
16419
+
16420
+ branches=("\${(@f)$(__gji_worktree_branches)}")
16421
+ targets=()
16422
+ if [[ -n "\${repo_key}" && $+commands[gh] -eq 1 ]]; then
16423
+ targets=("\${(@f)$(__gji_run_bounded env GH_PROMPT_DISABLED=1 gh pr list --state open --json number --limit 100 |
16424
+ tr '{},' '\\n' | sed -n 's/.*"number":[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p')}")
16425
+ elif [[ -n "\${repo_key}" && $+commands[glab] -eq 1 ]]; then
16426
+ targets=("\${(@f)$(__gji_run_bounded env GLAB_NON_INTERACTIVE=1 glab mr list --state opened --output json --per-page 100 |
16427
+ tr '{},' '\\n' | sed -n 's/.*"iid":[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p')}")
16428
+ elif [[ -n "\${repo_key}" && $+commands[bb] -eq 1 ]]; then
16429
+ targets=("\${(@f)$(__gji_run_bounded bb pr list --state OPEN --format json |
16430
+ tr '{},' '\\n' | sed -n 's/.*"id":[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p')}")
16431
+ fi
16432
+
16433
+ __gji_pr_targets_cache=("\${branches[@]}" "\${targets[@]}")
16434
+ __gji_pr_targets_cache_at="$now"
16435
+ __gji_pr_targets_cache_loaded=1
16436
+ __gji_pr_targets_cache_repo="$repo_key"
16437
+ print -rl -- "\${__gji_pr_targets_cache[@]}"
16438
+ }
16439
+
15704
16440
  local context state line
15705
- local -a commands worktree_branches
16441
+ local -a command_entries worktree_branches
15706
16442
 
15707
- commands=(
16443
+ command_entries=(
15708
16444
  ${commandLines}
15709
16445
  )
15710
16446
 
15711
16447
  if (( CURRENT == 2 )); then
15712
- _describe 'command' commands
16448
+ _describe 'command' command_entries
15713
16449
  return
15714
16450
  fi
15715
16451
 
@@ -15718,13 +16454,26 @@ case "\${words[2]}" in
15718
16454
  _arguments '--detached[create a detached worktree without a branch]' '--force[remove and recreate the worktree if the target path already exists]' '--open[open the new worktree in an editor after creation]' '--editor[editor CLI to use with --open (code, cursor, zed, \u2026)]:editor:' '--dry-run[show what would be created without executing any git commands or writing files]' '--json[emit JSON on success or error instead of human-readable output]' '2:branch: '
15719
16455
  ;;
15720
16456
  init)
15721
- _arguments '--write[write the integration to the shell config file]' '2:shell:(${shells})'
16457
+ _arguments '--write[write the integration to the shell config file]' '--json[emit a JSON error in non-interactive mode]' '2:shell:(${shells})'
16458
+ ;;
16459
+ doctor)
16460
+ _arguments '--fix[apply safe automatic fixes after showing the plan]' '--yes[apply --fix without prompting]' '--json[emit diagnostic checks as JSON]'
15722
16461
  ;;
15723
16462
  completion)
15724
16463
  _arguments '2:shell:(${shells})'
15725
16464
  ;;
15726
16465
  pr)
15727
- _arguments '--dry-run[show what would be created without executing any git commands or writing files]' '--json[emit JSON on success or error instead of human-readable output]' '2:ref: '
16466
+ if [[ "\${words[3]}" == "open" ]]; then
16467
+ if (( \${words[(I)--select]} )); then
16468
+ _arguments
16469
+ elif [[ -n "\${words[4]}" && "\${words[4]}" != -* ]]; then
16470
+ _arguments
16471
+ else
16472
+ _arguments '--select[choose a pull request from any linked worktree]' '4:branch or PR number:->pr_targets'
16473
+ fi
16474
+ else
16475
+ _arguments '--dry-run[show what would be created without executing any git commands or writing files]' '--json[emit JSON on success or error instead of human-readable output]' '2:ref:(open)'
16476
+ fi
15728
16477
  ;;
15729
16478
  back)
15730
16479
  _arguments '--print[print the resolved worktree path explicitly]' '2:steps: '
@@ -15787,6 +16536,10 @@ case "$state" in
15787
16536
  worktree_branches=(\${(@f)$(__gji_worktree_branches)})
15788
16537
  _describe 'worktree branch' worktree_branches
15789
16538
  ;;
16539
+ pr_targets)
16540
+ worktree_branches=(\${(@f)$(__gji_pr_targets)})
16541
+ _describe 'branch or PR number' worktree_branches
16542
+ ;;
15790
16543
  config_keys)
15791
16544
  _values 'config key' ${configKeys}
15792
16545
  ;;
@@ -15850,51 +16603,13 @@ function writeJson(stdout2, value) {
15850
16603
  `);
15851
16604
  }
15852
16605
 
15853
- // src/go.ts
15854
- import { basename as basename6 } from "node:path";
15855
-
15856
- // src/warp.ts
15857
- import { realpath as realpath2 } from "node:fs/promises";
15858
- import { basename as basename5, resolve as resolve5 } from "node:path";
15859
-
15860
- // src/new.ts
15861
- import { execFile as execFile3 } from "node:child_process";
15862
- import { mkdir as mkdir4 } from "node:fs/promises";
15863
- import { basename as basename3, dirname as dirname5 } from "node:path";
15864
- import { promisify as promisify4 } from "node:util";
15865
-
15866
- // src/conflict.ts
15867
- import { constants } from "node:fs";
15868
- import { access as access2 } from "node:fs/promises";
15869
- async function pathExists(path9) {
15870
- try {
15871
- await access2(path9, constants.F_OK);
15872
- return true;
15873
- } catch {
15874
- return false;
15875
- }
15876
- }
15877
- async function promptForPathConflict(path9) {
15878
- const choice = await ve({
15879
- message: `Target path already exists: ${path9}`,
15880
- options: [
15881
- {
15882
- value: "abort",
15883
- label: "Abort",
15884
- hint: "Keep the existing directory untouched"
15885
- },
15886
- {
15887
- value: "reuse",
15888
- label: "Reuse path",
15889
- hint: "Print the existing path and stop"
15890
- }
15891
- ]
15892
- });
15893
- if (pD(choice)) {
15894
- return "abort";
15895
- }
15896
- return choice;
15897
- }
16606
+ // src/doctor.ts
16607
+ import { execFile as execFile4 } from "node:child_process";
16608
+ import { constants as constants3 } from "node:fs";
16609
+ import { access as access4, readFile as readFile4 } from "node:fs/promises";
16610
+ import { homedir as homedir5 } from "node:os";
16611
+ import { basename as basename4, dirname as dirname5, join as join6 } from "node:path";
16612
+ import { promisify as promisify5 } from "node:util";
15898
16613
 
15899
16614
  // src/editor.ts
15900
16615
  import { spawn as spawn3 } from "node:child_process";
@@ -15934,14 +16649,774 @@ async function defaultSpawnEditor(cli, args) {
15934
16649
  child.unref();
15935
16650
  }
15936
16651
 
16652
+ // src/repo-registry.ts
16653
+ import { randomUUID } from "node:crypto";
16654
+ import { constants } from "node:fs";
16655
+ import {
16656
+ access as access2,
16657
+ mkdir as mkdir3,
16658
+ readFile as readFile3,
16659
+ realpath,
16660
+ rename,
16661
+ rm as rm2,
16662
+ writeFile as writeFile4
16663
+ } from "node:fs/promises";
16664
+ import { homedir as homedir4 } from "node:os";
16665
+ import { basename as basename3, dirname as dirname4, join as join4, resolve as resolve4 } from "node:path";
16666
+ var REGISTRY_FILE_NAME = "repos.json";
16667
+ var MAX_REGISTRY_ENTRIES = 100;
16668
+ var registryLocks = /* @__PURE__ */ new Map();
16669
+ function REGISTRY_FILE_PATH(home = homedir4()) {
16670
+ const configDir = process.env.GJI_CONFIG_DIR;
16671
+ if (configDir) {
16672
+ return join4(resolve4(configDir), REGISTRY_FILE_NAME);
16673
+ }
16674
+ return join4(home, GLOBAL_CONFIG_DIRECTORY, REGISTRY_FILE_NAME);
16675
+ }
16676
+ async function loadRegistry(home = homedir4()) {
16677
+ const path9 = REGISTRY_FILE_PATH(home);
16678
+ try {
16679
+ const raw = await readFile3(path9, "utf8");
16680
+ const parsed = JSON.parse(raw);
16681
+ if (!Array.isArray(parsed)) return [];
16682
+ return parsed.filter(isRegistryEntry);
16683
+ } catch {
16684
+ return [];
16685
+ }
16686
+ }
16687
+ async function canonicalizeRepoPath(repoPath) {
16688
+ try {
16689
+ return await realpath(repoPath);
16690
+ } catch {
16691
+ return resolve4(repoPath);
16692
+ }
16693
+ }
16694
+ async function registerRepo(repoPath, home = homedir4()) {
16695
+ const registryPath = REGISTRY_FILE_PATH(home);
16696
+ await withRegistryLock(registryPath, async () => {
16697
+ const existing = await normalizeRegistryForWrite(await loadRegistry(home));
16698
+ const canonicalRepoPath = await canonicalizeRepoPath(repoPath);
16699
+ if (existing.length > 0 && existing[0].path === canonicalRepoPath) return;
16700
+ const entry = {
16701
+ lastUsed: Date.now(),
16702
+ name: basename3(canonicalRepoPath),
16703
+ path: canonicalRepoPath
16704
+ };
16705
+ const filtered = existing.filter((e2) => e2.path !== canonicalRepoPath);
16706
+ const next = [entry, ...filtered].slice(0, MAX_REGISTRY_ENTRIES);
16707
+ await writeRegistry(next, registryPath);
16708
+ });
16709
+ }
16710
+ async function removeMissingRegistryEntries(paths, home = homedir4()) {
16711
+ if (paths.size === 0) return { removedPaths: [], skippedPaths: [] };
16712
+ const registryPath = REGISTRY_FILE_PATH(home);
16713
+ return withRegistryLock(registryPath, async () => {
16714
+ const entries = await loadRegistry(home);
16715
+ const candidates = entries.filter((entry) => paths.has(entry.path));
16716
+ const status = await Promise.all(
16717
+ candidates.map(async (entry) => ({
16718
+ entry,
16719
+ missing: await isMissingPath(entry.path)
16720
+ }))
16721
+ );
16722
+ const removedPaths = status.filter(({ missing }) => missing).map(({ entry }) => entry.path);
16723
+ const skippedPaths = status.filter(({ missing }) => !missing).map(({ entry }) => entry.path);
16724
+ if (removedPaths.length > 0) {
16725
+ await writeRegistry(
16726
+ entries.filter((entry) => !removedPaths.includes(entry.path)),
16727
+ registryPath
16728
+ );
16729
+ }
16730
+ return { removedPaths, skippedPaths };
16731
+ });
16732
+ }
16733
+ async function withRegistryLock(registryPath, operation) {
16734
+ const previous = registryLocks.get(registryPath);
16735
+ let release;
16736
+ const current = new Promise((resolve6) => {
16737
+ release = resolve6;
16738
+ });
16739
+ registryLocks.set(registryPath, current);
16740
+ if (previous) await previous;
16741
+ try {
16742
+ return await operation();
16743
+ } finally {
16744
+ release();
16745
+ if (registryLocks.get(registryPath) === current) {
16746
+ registryLocks.delete(registryPath);
16747
+ }
16748
+ }
16749
+ }
16750
+ async function isMissingPath(path9) {
16751
+ try {
16752
+ await access2(path9, constants.F_OK);
16753
+ return false;
16754
+ } catch (error) {
16755
+ return isMissingPathError(error);
16756
+ }
16757
+ }
16758
+ function isMissingPathError(error) {
16759
+ if (!(error instanceof Error) || !("code" in error)) return false;
16760
+ const code = error.code;
16761
+ return code === "ENOENT" || code === "ENOTDIR";
16762
+ }
16763
+ async function writeRegistry(entries, registryPath) {
16764
+ await mkdir3(dirname4(registryPath), { recursive: true });
16765
+ const temporaryPath = `${registryPath}.tmp-${process.pid}-${randomUUID()}`;
16766
+ try {
16767
+ await writeFile4(
16768
+ temporaryPath,
16769
+ `${JSON.stringify(entries, null, 2)}
16770
+ `,
16771
+ "utf8"
16772
+ );
16773
+ await rename(temporaryPath, registryPath);
16774
+ } catch (error) {
16775
+ await rm2(temporaryPath, { force: true }).catch(() => void 0);
16776
+ throw error;
16777
+ }
16778
+ }
16779
+ async function normalizeRegistryForWrite(entries) {
16780
+ const normalized = [];
16781
+ const seenPaths = /* @__PURE__ */ new Set();
16782
+ for (const entry of entries) {
16783
+ const canonicalPath = await canonicalizeRepoPath(entry.path);
16784
+ if (seenPaths.has(canonicalPath)) {
16785
+ continue;
16786
+ }
16787
+ seenPaths.add(canonicalPath);
16788
+ normalized.push({
16789
+ ...entry,
16790
+ name: basename3(canonicalPath),
16791
+ path: canonicalPath
16792
+ });
16793
+ }
16794
+ return normalized;
16795
+ }
16796
+ function isRegistryEntry(value) {
16797
+ return typeof value === "object" && value !== null && "path" in value && typeof value.path === "string" && "name" in value && typeof value.name === "string" && "lastUsed" in value && typeof value.lastUsed === "number";
16798
+ }
16799
+
16800
+ // src/shell-setup.ts
16801
+ import { constants as constants2 } from "node:fs";
16802
+ import { access as access3 } from "node:fs/promises";
16803
+ import { delimiter, join as join5 } from "node:path";
16804
+ var SHELL_INTEGRATION_START_MARKER = "# >>> gji init >>>";
16805
+ var SHELL_INTEGRATION_END_MARKER = "# <<< gji init <<<";
16806
+ var ONBOARDING_SHELL_INTEGRATION_START_MARKER = "# >>> gji shell integration >>>";
16807
+ var ONBOARDING_SHELL_INTEGRATION_END_MARKER = "# <<< gji shell integration <<<";
16808
+ async function executableExists(command) {
16809
+ const candidates = command.includes("/") ? [command] : (process.env.PATH ?? "").split(delimiter).filter(Boolean).map((directory) => join5(directory, command));
16810
+ for (const path9 of candidates) {
16811
+ try {
16812
+ await access3(path9, constants2.X_OK);
16813
+ return true;
16814
+ } catch {
16815
+ }
16816
+ }
16817
+ return false;
16818
+ }
16819
+ function resolveShellConfigPath(shell, home) {
16820
+ switch (shell) {
16821
+ case "bash":
16822
+ return join5(home, ".bashrc");
16823
+ case "fish":
16824
+ return join5(home, ".config", "fish", "config.fish");
16825
+ case "zsh":
16826
+ return join5(home, ".zshrc");
16827
+ }
16828
+ }
16829
+ function resolveCompletionPath(shell, home) {
16830
+ switch (shell) {
16831
+ case "bash":
16832
+ return join5(
16833
+ home,
16834
+ ".local",
16835
+ "share",
16836
+ "bash-completion",
16837
+ "completions",
16838
+ "gji"
16839
+ );
16840
+ case "fish":
16841
+ return join5(home, ".config", "fish", "completions", "gji.fish");
16842
+ case "zsh":
16843
+ return join5(home, ".zsh", "completions", "_gji");
16844
+ }
16845
+ }
16846
+ function hasShellIntegration(config, shell) {
16847
+ if (config.includes(SHELL_INTEGRATION_START_MARKER) && config.includes(SHELL_INTEGRATION_END_MARKER) || config.includes(ONBOARDING_SHELL_INTEGRATION_START_MARKER) && config.includes(ONBOARDING_SHELL_INTEGRATION_END_MARKER)) {
16848
+ return true;
16849
+ }
16850
+ if (shell === "fish") return false;
16851
+ return new RegExp(
16852
+ `^[\\t ]*eval\\s+["']?\\$\\(\\s*gji\\s+init\\s+${shell}\\s*\\)["']?\\s*(?:#.*)?$`,
16853
+ "m"
16854
+ ).test(config);
16855
+ }
16856
+
16857
+ // src/doctor.ts
16858
+ var execFileAsync4 = promisify5(execFile4);
16859
+ var MINIMUM_GIT_VERSION = { major: 2, minor: 17 };
16860
+ async function runDoctorCommand(options) {
16861
+ if (options.yes && !options.fix) {
16862
+ const message = "--yes requires --fix";
16863
+ if (options.json) {
16864
+ (options.stderr ?? options.stdout)(
16865
+ `${JSON.stringify({ error: message })}
16866
+ `
16867
+ );
16868
+ } else {
16869
+ (options.stderr ?? options.stdout)(`gji doctor: ${message}
16870
+ `);
16871
+ }
16872
+ return 1;
16873
+ }
16874
+ const home = options.home ?? homedir5();
16875
+ const shell = resolveSupportedShell(
16876
+ void 0,
16877
+ options.shell ?? process.env.SHELL
16878
+ );
16879
+ let inspection = await collectDoctorInspection(options.cwd, home, shell);
16880
+ let fixes = [];
16881
+ if (options.fix) {
16882
+ fixes = buildDoctorFixes(inspection.missingRegistryPaths);
16883
+ if (fixes.length > 0) {
16884
+ const approval = await requestFixApproval(fixes, options);
16885
+ if (approval === "apply") {
16886
+ fixes = await applyDoctorFixes(fixes, home);
16887
+ } else {
16888
+ fixes = fixes.map((fix) => ({
16889
+ ...fix,
16890
+ status: approval,
16891
+ hint: approval === "pending" ? "re-run with --yes to apply this fix without a prompt" : "run gji doctor --fix again to review this fix"
16892
+ }));
16893
+ }
16894
+ }
16895
+ inspection = await collectDoctorInspection(options.cwd, home, shell);
16896
+ }
16897
+ const problems = inspection.checks.filter(
16898
+ (check) => check.status === "fail"
16899
+ ).length;
16900
+ if (options.json) {
16901
+ const output = options.fix ? { checks: inspection.checks, problems, fixes } : { checks: inspection.checks, problems };
16902
+ options.stdout(`${JSON.stringify(output)}
16903
+ `);
16904
+ } else {
16905
+ options.stdout(renderDoctorChecks(inspection.checks, problems));
16906
+ if (options.fix) {
16907
+ options.stdout(renderDoctorFixes(fixes));
16908
+ }
16909
+ }
16910
+ return problems > 0 ? 1 : 0;
16911
+ }
16912
+ async function collectDoctorInspection(cwd, home, shell) {
16913
+ const repository = await detectRepositoryOrSkip(cwd);
16914
+ const globalConfig = await inspectConfig(
16915
+ GLOBAL_CONFIG_FILE_PATH(home),
16916
+ "global",
16917
+ KNOWN_GLOBAL_CONFIG_KEYS
16918
+ );
16919
+ const localConfig = repository ? await inspectConfig(
16920
+ join6(repository.repoRoot, CONFIG_FILE_NAME),
16921
+ "local",
16922
+ KNOWN_CONFIG_KEYS
16923
+ ) : null;
16924
+ const effectiveConfig = resolveEffectiveConfig(
16925
+ globalConfig.config,
16926
+ localConfig?.config ?? {},
16927
+ repository?.repoRoot,
16928
+ home
16929
+ );
16930
+ const registry = await inspectRegistry(home);
16931
+ return {
16932
+ checks: [
16933
+ await checkGitVersion(),
16934
+ await checkShellIntegration(shell, home),
16935
+ await checkCompletion(shell, home),
16936
+ globalConfig.check,
16937
+ localConfig?.check ?? skippedCheck(
16938
+ "local-config",
16939
+ "local config not checked outside a Git repository"
16940
+ ),
16941
+ await checkWorktreeBase(repository, effectiveConfig, home),
16942
+ registry.check,
16943
+ await checkEditor(effectiveConfig)
16944
+ ],
16945
+ missingRegistryPaths: registry.missingPaths
16946
+ };
16947
+ }
16948
+ function buildDoctorFixes(missingRegistryPaths) {
16949
+ if (missingRegistryPaths.length === 0) return [];
16950
+ const count = missingRegistryPaths.length;
16951
+ return [
16952
+ {
16953
+ id: "repo-registry",
16954
+ message: `remove ${count} stale ${count === 1 ? "repository entry" : "repository entries"} from the registry`,
16955
+ paths: missingRegistryPaths,
16956
+ status: "pending"
16957
+ }
16958
+ ];
16959
+ }
16960
+ async function requestFixApproval(fixes, options) {
16961
+ if (options.yes) return "apply";
16962
+ if (!isDoctorInteractive(options)) return "pending";
16963
+ const confirmed = options.confirmFixes ? await options.confirmFixes(fixes) : await ye({
16964
+ initialValue: true,
16965
+ message: `Apply ${fixes.length} automatic ${fixes.length === 1 ? "fix" : "fixes"} (${fixes.flatMap((fix) => fix.paths ?? []).join(", ")})?`
16966
+ });
16967
+ if (pD(confirmed) || !confirmed) return "declined";
16968
+ return "apply";
16969
+ }
16970
+ function isDoctorInteractive(options) {
16971
+ if (options.json || isHeadless()) return false;
16972
+ if (options.interactive !== void 0) return options.interactive;
16973
+ return process.stdin.isTTY === true && process.stdout.isTTY === true;
16974
+ }
16975
+ async function applyDoctorFixes(fixes, home) {
16976
+ return Promise.all(
16977
+ fixes.map(async (fix) => {
16978
+ try {
16979
+ const result = await removeMissingRegistryEntries(
16980
+ new Set(fix.paths ?? []),
16981
+ home
16982
+ );
16983
+ const removedCount = result.removedPaths.length;
16984
+ const skippedCount = result.skippedPaths.length;
16985
+ return {
16986
+ ...fix,
16987
+ message: removedCount === 0 ? "no stale repository entries were removed" : `removed ${removedCount} stale ${removedCount === 1 ? "repository entry" : "repository entries"} from the registry`,
16988
+ status: removedCount === 0 ? "skipped" : "applied",
16989
+ hint: skippedCount > 0 ? `${skippedCount} path(s) were no longer confirmed missing` : void 0
16990
+ };
16991
+ } catch (error) {
16992
+ return {
16993
+ ...fix,
16994
+ status: "failed",
16995
+ hint: error instanceof Error ? error.message : String(error)
16996
+ };
16997
+ }
16998
+ })
16999
+ );
17000
+ }
17001
+ async function detectRepositoryOrSkip(cwd) {
17002
+ try {
17003
+ return await detectRepository(cwd);
17004
+ } catch {
17005
+ return null;
17006
+ }
17007
+ }
17008
+ async function inspectConfig(path9, label, knownKeys) {
17009
+ try {
17010
+ const contents = await readFile4(path9, "utf8");
17011
+ const value = JSON.parse(contents);
17012
+ if (!isConfigObject(value)) {
17013
+ return {
17014
+ check: failedCheck(
17015
+ `${label}-config`,
17016
+ `${label} config must contain a JSON object (${path9})`,
17017
+ "replace the file contents with a JSON object"
17018
+ ),
17019
+ config: {}
17020
+ };
17021
+ }
17022
+ const unknownKeys = Object.keys(value).filter((key) => !knownKeys.has(key));
17023
+ const warning = unknownKeys.length > 0 ? `; warning: unknown ${unknownKeys.length === 1 ? "key" : "keys"} ${unknownKeys.map((key) => `"${key}"`).join(", ")}` : "";
17024
+ return {
17025
+ check: okCheck(
17026
+ `${label}-config`,
17027
+ `${label} config valid (${path9}${warning})`
17028
+ ),
17029
+ config: value
17030
+ };
17031
+ } catch (error) {
17032
+ if (isMissingFileError2(error)) {
17033
+ return {
17034
+ check: okCheck(
17035
+ `${label}-config`,
17036
+ `${label} config not found (optional)`
17037
+ ),
17038
+ config: {}
17039
+ };
17040
+ }
17041
+ return {
17042
+ check: failedCheck(
17043
+ `${label}-config`,
17044
+ `${label} config is invalid (${path9})`,
17045
+ "fix the JSON syntax and run gji doctor again"
17046
+ ),
17047
+ config: {}
17048
+ };
17049
+ }
17050
+ }
17051
+ function isConfigObject(value) {
17052
+ return typeof value === "object" && value !== null && !Array.isArray(value);
17053
+ }
17054
+ function isMissingFileError2(error) {
17055
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
17056
+ }
17057
+ function resolveEffectiveConfig(globalConfig, localConfig, repoRoot, home) {
17058
+ const globalBase = { ...globalConfig };
17059
+ const repos = globalBase.repos;
17060
+ delete globalBase.repos;
17061
+ return {
17062
+ ...globalBase,
17063
+ ...resolvePerRepoConfig(repos, repoRoot, home),
17064
+ ...localConfig
17065
+ };
17066
+ }
17067
+ function resolvePerRepoConfig(repos, repoRoot, home) {
17068
+ if (!repoRoot || !isConfigObject(repos)) {
17069
+ return {};
17070
+ }
17071
+ for (const [path9, config] of Object.entries(repos)) {
17072
+ if (expandTilde2(path9, home) === repoRoot && isConfigObject(config)) {
17073
+ return config;
17074
+ }
17075
+ }
17076
+ return {};
17077
+ }
17078
+ function expandTilde2(path9, home) {
17079
+ if (path9 === "~") return home;
17080
+ if (path9.startsWith("~/")) return join6(home, path9.slice(2));
17081
+ return path9;
17082
+ }
17083
+ async function checkGitVersion() {
17084
+ try {
17085
+ const { stdout: stdout2 } = await execFileAsync4("git", ["--version"]);
17086
+ const version = parseGitVersion(stdout2);
17087
+ if (!version) {
17088
+ return failedCheck(
17089
+ "git-version",
17090
+ "could not parse the installed Git version",
17091
+ "install Git 2.17 or newer"
17092
+ );
17093
+ }
17094
+ if (isGitVersionSupported(version)) {
17095
+ return okCheck("git-version", `git ${version.raw}`);
17096
+ }
17097
+ return failedCheck(
17098
+ "git-version",
17099
+ `git ${version.raw} is too old (requires Git 2.17 or newer)`,
17100
+ "upgrade Git and run gji doctor again"
17101
+ );
17102
+ } catch {
17103
+ return failedCheck(
17104
+ "git-version",
17105
+ "git is not available on PATH",
17106
+ "install Git 2.17 or newer"
17107
+ );
17108
+ }
17109
+ }
17110
+ function parseGitVersion(output) {
17111
+ const match = /(?:^|\s)(\d+)\.(\d+)(?:\.(\d+))?/.exec(output);
17112
+ if (!match) return null;
17113
+ return {
17114
+ major: Number(match[1]),
17115
+ minor: Number(match[2]),
17116
+ raw: match.slice(1).filter(Boolean).join(".")
17117
+ };
17118
+ }
17119
+ function isGitVersionSupported(version) {
17120
+ return version.major > MINIMUM_GIT_VERSION.major || version.major === MINIMUM_GIT_VERSION.major && version.minor >= MINIMUM_GIT_VERSION.minor;
17121
+ }
17122
+ async function checkShellIntegration(shell, home) {
17123
+ if (!shell) {
17124
+ return skippedCheck(
17125
+ "shell-integration",
17126
+ "shell integration not checked (unable to detect a supported shell)"
17127
+ );
17128
+ }
17129
+ const rcPath = resolveShellConfigPath(shell, home);
17130
+ try {
17131
+ const contents = await readFile4(rcPath, "utf8");
17132
+ if (hasShellIntegration(contents, shell)) {
17133
+ return okCheck(
17134
+ "shell-integration",
17135
+ `${shell} integration found in ${rcPath}`
17136
+ );
17137
+ }
17138
+ } catch (error) {
17139
+ if (isMissingFileError2(error)) {
17140
+ return failedCheck(
17141
+ "shell-integration",
17142
+ `shell integration not found because ${rcPath} does not exist`,
17143
+ `create ${rcPath}, then add: eval "$(gji init ${shell})"`
17144
+ );
17145
+ }
17146
+ return failedCheck(
17147
+ "shell-integration",
17148
+ `could not read ${rcPath}`,
17149
+ `add: eval "$(gji init ${shell})"`
17150
+ );
17151
+ }
17152
+ return failedCheck(
17153
+ "shell-integration",
17154
+ `shell integration not found in ${rcPath}`,
17155
+ `add: eval "$(gji init ${shell})"`
17156
+ );
17157
+ }
17158
+ async function checkCompletion(shell, home) {
17159
+ if (!shell) {
17160
+ return skippedCheck(
17161
+ "completion",
17162
+ "shell completion not checked (unable to detect a supported shell)"
17163
+ );
17164
+ }
17165
+ const path9 = resolveCompletionPath(shell, home);
17166
+ if (await pathExists(path9)) {
17167
+ return okCheck("completion", `${shell} completion installed (${path9})`);
17168
+ }
17169
+ return skippedCheck(
17170
+ "completion",
17171
+ `${shell} completion not installed (optional)`,
17172
+ `run: gji completion ${shell} > ${path9}`
17173
+ );
17174
+ }
17175
+ async function checkWorktreeBase(repository, config, home) {
17176
+ if (!repository) {
17177
+ return skippedCheck(
17178
+ "worktree-base",
17179
+ "worktree base not checked outside a Git repository"
17180
+ );
17181
+ }
17182
+ const basePath = resolveWorktreeBase(repository.repoRoot, config, home);
17183
+ const writablePath = await findNearestExistingPath(basePath);
17184
+ if (!writablePath) {
17185
+ return failedCheck(
17186
+ "worktree-base",
17187
+ `worktree base cannot be created (${basePath})`,
17188
+ "create a writable parent directory or update worktreePath"
17189
+ );
17190
+ }
17191
+ try {
17192
+ await access4(writablePath, constants3.W_OK | constants3.X_OK);
17193
+ return okCheck("worktree-base", `worktree base writable (${basePath})`);
17194
+ } catch {
17195
+ return failedCheck(
17196
+ "worktree-base",
17197
+ `worktree base is not writable (${writablePath})`,
17198
+ "update worktreePath to use a writable directory"
17199
+ );
17200
+ }
17201
+ }
17202
+ function resolveWorktreeBase(repoRoot, config, home) {
17203
+ const configuredPath = config.worktreePath;
17204
+ if (typeof configuredPath === "string" && (configuredPath.startsWith("/") || configuredPath.startsWith("~"))) {
17205
+ return expandTilde2(configuredPath, home);
17206
+ }
17207
+ return join6(dirname5(repoRoot), "worktrees", basename4(repoRoot));
17208
+ }
17209
+ async function findNearestExistingPath(path9) {
17210
+ let candidate = path9;
17211
+ while (true) {
17212
+ try {
17213
+ await access4(candidate, constants3.F_OK);
17214
+ return candidate;
17215
+ } catch {
17216
+ const parent = dirname5(candidate);
17217
+ if (parent === candidate) return null;
17218
+ candidate = parent;
17219
+ }
17220
+ }
17221
+ }
17222
+ async function inspectRegistry(home) {
17223
+ const entries = await loadRegistry(home);
17224
+ const missingEntries = await Promise.all(
17225
+ entries.map(async (entry) => ({
17226
+ entry,
17227
+ status: await inspectRegistryPath(entry.path)
17228
+ }))
17229
+ );
17230
+ const missingCount = missingEntries.filter(
17231
+ ({ status }) => status === "missing"
17232
+ ).length;
17233
+ const unreadableCount = missingEntries.filter(
17234
+ ({ status }) => status === "unreadable"
17235
+ ).length;
17236
+ const missingPaths = missingEntries.filter(({ status }) => status === "missing").map(({ entry }) => entry.path);
17237
+ if (missingCount === 0 && unreadableCount === 0) {
17238
+ return {
17239
+ check: okCheck(
17240
+ "repo-registry",
17241
+ `${entries.length} repos registered, all reachable`
17242
+ ),
17243
+ missingPaths
17244
+ };
17245
+ }
17246
+ const messageParts = [];
17247
+ if (missingCount > 0) {
17248
+ messageParts.push(
17249
+ `${missingCount} ${missingCount === 1 ? "path is" : "paths are"} missing`
17250
+ );
17251
+ }
17252
+ if (unreadableCount > 0) {
17253
+ messageParts.push(
17254
+ `${unreadableCount} ${unreadableCount === 1 ? "path is" : "paths are"} not accessible`
17255
+ );
17256
+ }
17257
+ return {
17258
+ check: failedCheck(
17259
+ "repo-registry",
17260
+ `${entries.length} repos registered, ${messageParts.join(", ")}`,
17261
+ missingCount > 0 ? `remove confirmed stale entries from ${REGISTRY_FILE_PATH(home)}; check permissions for inaccessible paths` : "check permissions for inaccessible paths before removing registry entries"
17262
+ ),
17263
+ missingPaths
17264
+ };
17265
+ }
17266
+ async function inspectRegistryPath(path9) {
17267
+ try {
17268
+ await access4(path9, constants3.F_OK);
17269
+ return "exists";
17270
+ } catch (error) {
17271
+ if (isMissingPathError2(error)) return "missing";
17272
+ return "unreadable";
17273
+ }
17274
+ }
17275
+ function isMissingPathError2(error) {
17276
+ if (!(error instanceof Error) || !("code" in error)) return false;
17277
+ const code = error.code;
17278
+ return code === "ENOENT" || code === "ENOTDIR";
17279
+ }
17280
+ async function pathExists(path9) {
17281
+ try {
17282
+ await access4(path9, constants3.F_OK);
17283
+ return true;
17284
+ } catch {
17285
+ return false;
17286
+ }
17287
+ }
17288
+ async function checkEditor(config) {
17289
+ const editor = config.editor;
17290
+ if (typeof editor !== "string" || editor.length === 0) {
17291
+ return skippedCheck("editor", "editor not configured (optional)");
17292
+ }
17293
+ if (await executableExists(editor)) {
17294
+ return okCheck("editor", `editor "${editor}" found on PATH`);
17295
+ }
17296
+ const knownEditor = EDITORS.some(({ cli }) => cli === editor);
17297
+ return failedCheck(
17298
+ "editor",
17299
+ `editor "${editor}" was not found on PATH`,
17300
+ knownEditor ? `install ${editor} or choose another editor with: gji open --save` : "choose another editor with: gji open --save"
17301
+ );
17302
+ }
17303
+ function okCheck(id, message) {
17304
+ return { id, message, status: "ok" };
17305
+ }
17306
+ function failedCheck(id, message, hint) {
17307
+ return { hint, id, message, status: "fail" };
17308
+ }
17309
+ function skippedCheck(id, message, hint) {
17310
+ return { hint, id, message, status: "skip" };
17311
+ }
17312
+ function renderDoctorChecks(checks, problems) {
17313
+ const lines = ["gji doctor", ""];
17314
+ for (const check of checks) {
17315
+ lines.push(` ${statusSymbol(check.status)} ${check.message}`);
17316
+ if (check.hint) lines.push(` ${check.hint}`);
17317
+ }
17318
+ lines.push(
17319
+ "",
17320
+ `${problems} ${problems === 1 ? "problem" : "problems"} found.`
17321
+ );
17322
+ return `${lines.join("\n")}
17323
+ `;
17324
+ }
17325
+ function renderDoctorFixes(fixes) {
17326
+ const lines = ["", "Automatic fixes:"];
17327
+ if (fixes.length === 0) {
17328
+ lines.push(" No automatic fixes available.");
17329
+ } else {
17330
+ for (const fix of fixes) {
17331
+ lines.push(` ${fixStatusSymbol(fix.status)} ${fix.message}`);
17332
+ if (fix.paths && fix.paths.length > 0) {
17333
+ for (const path9 of fix.paths) lines.push(` ${path9}`);
17334
+ }
17335
+ if (fix.hint) lines.push(` ${fix.hint}`);
17336
+ }
17337
+ }
17338
+ return `${lines.join("\n")}
17339
+ `;
17340
+ }
17341
+ function fixStatusSymbol(status) {
17342
+ switch (status) {
17343
+ case "applied":
17344
+ return "\u2713";
17345
+ case "declined":
17346
+ return "-";
17347
+ case "failed":
17348
+ return "\u2717";
17349
+ case "pending":
17350
+ return "!";
17351
+ case "skipped":
17352
+ return "-";
17353
+ }
17354
+ }
17355
+ function statusSymbol(status) {
17356
+ switch (status) {
17357
+ case "fail":
17358
+ return "\u2717";
17359
+ case "ok":
17360
+ return "\u2713";
17361
+ case "skip":
17362
+ return "-";
17363
+ }
17364
+ }
17365
+
17366
+ // src/go.ts
17367
+ import { basename as basename7 } from "node:path";
17368
+
17369
+ // src/warp.ts
17370
+ import { realpath as realpath2 } from "node:fs/promises";
17371
+ import { basename as basename6, resolve as resolve5 } from "node:path";
17372
+
17373
+ // src/new.ts
17374
+ import { execFile as execFile5 } from "node:child_process";
17375
+ import { mkdir as mkdir5 } from "node:fs/promises";
17376
+ import { basename as basename5, dirname as dirname7 } from "node:path";
17377
+ import { promisify as promisify6 } from "node:util";
17378
+
17379
+ // src/conflict.ts
17380
+ import { constants as constants4 } from "node:fs";
17381
+ import { access as access5 } from "node:fs/promises";
17382
+ async function pathExists2(path9) {
17383
+ try {
17384
+ await access5(path9, constants4.F_OK);
17385
+ return true;
17386
+ } catch {
17387
+ return false;
17388
+ }
17389
+ }
17390
+ async function promptForPathConflict(path9) {
17391
+ const choice = await ve({
17392
+ message: `Target path already exists: ${path9}`,
17393
+ options: [
17394
+ {
17395
+ value: "abort",
17396
+ label: "Abort",
17397
+ hint: "Keep the existing directory untouched"
17398
+ },
17399
+ {
17400
+ value: "reuse",
17401
+ label: "Reuse path",
17402
+ hint: "Print the existing path and stop"
17403
+ }
17404
+ ]
17405
+ });
17406
+ if (pD(choice)) {
17407
+ return "abort";
17408
+ }
17409
+ return choice;
17410
+ }
17411
+
15937
17412
  // src/file-sync.ts
15938
- import { copyFile, mkdir as mkdir3, stat } from "node:fs/promises";
15939
- import { dirname as dirname4, isAbsolute as isAbsolute2, join as join4, normalize } from "node:path";
17413
+ import { copyFile, mkdir as mkdir4, stat } from "node:fs/promises";
17414
+ import { dirname as dirname6, isAbsolute as isAbsolute2, join as join7, normalize } from "node:path";
15940
17415
  async function syncFiles(mainRoot, targetPath, patterns) {
15941
17416
  for (const pattern of patterns) {
15942
17417
  const normalized = validateSyncFilePattern(pattern);
15943
- const sourcePath = join4(mainRoot, normalized);
15944
- const destPath = join4(targetPath, normalized);
17418
+ const sourcePath = join7(mainRoot, normalized);
17419
+ const destPath = join7(targetPath, normalized);
15945
17420
  const sourceExists = await fileExists(sourcePath);
15946
17421
  if (!sourceExists) {
15947
17422
  continue;
@@ -15950,7 +17425,7 @@ async function syncFiles(mainRoot, targetPath, patterns) {
15950
17425
  if (destExists) {
15951
17426
  continue;
15952
17427
  }
15953
- await mkdir3(dirname4(destPath), { recursive: true });
17428
+ await mkdir4(dirname6(destPath), { recursive: true });
15954
17429
  await copyFile(sourcePath, destPath);
15955
17430
  }
15956
17431
  }
@@ -15987,8 +17462,8 @@ function isNotFoundError(error) {
15987
17462
  import { spawn as spawn4 } from "node:child_process";
15988
17463
 
15989
17464
  // src/package-manager.ts
15990
- import { access as access3, readdir } from "node:fs/promises";
15991
- import { join as join5 } from "node:path";
17465
+ import { access as access6, readdir } from "node:fs/promises";
17466
+ import { join as join8 } from "node:path";
15992
17467
  var ENTRIES = [
15993
17468
  // JavaScript / TypeScript
15994
17469
  { name: "pnpm", signals: ["pnpm-lock.yaml"], command: "pnpm install" },
@@ -16118,7 +17593,7 @@ async function detectPackageManager(repoRoot) {
16118
17593
  async function matchesExact(repoRoot, signals) {
16119
17594
  for (const signal of signals) {
16120
17595
  try {
16121
- await access3(join5(repoRoot, signal));
17596
+ await access6(join8(repoRoot, signal));
16122
17597
  return true;
16123
17598
  } catch {
16124
17599
  }
@@ -16282,7 +17757,7 @@ function isPlainObject2(value) {
16282
17757
  }
16283
17758
 
16284
17759
  // src/new.ts
16285
- var execFileAsync3 = promisify4(execFile3);
17760
+ var execFileAsync5 = promisify6(execFile5);
16286
17761
  var NEW_OUTPUT_FILE_ENV = "GJI_NEW_OUTPUT_FILE";
16287
17762
  function createNewCommand(dependencies = {}) {
16288
17763
  const createBranchPlaceholder = dependencies.createBranchPlaceholder ?? generateBranchPlaceholder;
@@ -16350,11 +17825,11 @@ function createNewCommand(dependencies = {}) {
16350
17825
  worktreeName,
16351
17826
  configuredBasePath
16352
17827
  );
16353
- if (!usesGeneratedDetachedName && await pathExists(worktreePath)) {
17828
+ if (!usesGeneratedDetachedName && await pathExists2(worktreePath)) {
16354
17829
  if (options.force) {
16355
17830
  if (!options.dryRun) {
16356
17831
  try {
16357
- await execFileAsync3(
17832
+ await execFileAsync5(
16358
17833
  "git",
16359
17834
  ["worktree", "remove", "--force", worktreePath],
16360
17835
  { cwd: repository.repoRoot }
@@ -16375,7 +17850,7 @@ function createNewCommand(dependencies = {}) {
16375
17850
  }
16376
17851
  if (!options.detached) {
16377
17852
  try {
16378
- await execFileAsync3("git", ["branch", "-D", worktreeName], {
17853
+ await execFileAsync5("git", ["branch", "-D", worktreeName], {
16379
17854
  cwd: repository.repoRoot
16380
17855
  });
16381
17856
  } catch {
@@ -16432,9 +17907,9 @@ function createNewCommand(dependencies = {}) {
16432
17907
  }
16433
17908
  return 0;
16434
17909
  }
16435
- await mkdir4(dirname5(worktreePath), { recursive: true });
17910
+ await mkdir5(dirname7(worktreePath), { recursive: true });
16436
17911
  const gitArgs = options.detached ? ["worktree", "add", "--detach", worktreePath] : await localBranchExists(repository.repoRoot, worktreeName) ? ["worktree", "add", worktreePath, worktreeName] : ["worktree", "add", "-b", worktreeName, worktreePath];
16437
- await execFileAsync3("git", gitArgs, { cwd: repository.repoRoot });
17912
+ await execFileAsync5("git", gitArgs, { cwd: repository.repoRoot });
16438
17913
  const syncPatterns = Array.isArray(config.syncFiles) ? config.syncFiles.filter(
16439
17914
  (p2) => typeof p2 === "string"
16440
17915
  ) : [];
@@ -16463,7 +17938,7 @@ function createNewCommand(dependencies = {}) {
16463
17938
  {
16464
17939
  branch: worktreeName,
16465
17940
  path: worktreePath,
16466
- repo: basename3(repository.repoRoot)
17941
+ repo: basename5(repository.repoRoot)
16467
17942
  },
16468
17943
  options.stderr
16469
17944
  );
@@ -16615,7 +18090,7 @@ async function resolveUniqueDetachedWorktreePath(repoRoot, baseName, basePath) {
16615
18090
  candidateName,
16616
18091
  basePath
16617
18092
  );
16618
- if (!await pathExists(candidatePath)) {
18093
+ if (!await pathExists2(candidatePath)) {
16619
18094
  return candidatePath;
16620
18095
  }
16621
18096
  attempt += 1;
@@ -16638,7 +18113,7 @@ async function defaultPromptForBranch(placeholder) {
16638
18113
  }
16639
18114
  async function localBranchExists(repoRoot, branchName) {
16640
18115
  try {
16641
- await execFileAsync3(
18116
+ await execFileAsync5(
16642
18117
  "git",
16643
18118
  ["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`],
16644
18119
  { cwd: repoRoot }
@@ -16683,74 +18158,6 @@ async function openWorktree(worktreePath, editorCli, spawnFn, stderr) {
16683
18158
  }
16684
18159
  }
16685
18160
 
16686
- // src/repo-registry.ts
16687
- import { mkdir as mkdir5, readFile as readFile3, realpath, writeFile as writeFile4 } from "node:fs/promises";
16688
- import { homedir as homedir4 } from "node:os";
16689
- import { basename as basename4, dirname as dirname6, join as join6, resolve as resolve4 } from "node:path";
16690
- var REGISTRY_FILE_NAME = "repos.json";
16691
- var MAX_REGISTRY_ENTRIES = 100;
16692
- function REGISTRY_FILE_PATH(home = homedir4()) {
16693
- const configDir = process.env.GJI_CONFIG_DIR;
16694
- if (configDir) {
16695
- return join6(resolve4(configDir), REGISTRY_FILE_NAME);
16696
- }
16697
- return join6(home, GLOBAL_CONFIG_DIRECTORY, REGISTRY_FILE_NAME);
16698
- }
16699
- async function loadRegistry(home = homedir4()) {
16700
- const path9 = REGISTRY_FILE_PATH(home);
16701
- try {
16702
- const raw = await readFile3(path9, "utf8");
16703
- const parsed = JSON.parse(raw);
16704
- if (!Array.isArray(parsed)) return [];
16705
- return parsed.filter(isRegistryEntry);
16706
- } catch {
16707
- return [];
16708
- }
16709
- }
16710
- async function canonicalizeRepoPath(repoPath) {
16711
- try {
16712
- return await realpath(repoPath);
16713
- } catch {
16714
- return resolve4(repoPath);
16715
- }
16716
- }
16717
- async function registerRepo(repoPath, home = homedir4()) {
16718
- const registryPath = REGISTRY_FILE_PATH(home);
16719
- const existing = await normalizeRegistryForWrite(await loadRegistry(home));
16720
- const canonicalRepoPath = await canonicalizeRepoPath(repoPath);
16721
- if (existing.length > 0 && existing[0].path === canonicalRepoPath) return;
16722
- const entry = {
16723
- lastUsed: Date.now(),
16724
- name: basename4(canonicalRepoPath),
16725
- path: canonicalRepoPath
16726
- };
16727
- const filtered = existing.filter((e2) => e2.path !== canonicalRepoPath);
16728
- const next = [entry, ...filtered].slice(0, MAX_REGISTRY_ENTRIES);
16729
- await mkdir5(dirname6(registryPath), { recursive: true });
16730
- await writeFile4(registryPath, `${JSON.stringify(next, null, 2)}
16731
- `, "utf8");
16732
- }
16733
- async function normalizeRegistryForWrite(entries) {
16734
- const normalized = [];
16735
- const seenPaths = /* @__PURE__ */ new Set();
16736
- for (const entry of entries) {
16737
- const canonicalPath = await canonicalizeRepoPath(entry.path);
16738
- if (seenPaths.has(canonicalPath)) {
16739
- continue;
16740
- }
16741
- seenPaths.add(canonicalPath);
16742
- normalized.push({
16743
- ...entry,
16744
- name: basename4(canonicalPath),
16745
- path: canonicalPath
16746
- });
16747
- }
16748
- return normalized;
16749
- }
16750
- function isRegistryEntry(value) {
16751
- return typeof value === "object" && value !== null && "path" in value && typeof value.path === "string" && "name" in value && typeof value.name === "string" && "lastUsed" in value && typeof value.lastUsed === "number";
16752
- }
16753
-
16754
18161
  // src/warp.ts
16755
18162
  var WARP_OUTPUT_FILE_ENV = "GJI_WARP_OUTPUT_FILE";
16756
18163
  async function runWarpCommand(options) {
@@ -16782,7 +18189,8 @@ async function runWarpNavigate(options) {
16782
18189
  const target = await resolveWarpTarget({
16783
18190
  ...options,
16784
18191
  commandName: "gji warp",
16785
- json: options.json
18192
+ json: options.json,
18193
+ queryPullRequests: options.queryPullRequests
16786
18194
  });
16787
18195
  if (!target) return 1;
16788
18196
  if (options.json) {
@@ -16862,7 +18270,7 @@ async function deduplicateRegistryForNew(registry) {
16862
18270
  seenPaths.add(canonicalPath);
16863
18271
  deduplicated.push({
16864
18272
  ...entry,
16865
- name: basename5(canonicalPath),
18273
+ name: basename6(canonicalPath),
16866
18274
  path: canonicalPath
16867
18275
  });
16868
18276
  }
@@ -16899,15 +18307,16 @@ async function resolveWarpTarget(options) {
16899
18307
  const results = await Promise.allSettled(
16900
18308
  registry.map(async (entry) => {
16901
18309
  const worktrees = await listWorktrees(entry.path);
16902
- return { repoName: entry.name, worktrees };
18310
+ return { repoName: entry.name, repoRoot: entry.path, worktrees };
16903
18311
  })
16904
18312
  );
16905
18313
  const allItems = [];
16906
18314
  for (const result of results) {
16907
18315
  if (result.status === "rejected") continue;
16908
- const { repoName, worktrees } = result.value;
18316
+ const { repoName, repoRoot, worktrees } = result.value;
16909
18317
  for (const worktree of worktrees) {
16910
18318
  allItems.push({
18319
+ repoRoot,
16911
18320
  repoName,
16912
18321
  worktree: {
16913
18322
  ...worktree,
@@ -16921,6 +18330,7 @@ async function resolveWarpTarget(options) {
16921
18330
  return null;
16922
18331
  }
16923
18332
  const promptSources = allItems.map((item) => ({
18333
+ repoRoot: item.repoRoot,
16924
18334
  repoName: item.repoName,
16925
18335
  worktree: item.worktree
16926
18336
  }));
@@ -16932,7 +18342,9 @@ async function resolveWarpTarget(options) {
16932
18342
  }
16933
18343
  return { branch: match.worktree.branch, path: match.worktree.path };
16934
18344
  }
16935
- const promptEntries = await buildWorktreePromptEntries(promptSources);
18345
+ const promptEntries = await buildWorktreePromptEntries(promptSources, {
18346
+ queryPullRequests: options.queryPullRequests
18347
+ });
16936
18348
  const path9 = await promptForWarpTarget(promptEntries);
16937
18349
  if (!path9) {
16938
18350
  options.stderr("Aborted\n");
@@ -16980,10 +18392,13 @@ function createGoCommand(dependencies = {}) {
16980
18392
  return 1;
16981
18393
  }
16982
18394
  const promptSources = worktrees.map((worktree) => ({
18395
+ repoRoot: repository.repoRoot,
16983
18396
  repoName: repository.repoName,
16984
18397
  worktree
16985
18398
  }));
16986
- const promptEntries = options.branch ? [] : await buildWorktreePromptEntries(promptSources);
18399
+ const promptEntries = options.branch ? [] : await buildWorktreePromptEntries(promptSources, {
18400
+ queryPullRequests: dependencies.queryPullRequests
18401
+ });
16987
18402
  const queried = options.branch ? resolveWorktreeQuery(promptSources, options.branch) : null;
16988
18403
  const prompted = options.branch ? null : await prompt(promptEntries);
16989
18404
  const resolvedPath = options.branch ? queried?.worktree.path : prompted ?? void 0;
@@ -17011,7 +18426,7 @@ function createGoCommand(dependencies = {}) {
17011
18426
  {
17012
18427
  branch: chosenWorktree?.branch ?? void 0,
17013
18428
  path: resolvedPath,
17014
- repo: basename6(repository.repoRoot)
18429
+ repo: basename7(repository.repoRoot)
17015
18430
  },
17016
18431
  options.stderr
17017
18432
  );
@@ -17042,11 +18457,11 @@ async function runHistoryCommand(options) {
17042
18457
  }
17043
18458
 
17044
18459
  // src/init.ts
17045
- import { mkdir as mkdir6, readFile as readFile4, writeFile as writeFile5 } from "node:fs/promises";
17046
- import { homedir as homedir5 } from "node:os";
17047
- import { dirname as dirname7, join as join7 } from "node:path";
17048
- var START_MARKER = "# >>> gji init >>>";
17049
- var END_MARKER = "# <<< gji init <<<";
18460
+ import { constants as constants5 } from "node:fs";
18461
+ import { access as access7, mkdir as mkdir6, readFile as readFile5, writeFile as writeFile5 } from "node:fs/promises";
18462
+ import { homedir as homedir6 } from "node:os";
18463
+ import { dirname as dirname8 } from "node:path";
18464
+ var ZSH_COMPLETION_PATH_LINE = "fpath=(~/.zsh/completions $fpath)";
17050
18465
  var SHELL_WRAPPED_COMMANDS = [
17051
18466
  {
17052
18467
  bypassOptions: ["--help"],
@@ -17097,10 +18512,256 @@ var SHELL_WRAPPED_COMMANDS = [
17097
18512
  names: ["warp"],
17098
18513
  tempPrefix: "gji-warp"
17099
18514
  }
17100
- ];
17101
- async function runInitCommand(options) {
18515
+ ];
18516
+ async function runInitCommand(options) {
18517
+ if (options.shell === void 0) {
18518
+ return runOnboardingInitCommand(options);
18519
+ }
18520
+ return runLegacyInitCommand(options);
18521
+ }
18522
+ async function runOnboardingInitCommand(options) {
18523
+ if (options.json || isHeadless() || !canRunOnboarding(options)) {
18524
+ return writeNonInteractiveInitError(options);
18525
+ }
18526
+ const home = options.home ?? homedir6();
18527
+ const context = {
18528
+ detectedShell: resolveSupportedShell(void 0, process.env.SHELL),
18529
+ home
18530
+ };
18531
+ if (options.promptForOnboarding === void 0) {
18532
+ return runDefaultOnboarding(context);
18533
+ }
18534
+ const result = await options.promptForOnboarding(context);
18535
+ if (!result) {
18536
+ options.stderr?.("Aborted.\n");
18537
+ return 1;
18538
+ }
18539
+ await applyOnboardingResult(result, home);
18540
+ return 0;
18541
+ }
18542
+ function canRunOnboarding(options) {
18543
+ return options.interactive ?? (options.promptForOnboarding !== void 0 || process.stdin.isTTY === true && process.stdout.isTTY === true);
18544
+ }
18545
+ function writeNonInteractiveInitError(options) {
18546
+ const error = "run `gji init <shell> --write` in non-interactive mode";
18547
+ options.stderr?.(
18548
+ options.json ? `${JSON.stringify({ error })}
18549
+ ` : `${error}
18550
+ `
18551
+ );
18552
+ return 1;
18553
+ }
18554
+ async function runDefaultOnboarding(context) {
18555
+ Ie("gji init");
18556
+ const shell = await ve({
18557
+ message: "Which shell should gji configure?",
18558
+ initialValue: context.detectedShell ?? "zsh",
18559
+ options: [
18560
+ { value: "zsh", label: "zsh" },
18561
+ { value: "bash", label: "bash" },
18562
+ { value: "fish", label: "fish" }
18563
+ ]
18564
+ });
18565
+ if (pD(shell)) return abortDefaultOnboarding();
18566
+ const rcPath = resolveShellConfigPath(shell, context.home);
18567
+ const currentConfig = await readExistingConfig(rcPath);
18568
+ const shellIntegration = await promptForShellIntegration(
18569
+ currentConfig,
18570
+ rcPath,
18571
+ shell
18572
+ );
18573
+ if (shellIntegration === null) return abortDefaultOnboarding();
18574
+ const completionPath = resolveCompletionPath(shell, context.home);
18575
+ const completionAlreadyInstalled = await fileExists2(completionPath);
18576
+ await applyShellIntegration(shell, shellIntegration, context.home);
18577
+ const installCompletion = await promptForCompletion(shell, completionPath);
18578
+ if (installCompletion === null) return abortDefaultOnboarding();
18579
+ if (installCompletion) {
18580
+ await mkdir6(dirname8(completionPath), { recursive: true });
18581
+ await writeFile5(completionPath, renderShellCompletion(shell), "utf8");
18582
+ }
18583
+ if (shell === "zsh" && (completionAlreadyInstalled || installCompletion)) {
18584
+ await ensureZshCompletionPath(rcPath);
18585
+ }
18586
+ const editor = await promptForEditor();
18587
+ if (editor === null) return abortDefaultOnboarding();
18588
+ if (editor) {
18589
+ await updateGlobalConfigKey("editor", editor, context.home);
18590
+ }
18591
+ Se(
18592
+ `Setup complete. Restart your shell or run: source ${rcPath}
18593
+ Verify with: gji doctor`
18594
+ );
18595
+ return 0;
18596
+ }
18597
+ async function applyShellIntegration(shell, choice, home) {
18598
+ if (choice === "install") {
18599
+ await installOnboardingShellIntegration(shell, home);
18600
+ }
18601
+ if (choice !== "skip") {
18602
+ await updateGlobalConfigKey("shellIntegration", true, home);
18603
+ }
18604
+ }
18605
+ function abortDefaultOnboarding() {
18606
+ Se("Aborted.");
18607
+ return 1;
18608
+ }
18609
+ async function promptForShellIntegration(currentConfig, rcPath, shell) {
18610
+ if (currentConfig.includes(ONBOARDING_SHELL_INTEGRATION_START_MARKER) && currentConfig.includes(ONBOARDING_SHELL_INTEGRATION_END_MARKER)) {
18611
+ M2.success(`Shell integration marker found in ${rcPath}; refreshing it.`);
18612
+ return "install";
18613
+ }
18614
+ if (hasShellIntegration(currentConfig, shell)) {
18615
+ M2.success(`Shell integration already installed in ${rcPath} \u2713`);
18616
+ return "existing";
18617
+ }
18618
+ const confirmed = await ye({
18619
+ message: `Install shell integration in ${rcPath}?`,
18620
+ initialValue: true
18621
+ });
18622
+ if (pD(confirmed)) return null;
18623
+ return confirmed ? "install" : "skip";
18624
+ }
18625
+ async function promptForCompletion(shell, completionPath) {
18626
+ if (await fileExists2(completionPath)) {
18627
+ M2.success(`${shell} completion already installed at ${completionPath} \u2713`);
18628
+ return false;
18629
+ }
18630
+ const confirmed = await ye({
18631
+ message: `Install ${shell} completion at ${completionPath}?`,
18632
+ initialValue: true
18633
+ });
18634
+ if (pD(confirmed)) return null;
18635
+ return confirmed;
18636
+ }
18637
+ async function promptForEditor() {
18638
+ const availableEditors = await findAvailableEditors();
18639
+ if (availableEditors.length === 0) {
18640
+ M2.info("No supported editor CLIs found on PATH; skipping editor setup.");
18641
+ return void 0;
18642
+ }
18643
+ const skipValue = "__gji_skip_editor__";
18644
+ const selected = await ve({
18645
+ message: "Which editor should gji use?",
18646
+ options: [
18647
+ ...availableEditors.map(({ cli, name }) => ({
18648
+ value: cli,
18649
+ label: name,
18650
+ hint: cli
18651
+ })),
18652
+ { value: skipValue, label: "Skip" }
18653
+ ]
18654
+ });
18655
+ if (pD(selected)) return null;
18656
+ return selected === skipValue ? void 0 : selected;
18657
+ }
18658
+ async function findAvailableEditors() {
18659
+ const availableEditors = [];
18660
+ for (const editor of EDITORS) {
18661
+ if (await executableExists(editor.cli)) {
18662
+ availableEditors.push(editor);
18663
+ }
18664
+ }
18665
+ return availableEditors;
18666
+ }
18667
+ async function applyOnboardingResult(result, home) {
18668
+ const rcPath = resolveShellConfigPath(result.shell, home);
18669
+ const completionPath = resolveCompletionPath(result.shell, home);
18670
+ const completionAlreadyInstalled = await fileExists2(completionPath);
18671
+ await applyShellIntegration(result.shell, result.shellIntegration, home);
18672
+ if (result.installCompletion) {
18673
+ await mkdir6(dirname8(completionPath), { recursive: true });
18674
+ await writeFile5(
18675
+ completionPath,
18676
+ renderShellCompletion(result.shell),
18677
+ "utf8"
18678
+ );
18679
+ }
18680
+ if (result.shell === "zsh" && (completionAlreadyInstalled || result.installCompletion)) {
18681
+ await ensureZshCompletionPath(rcPath);
18682
+ }
18683
+ if (result.editor) {
18684
+ await updateGlobalConfigKey("editor", result.editor, home);
18685
+ }
18686
+ }
18687
+ async function installOnboardingShellIntegration(shell, home) {
18688
+ const rcPath = resolveShellConfigPath(shell, home);
18689
+ await mkdir6(dirname8(rcPath), { recursive: true });
18690
+ const current = await readExistingConfig(rcPath);
18691
+ if (shell === "fish") {
18692
+ await writeFile5(
18693
+ rcPath,
18694
+ upsertShellIntegration(current, renderShellIntegration(shell)),
18695
+ "utf8"
18696
+ );
18697
+ return;
18698
+ }
18699
+ await writeFile5(
18700
+ rcPath,
18701
+ upsertOnboardingShellIntegration(current, shell),
18702
+ "utf8"
18703
+ );
18704
+ }
18705
+ function upsertOnboardingShellIntegration(existingConfig, shell) {
18706
+ const script = renderOnboardingShellIntegration(shell).trimEnd();
18707
+ const blockPattern = new RegExp(
18708
+ `${escapeForRegExp(ONBOARDING_SHELL_INTEGRATION_START_MARKER)}[\\s\\S]*?${escapeForRegExp(ONBOARDING_SHELL_INTEGRATION_END_MARKER)}\\n?`,
18709
+ "m"
18710
+ );
18711
+ if (blockPattern.test(existingConfig)) {
18712
+ return ensureTrailingNewline(
18713
+ existingConfig.replace(blockPattern, `${script}
18714
+ `)
18715
+ );
18716
+ }
18717
+ const prefix = existingConfig.trimEnd();
18718
+ if (prefix.length === 0) return ensureTrailingNewline(script);
18719
+ return ensureTrailingNewline(`${prefix}
18720
+
18721
+ ${script}`);
18722
+ }
18723
+ function renderOnboardingShellIntegration(shell) {
18724
+ return `${ONBOARDING_SHELL_INTEGRATION_START_MARKER}
18725
+ eval "$(gji init ${shell})"
18726
+ ${ONBOARDING_SHELL_INTEGRATION_END_MARKER}
18727
+ `;
18728
+ }
18729
+ async function ensureZshCompletionPath(rcPath) {
18730
+ const current = await readExistingConfig(rcPath);
18731
+ const next = upsertZshCompletionPath(current);
18732
+ if (next !== current) {
18733
+ await writeFile5(rcPath, next, "utf8");
18734
+ }
18735
+ }
18736
+ function upsertZshCompletionPath(existingConfig) {
18737
+ const configWithoutCompletionPath = existingConfig.replace(
18738
+ /# >>> gji zsh completion path >>>[\s\S]*?# <<< gji zsh completion path <<<\n?/m,
18739
+ ""
18740
+ ).replace(
18741
+ new RegExp(
18742
+ `^[\\t ]*${escapeForRegExp(ZSH_COMPLETION_PATH_LINE)}[\\t ]*\\n?`,
18743
+ "gm"
18744
+ ),
18745
+ ""
18746
+ );
18747
+ const suffix = configWithoutCompletionPath.replace(/^(?:\r?\n)+/, "").trimEnd();
18748
+ return ensureTrailingNewline(
18749
+ suffix.length === 0 ? ZSH_COMPLETION_PATH_LINE : `${ZSH_COMPLETION_PATH_LINE}
18750
+
18751
+ ${suffix}`
18752
+ );
18753
+ }
18754
+ async function fileExists2(path9) {
18755
+ try {
18756
+ await access7(path9, constants5.F_OK);
18757
+ return true;
18758
+ } catch {
18759
+ return false;
18760
+ }
18761
+ }
18762
+ async function runLegacyInitCommand(options) {
17102
18763
  const shell = resolveSupportedShell(options.shell, process.env.SHELL);
17103
- const home = options.home ?? homedir5();
18764
+ const home = options.home ?? homedir6();
17104
18765
  if (!shell) {
17105
18766
  options.stderr?.(
17106
18767
  "Unable to detect a supported shell. Specify one explicitly: bash, fish, or zsh.\n"
@@ -17113,7 +18774,7 @@ async function runInitCommand(options) {
17113
18774
  return 0;
17114
18775
  }
17115
18776
  const rcPath = resolveShellConfigPath(shell, home);
17116
- await mkdir6(dirname7(rcPath), { recursive: true });
18777
+ await mkdir6(dirname8(rcPath), { recursive: true });
17117
18778
  const current = await readExistingConfig(rcPath);
17118
18779
  const next = upsertShellIntegration(current, script);
17119
18780
  await writeFile5(rcPath, next, "utf8");
@@ -17122,7 +18783,7 @@ async function runInitCommand(options) {
17122
18783
  const { config: globalConfig } = await loadGlobalConfig(home);
17123
18784
  const alreadyConfigured = "shellIntegration" in globalConfig || "installSaveTarget" in globalConfig;
17124
18785
  const hasCustomPrompt = options.promptForSetup !== void 0;
17125
- const canPrompt = hasCustomPrompt || process.stdout.isTTY === true;
18786
+ const canPrompt = !isHeadless() && (hasCustomPrompt || process.stdout.isTTY === true);
17126
18787
  if (!alreadyConfigured && canPrompt) {
17127
18788
  const prompt = options.promptForSetup ?? defaultPromptForSetup;
17128
18789
  const result = await prompt();
@@ -17144,30 +18805,30 @@ function renderShellIntegration(shell) {
17144
18805
  ).join("\n\n");
17145
18806
  switch (shell) {
17146
18807
  case "fish":
17147
- return `${START_MARKER}
18808
+ return `${SHELL_INTEGRATION_START_MARKER}
17148
18809
  function gji --wraps gji --description 'gji shell integration'
17149
18810
  ${indentBlock(commandBlocks, 4)}
17150
18811
 
17151
18812
  command gji $argv
17152
18813
  end
17153
- ${END_MARKER}
18814
+ ${SHELL_INTEGRATION_END_MARKER}
17154
18815
  `;
17155
18816
  case "bash":
17156
18817
  case "zsh":
17157
- return `${START_MARKER}
18818
+ return `${SHELL_INTEGRATION_START_MARKER}
17158
18819
  gji() {
17159
18820
  ${indentBlock(commandBlocks, 2)}
17160
18821
 
17161
18822
  command gji "$@"
17162
18823
  }
17163
- ${END_MARKER}
18824
+ ${SHELL_INTEGRATION_END_MARKER}
17164
18825
  `;
17165
18826
  }
17166
18827
  }
17167
18828
  function upsertShellIntegration(existingConfig, script) {
17168
18829
  const trimmedScript = script.trimEnd();
17169
18830
  const blockPattern = new RegExp(
17170
- `${escapeForRegExp(START_MARKER)}[\\s\\S]*?${escapeForRegExp(END_MARKER)}\\n?`,
18831
+ `${escapeForRegExp(SHELL_INTEGRATION_START_MARKER)}[\\s\\S]*?${escapeForRegExp(SHELL_INTEGRATION_END_MARKER)}\\n?`,
17171
18832
  "m"
17172
18833
  );
17173
18834
  if (blockPattern.test(existingConfig)) {
@@ -17205,21 +18866,11 @@ async function saveWizardConfig(result, cwd, home) {
17205
18866
  await saveGlobalConfig({ ...existing, ...values }, home);
17206
18867
  }
17207
18868
  }
17208
- function resolveShellConfigPath(shell, home) {
17209
- switch (shell) {
17210
- case "bash":
17211
- return join7(home, ".bashrc");
17212
- case "fish":
17213
- return join7(home, ".config", "fish", "config.fish");
17214
- case "zsh":
17215
- return join7(home, ".zshrc");
17216
- }
17217
- }
17218
18869
  async function readExistingConfig(path9) {
17219
18870
  try {
17220
- return await readFile4(path9, "utf8");
18871
+ return await readFile5(path9, "utf8");
17221
18872
  } catch (error) {
17222
- if (isMissingFileError2(error)) {
18873
+ if (isMissingFileError3(error)) {
17223
18874
  return "";
17224
18875
  }
17225
18876
  throw error;
@@ -17232,7 +18883,7 @@ function ensureTrailingNewline(value) {
17232
18883
  function escapeForRegExp(value) {
17233
18884
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
17234
18885
  }
17235
- function isMissingFileError2(error) {
18886
+ function isMissingFileError3(error) {
17236
18887
  return error instanceof Error && "code" in error && error.code === "ENOENT";
17237
18888
  }
17238
18889
  function renderFishWrapper(command) {
@@ -17457,14 +19108,14 @@ function sortWorktrees(worktrees) {
17457
19108
  }
17458
19109
 
17459
19110
  // src/open.ts
17460
- import { execFile as execFile4 } from "node:child_process";
17461
- import { access as access4, writeFile as writeFile6 } from "node:fs/promises";
17462
- import { join as join8 } from "node:path";
17463
- import { promisify as promisify5 } from "node:util";
17464
- var execFileAsync4 = promisify5(execFile4);
19111
+ import { execFile as execFile6 } from "node:child_process";
19112
+ import { access as access8, writeFile as writeFile6 } from "node:fs/promises";
19113
+ import { join as join9 } from "node:path";
19114
+ import { promisify as promisify7 } from "node:util";
19115
+ var execFileAsync6 = promisify7(execFile6);
17465
19116
  function createOpenCommand(dependencies = {}) {
17466
19117
  const detectEditors = dependencies.detectEditors ?? detectInstalledEditors;
17467
- const promptForEditor = dependencies.promptForEditor ?? defaultPromptForEditor;
19118
+ const promptForEditor2 = dependencies.promptForEditor ?? defaultPromptForEditor;
17468
19119
  const promptForWorktree2 = dependencies.promptForWorktree ?? defaultPromptForWorktree;
17469
19120
  const spawnEditor = dependencies.spawnEditor ?? defaultSpawnEditor;
17470
19121
  return async function runOpenCommand2(options) {
@@ -17499,9 +19150,11 @@ function createOpenCommand(dependencies = {}) {
17499
19150
  } else {
17500
19151
  const entries = await buildWorktreePromptEntries(
17501
19152
  worktrees.map((worktree) => ({
19153
+ repoRoot: repository.repoRoot,
17502
19154
  repoName: repository.repoName,
17503
19155
  worktree
17504
- }))
19156
+ })),
19157
+ { queryPullRequests: dependencies.queryPullRequests }
17505
19158
  );
17506
19159
  const chosen = await promptForWorktree2(entries);
17507
19160
  if (!chosen) {
@@ -17533,7 +19186,7 @@ function createOpenCommand(dependencies = {}) {
17533
19186
  if (installed.length === 1 || isHeadless()) {
17534
19187
  editorCli = installed[0].cli;
17535
19188
  } else {
17536
- const chosen = await promptForEditor(installed);
19189
+ const chosen = await promptForEditor2(installed);
17537
19190
  if (!chosen) {
17538
19191
  options.stderr("Aborted\n");
17539
19192
  return 1;
@@ -17592,7 +19245,7 @@ async function detectInstalledEditors() {
17592
19245
  }
17593
19246
  async function isCommandAvailable(command) {
17594
19247
  try {
17595
- await execFileAsync4("which", [command]);
19248
+ await execFileAsync6("which", [command]);
17596
19249
  return true;
17597
19250
  } catch {
17598
19251
  return false;
@@ -17610,9 +19263,9 @@ async function defaultPromptForEditor(editors) {
17610
19263
  return choice;
17611
19264
  }
17612
19265
  async function ensureWorkspaceFile(worktreePath, repoName) {
17613
- const workspacePath = join8(worktreePath, `${repoName}.code-workspace`);
19266
+ const workspacePath = join9(worktreePath, `${repoName}.code-workspace`);
17614
19267
  try {
17615
- await access4(workspacePath);
19268
+ await access8(workspacePath);
17616
19269
  return workspacePath;
17617
19270
  } catch {
17618
19271
  }
@@ -17627,11 +19280,11 @@ async function ensureWorkspaceFile(worktreePath, repoName) {
17627
19280
  }
17628
19281
 
17629
19282
  // src/pr.ts
17630
- import { execFile as execFile5 } from "node:child_process";
19283
+ import { execFile as execFile7 } from "node:child_process";
17631
19284
  import { mkdir as mkdir7 } from "node:fs/promises";
17632
- import { basename as basename7, dirname as dirname8 } from "node:path";
17633
- import { promisify as promisify6 } from "node:util";
17634
- var execFileAsync5 = promisify6(execFile5);
19285
+ import { basename as basename8, dirname as dirname9 } from "node:path";
19286
+ import { promisify as promisify8 } from "node:util";
19287
+ var execFileAsync7 = promisify8(execFile7);
17635
19288
  var PR_OUTPUT_FILE_ENV = "GJI_PR_OUTPUT_FILE";
17636
19289
  function parsePrInput(input) {
17637
19290
  if (/^\d+$/.test(input)) return input;
@@ -17673,7 +19326,7 @@ function createPrCommand(dependencies = {}) {
17673
19326
  branchName,
17674
19327
  configuredBasePath
17675
19328
  );
17676
- if (await pathExists(worktreePath)) {
19329
+ if (await pathExists2(worktreePath)) {
17677
19330
  if (options.json || isHeadless()) {
17678
19331
  const message = `target worktree path already exists: ${worktreePath}`;
17679
19332
  if (options.json) {
@@ -17739,13 +19392,13 @@ function createPrCommand(dependencies = {}) {
17739
19392
  }
17740
19393
  return 1;
17741
19394
  }
17742
- await mkdir7(dirname8(worktreePath), { recursive: true });
19395
+ await mkdir7(dirname9(worktreePath), { recursive: true });
17743
19396
  const branchAlreadyExists = await localBranchExists2(
17744
19397
  repository.repoRoot,
17745
19398
  branchName
17746
19399
  );
17747
19400
  const worktreeArgs = branchAlreadyExists ? ["worktree", "add", worktreePath, branchName] : ["worktree", "add", "-b", branchName, worktreePath, remoteRef];
17748
- await execFileAsync5("git", worktreeArgs, { cwd: repository.repoRoot });
19401
+ await execFileAsync7("git", worktreeArgs, { cwd: repository.repoRoot });
17749
19402
  const syncPatterns = Array.isArray(config.syncFiles) ? config.syncFiles.filter(
17750
19403
  (p2) => typeof p2 === "string"
17751
19404
  ) : [];
@@ -17774,7 +19427,7 @@ function createPrCommand(dependencies = {}) {
17774
19427
  {
17775
19428
  branch: branchName,
17776
19429
  path: worktreePath,
17777
- repo: basename7(repository.repoRoot)
19430
+ repo: basename8(repository.repoRoot)
17778
19431
  },
17779
19432
  options.stderr
17780
19433
  );
@@ -17792,7 +19445,7 @@ function createPrCommand(dependencies = {}) {
17792
19445
  }
17793
19446
  async function localBranchExists2(repoRoot, branchName) {
17794
19447
  try {
17795
- await execFileAsync5(
19448
+ await execFileAsync7(
17796
19449
  "git",
17797
19450
  ["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`],
17798
19451
  { cwd: repoRoot }
@@ -17806,7 +19459,7 @@ var runPrCommand = createPrCommand();
17806
19459
  async function fetchPullRequestRef(repoRoot, input, prNumber, remoteRef) {
17807
19460
  for (const sourceRef of listPullRequestSourceRefs(input, prNumber)) {
17808
19461
  try {
17809
- await execFileAsync5(
19462
+ await execFileAsync7(
17810
19463
  "git",
17811
19464
  ["fetch", "origin", `${sourceRef}:${remoteRef}`],
17812
19465
  { cwd: repoRoot }
@@ -17856,11 +19509,415 @@ async function writeOutput2(worktreePath, stdout2) {
17856
19509
  await writeShellOutput(PR_OUTPUT_FILE_ENV, worktreePath, stdout2);
17857
19510
  }
17858
19511
 
19512
+ // src/browser.ts
19513
+ import { execFile as execFile8 } from "node:child_process";
19514
+ import { platform as platform2 } from "node:process";
19515
+ import { promisify as promisify9 } from "node:util";
19516
+ var execFileAsync8 = promisify9(execFile8);
19517
+ function createBrowserOpener(dependencies = {}) {
19518
+ const currentPlatform = dependencies.platform ?? platform2;
19519
+ const runCommand = dependencies.runCommand ?? defaultRunCommand2;
19520
+ return async function openBrowser2(url) {
19521
+ validateBrowserUrl(url);
19522
+ const command = browserCommand(currentPlatform, url);
19523
+ await runCommand(command.command, command.args);
19524
+ };
19525
+ }
19526
+ var openBrowser = createBrowserOpener();
19527
+ function browserCommand(currentPlatform, url) {
19528
+ if (currentPlatform === "darwin") return { args: [url], command: "open" };
19529
+ if (currentPlatform === "win32") {
19530
+ return { args: [url], command: "explorer.exe" };
19531
+ }
19532
+ return { args: [url], command: "xdg-open" };
19533
+ }
19534
+ function validateBrowserUrl(url) {
19535
+ let parsed;
19536
+ try {
19537
+ parsed = new URL(url);
19538
+ } catch {
19539
+ throw new Error("browser URL is invalid");
19540
+ }
19541
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
19542
+ throw new Error("browser URL must use http or https");
19543
+ }
19544
+ }
19545
+ async function defaultRunCommand2(command, args) {
19546
+ await execFileAsync8(command, args, { timeout: 1e4 });
19547
+ }
19548
+
19549
+ // src/pr-open.ts
19550
+ function createPrOpenCommand(dependencies = {}) {
19551
+ const query = createPullRequestQuery();
19552
+ const findOpenPullRequest2 = dependencies.findOpenPullRequest ?? query.findOpenPullRequest;
19553
+ const openInBrowser = dependencies.openBrowser ?? openBrowser;
19554
+ const promptForPullRequest = dependencies.promptForPullRequest ?? defaultPromptForPullRequest;
19555
+ const promptForWorktree2 = dependencies.promptForWorktree ?? defaultPromptForWorktree2;
19556
+ const queryPullRequests = dependencies.queryPullRequests ?? query.listOpenPullRequests;
19557
+ const queryRepositoryPullRequests = dependencies.queryRepositoryPullRequests ?? (dependencies.queryPullRequests === void 0 ? query.listOpenPullRequestsForRepository : void 0);
19558
+ return async function runPrOpenCommand2(options) {
19559
+ if (options.select && options.target !== void 0) {
19560
+ options.stderr("gji pr open: --select cannot be used with a target\n");
19561
+ return 1;
19562
+ }
19563
+ if (options.select && isHeadless()) {
19564
+ options.stderr(
19565
+ "gji pr open --select: selector is unavailable in non-interactive mode (GJI_NO_TUI=1)\n"
19566
+ );
19567
+ return 1;
19568
+ }
19569
+ const repository = await detectRepository(options.cwd).catch((error) => {
19570
+ options.stderr(
19571
+ `gji pr open: unable to detect the current repository: ${formatError(error)}
19572
+ `
19573
+ );
19574
+ return null;
19575
+ });
19576
+ if (repository === null) return 1;
19577
+ if (options.target === void 0) {
19578
+ if (!options.select) {
19579
+ return openCurrentWorktree(
19580
+ repository.repoRoot,
19581
+ options,
19582
+ queryPullRequests,
19583
+ promptForPullRequest,
19584
+ openInBrowser
19585
+ );
19586
+ }
19587
+ return openFromWorktreeSelector(
19588
+ repository.repoRoot,
19589
+ repository.repoName,
19590
+ options,
19591
+ promptForWorktree2,
19592
+ promptForPullRequest,
19593
+ queryPullRequests,
19594
+ queryRepositoryPullRequests,
19595
+ openInBrowser
19596
+ );
19597
+ }
19598
+ const number = parsePrNumberTarget(options.target);
19599
+ if (number !== null) {
19600
+ return openByNumber(
19601
+ repository.repoRoot,
19602
+ number,
19603
+ options,
19604
+ findOpenPullRequest2,
19605
+ openInBrowser
19606
+ );
19607
+ }
19608
+ const worktrees = await listWorktrees(options.cwd).catch((error) => {
19609
+ options.stderr(
19610
+ `gji pr open: unable to list worktrees: ${formatError(error)}
19611
+ `
19612
+ );
19613
+ return null;
19614
+ });
19615
+ if (worktrees === null) return 1;
19616
+ const match = resolveWorktreeQuery(
19617
+ worktrees.map((worktree) => ({
19618
+ repoRoot: repository.repoRoot,
19619
+ repoName: repository.repoName,
19620
+ worktree
19621
+ })),
19622
+ options.target
19623
+ );
19624
+ if (match === null) {
19625
+ options.stderr(
19626
+ `gji pr open: no worktree found matching: ${options.target}
19627
+ `
19628
+ );
19629
+ return 1;
19630
+ }
19631
+ return openForBranch(
19632
+ repository.repoRoot,
19633
+ match.worktree.branch,
19634
+ options,
19635
+ queryPullRequests,
19636
+ promptForPullRequest,
19637
+ openInBrowser
19638
+ );
19639
+ };
19640
+ }
19641
+ var runPrOpenCommand = createPrOpenCommand();
19642
+ async function openCurrentWorktree(repoRoot, options, queryPullRequests, promptForPullRequest, openInBrowser) {
19643
+ const worktrees = await listWorktrees(options.cwd).catch((error) => {
19644
+ options.stderr(
19645
+ `gji pr open: unable to list worktrees: ${formatError(error)}
19646
+ `
19647
+ );
19648
+ return null;
19649
+ });
19650
+ if (worktrees === null) return 1;
19651
+ const currentWorktree = worktrees.find((worktree) => worktree.isCurrent);
19652
+ if (currentWorktree === void 0) {
19653
+ options.stderr("gji pr open: unable to identify the current worktree\n");
19654
+ return 1;
19655
+ }
19656
+ if (currentWorktree.branch === null) {
19657
+ options.stderr(
19658
+ "gji pr open: current worktree is detached and has no PR branch\n"
19659
+ );
19660
+ return 1;
19661
+ }
19662
+ let pullRequests;
19663
+ try {
19664
+ pullRequests = await queryPullRequests(repoRoot, currentWorktree.branch);
19665
+ } catch (error) {
19666
+ options.stderr(
19667
+ `gji pr open: failed to look up PRs for current branch ${currentWorktree.branch}: ${formatError(error)}
19668
+ `
19669
+ );
19670
+ return 1;
19671
+ }
19672
+ if (pullRequests.length === 0) {
19673
+ options.stderr(
19674
+ isHeadless() ? "gji pr open: no open PR found for the current worktree; pass an explicit branch or PR number (for example, gji pr open '#123')\n" : "gji pr open: no open PR found for the current worktree; use gji pr open --select to choose another\n"
19675
+ );
19676
+ return 1;
19677
+ }
19678
+ return openPullRequests(
19679
+ pullRequests,
19680
+ options,
19681
+ promptForPullRequest,
19682
+ openInBrowser
19683
+ );
19684
+ }
19685
+ async function openFromWorktreeSelector(repoRoot, repoName, options, promptForWorktree2, promptForPullRequest, queryPullRequests, queryRepositoryPullRequests, openInBrowser) {
19686
+ const worktrees = await listWorktrees(options.cwd).catch((error) => {
19687
+ options.stderr(
19688
+ `gji pr open: unable to list worktrees: ${formatError(error)}
19689
+ `
19690
+ );
19691
+ return null;
19692
+ });
19693
+ if (worktrees === null) return 1;
19694
+ let lookupError = null;
19695
+ const queryForSelector = async (root, branch) => {
19696
+ try {
19697
+ return await queryPullRequests(root, branch);
19698
+ } catch (error) {
19699
+ lookupError ??= error;
19700
+ throw error;
19701
+ }
19702
+ };
19703
+ const queryRepositoryForSelector = queryRepositoryPullRequests === void 0 ? void 0 : async (root) => {
19704
+ try {
19705
+ return await queryRepositoryPullRequests(root);
19706
+ } catch (error) {
19707
+ lookupError ??= error;
19708
+ throw error;
19709
+ }
19710
+ };
19711
+ let connectedWorktrees;
19712
+ try {
19713
+ connectedWorktrees = await readConnectedWorktreeSources(
19714
+ repoRoot,
19715
+ repoName,
19716
+ worktrees,
19717
+ queryForSelector,
19718
+ queryRepositoryForSelector
19719
+ );
19720
+ } catch {
19721
+ connectedWorktrees = { pullRequestsByBranch: /* @__PURE__ */ new Map(), sources: [] };
19722
+ }
19723
+ const { pullRequestsByBranch, sources } = connectedWorktrees;
19724
+ const entries = await buildWorktreePromptEntries(sources, {
19725
+ queryPullRequests: async (_root, branch) => pullRequestsByBranch.get(branch) ?? []
19726
+ });
19727
+ const connectedEntries = entries.filter(
19728
+ (entry) => (entry.pullRequestNumbers?.length ?? 0) > 0
19729
+ );
19730
+ if (connectedEntries.length === 0) {
19731
+ if (lookupError !== null) {
19732
+ options.stderr(
19733
+ `gji pr open: failed to look up open PRs: ${formatError(lookupError)}
19734
+ `
19735
+ );
19736
+ return 1;
19737
+ }
19738
+ options.stderr("gji pr open: no open PR worktrees found\n");
19739
+ return 1;
19740
+ }
19741
+ const selectedPath = await promptForWorktree2(connectedEntries);
19742
+ if (selectedPath === null) {
19743
+ options.stderr("Aborted\n");
19744
+ return 1;
19745
+ }
19746
+ const selectedEntry = connectedEntries.find(
19747
+ (entry) => entry.path === selectedPath
19748
+ );
19749
+ if (selectedEntry === void 0) {
19750
+ options.stderr("gji pr open: selected worktree is no longer available\n");
19751
+ return 1;
19752
+ }
19753
+ return openSelectedEntry(
19754
+ selectedEntry,
19755
+ options,
19756
+ promptForPullRequest,
19757
+ openInBrowser
19758
+ );
19759
+ }
19760
+ async function readConnectedWorktreeSources(repoRoot, repoName, worktrees, queryPullRequests, queryRepositoryPullRequests) {
19761
+ const pullRequestsByBranch = /* @__PURE__ */ new Map();
19762
+ if (queryRepositoryPullRequests !== void 0) {
19763
+ const pullRequests = await queryRepositoryPullRequests(repoRoot);
19764
+ for (const worktree of worktrees) {
19765
+ if (worktree.branch === null) continue;
19766
+ const branchPullRequests = pullRequests.filter(
19767
+ (pullRequest) => pullRequest.sourceBranch === worktree.branch
19768
+ );
19769
+ if (branchPullRequests.length > 0) {
19770
+ pullRequestsByBranch.set(worktree.branch, branchPullRequests);
19771
+ }
19772
+ }
19773
+ } else {
19774
+ const branchResults = await Promise.all(
19775
+ worktrees.map(async (worktree) => {
19776
+ if (worktree.branch === null) {
19777
+ return { pullRequests: [], worktree };
19778
+ }
19779
+ try {
19780
+ return {
19781
+ pullRequests: await queryPullRequests(repoRoot, worktree.branch),
19782
+ worktree
19783
+ };
19784
+ } catch {
19785
+ return { pullRequests: [], worktree };
19786
+ }
19787
+ })
19788
+ );
19789
+ for (const { pullRequests, worktree } of branchResults) {
19790
+ if (worktree.branch !== null && pullRequests.length > 0) {
19791
+ pullRequestsByBranch.set(worktree.branch, pullRequests);
19792
+ }
19793
+ }
19794
+ }
19795
+ const sources = worktrees.flatMap((worktree) => {
19796
+ if (worktree.branch === null || !pullRequestsByBranch.has(worktree.branch)) {
19797
+ return [];
19798
+ }
19799
+ return [{ repoRoot, repoName, worktree }];
19800
+ });
19801
+ return { pullRequestsByBranch, sources };
19802
+ }
19803
+ async function openByNumber(repoRoot, number, options, findOpenPullRequest2, openInBrowser) {
19804
+ let pullRequest;
19805
+ try {
19806
+ pullRequest = await findOpenPullRequest2(repoRoot, number);
19807
+ } catch (error) {
19808
+ options.stderr(
19809
+ `gji pr open: failed to look up PR #${number}: ${formatError(error)}
19810
+ `
19811
+ );
19812
+ return 1;
19813
+ }
19814
+ if (pullRequest === null) {
19815
+ options.stderr(`gji pr open: no open PR found for #${number}
19816
+ `);
19817
+ return 1;
19818
+ }
19819
+ return launchBrowser(pullRequest, options, openInBrowser);
19820
+ }
19821
+ async function openForBranch(repoRoot, branch, options, queryPullRequests, promptForPullRequest, openInBrowser) {
19822
+ if (branch === null) {
19823
+ options.stderr("gji pr open: detached worktrees have no PR branch\n");
19824
+ return 1;
19825
+ }
19826
+ let pullRequests;
19827
+ try {
19828
+ pullRequests = await queryPullRequests(repoRoot, branch);
19829
+ } catch (error) {
19830
+ options.stderr(
19831
+ `gji pr open: failed to look up PRs for branch ${branch}: ${formatError(error)}
19832
+ `
19833
+ );
19834
+ return 1;
19835
+ }
19836
+ return openPullRequests(
19837
+ pullRequests,
19838
+ options,
19839
+ promptForPullRequest,
19840
+ openInBrowser
19841
+ );
19842
+ }
19843
+ async function openSelectedEntry(entry, options, promptForPullRequest, openInBrowser) {
19844
+ const numbers = entry.pullRequestNumbers ?? [];
19845
+ const urls = entry.pullRequestUrls ?? [];
19846
+ const pullRequests = numbers.map((number, index) => ({
19847
+ number,
19848
+ sourceBranch: entry.branch ?? "",
19849
+ url: urls[index] ?? ""
19850
+ })).filter((pullRequest) => pullRequest.url.length > 0);
19851
+ return openPullRequests(
19852
+ pullRequests,
19853
+ options,
19854
+ promptForPullRequest,
19855
+ openInBrowser
19856
+ );
19857
+ }
19858
+ async function openPullRequests(pullRequests, options, promptForPullRequest, openInBrowser) {
19859
+ if (pullRequests.length === 0) {
19860
+ options.stderr("gji pr open: no open PR found for the selected branch\n");
19861
+ return 1;
19862
+ }
19863
+ if (pullRequests.length > 1 && isHeadless()) {
19864
+ options.stderr(
19865
+ "gji pr open: multiple open PRs found; pass an explicit PR number in non-interactive mode (GJI_NO_TUI=1)\n"
19866
+ );
19867
+ return 1;
19868
+ }
19869
+ const selected = pullRequests.length === 1 ? pullRequests[0] : await promptForPullRequest(pullRequests);
19870
+ if (selected === null || selected === void 0) {
19871
+ options.stderr("Aborted\n");
19872
+ return 1;
19873
+ }
19874
+ return launchBrowser(selected, options, openInBrowser);
19875
+ }
19876
+ async function launchBrowser(pullRequest, options, openInBrowser) {
19877
+ try {
19878
+ await openInBrowser(pullRequest.url);
19879
+ } catch (error) {
19880
+ options.stderr(
19881
+ `gji pr open: failed to open PR #${pullRequest.number} in the browser: ${formatError(error)}
19882
+ `
19883
+ );
19884
+ return 1;
19885
+ }
19886
+ options.stdout(`Opened PR #${pullRequest.number}: ${pullRequest.url}
19887
+ `);
19888
+ return 0;
19889
+ }
19890
+ function parsePrNumberTarget(target) {
19891
+ const match = target.match(/^#?(\d+)$/);
19892
+ return match === null ? null : Number(match[1]);
19893
+ }
19894
+ async function defaultPromptForWorktree2(worktrees) {
19895
+ return promptForSingleWorktree(
19896
+ "Choose a worktree with an open PR",
19897
+ worktrees
19898
+ );
19899
+ }
19900
+ async function defaultPromptForPullRequest(pullRequests) {
19901
+ const choice = await ve({
19902
+ message: "Choose a pull request",
19903
+ options: pullRequests.map((pullRequest) => ({
19904
+ value: pullRequest.number,
19905
+ label: `#${pullRequest.number}`,
19906
+ hint: pullRequest.url
19907
+ }))
19908
+ });
19909
+ if (pD(choice)) return null;
19910
+ return pullRequests.find((pullRequest) => pullRequest.number === choice) ?? null;
19911
+ }
19912
+ function formatError(error) {
19913
+ return error instanceof Error ? error.message : String(error);
19914
+ }
19915
+
17859
19916
  // src/remove.ts
17860
- import { basename as basename8 } from "node:path";
19917
+ import { basename as basename9 } from "node:path";
17861
19918
  var REMOVE_OUTPUT_FILE_ENV = "GJI_REMOVE_OUTPUT_FILE";
17862
19919
  function createRemoveCommand(dependencies = {}) {
17863
- const promptForWorktree2 = dependencies.promptForWorktree ?? defaultPromptForWorktree2;
19920
+ const promptForWorktree2 = dependencies.promptForWorktree ?? defaultPromptForWorktree3;
17864
19921
  const confirmRemoval = dependencies.confirmRemoval ?? defaultConfirmRemoval2;
17865
19922
  const confirmForceRemoveWorktree = dependencies.confirmForceRemoveWorktree ?? defaultConfirmForceRemoveWorktree;
17866
19923
  const confirmForceDeleteBranch = dependencies.confirmForceDeleteBranch ?? defaultConfirmForceDeleteBranch;
@@ -17944,7 +20001,7 @@ function createRemoveCommand(dependencies = {}) {
17944
20001
  {
17945
20002
  branch: worktree.branch ?? void 0,
17946
20003
  path: worktree.path,
17947
- repo: basename8(repository.repoRoot)
20004
+ repo: basename9(repository.repoRoot)
17948
20005
  },
17949
20006
  options.stderr
17950
20007
  );
@@ -18004,7 +20061,7 @@ function createRemoveCommand(dependencies = {}) {
18004
20061
  };
18005
20062
  }
18006
20063
  var runRemoveCommand = createRemoveCommand();
18007
- async function defaultPromptForWorktree2(worktrees) {
20064
+ async function defaultPromptForWorktree3(worktrees) {
18008
20065
  return promptForSingleWorktree("Choose a worktree to finish", worktrees);
18009
20066
  }
18010
20067
  async function defaultConfirmRemoval2(worktree) {
@@ -18329,11 +20386,11 @@ function resolveConfiguredString2(value) {
18329
20386
  }
18330
20387
 
18331
20388
  // src/sync-files-command.ts
18332
- import { homedir as homedir6 } from "node:os";
18333
- import { join as join9 } from "node:path";
20389
+ import { homedir as homedir7 } from "node:os";
20390
+ import { join as join10 } from "node:path";
18334
20391
  async function runSyncFilesCommand(options) {
18335
20392
  const repository = await detectRepository(options.cwd);
18336
- const home = options.home ?? homedir6();
20393
+ const home = options.home ?? homedir7();
18337
20394
  const loaded = await loadGlobalConfig(home);
18338
20395
  const repoEntry = findRepoConfigEntry(
18339
20396
  loaded.config,
@@ -18379,7 +20436,7 @@ function findRepoConfigEntry(config, repoRoot, home) {
18379
20436
  const repos = config.repos;
18380
20437
  if (!isPlainObject3(repos)) return null;
18381
20438
  for (const [key, value] of Object.entries(repos)) {
18382
- if (expandTilde2(key, home) === repoRoot && isPlainObject3(value)) {
20439
+ if (expandTilde3(key, home) === repoRoot && isPlainObject3(value)) {
18383
20440
  return { config: value, key };
18384
20441
  }
18385
20442
  }
@@ -18463,9 +20520,9 @@ function writeError(options, message) {
18463
20520
  options.stderr(`gji sync-files: ${message}
18464
20521
  `);
18465
20522
  }
18466
- function expandTilde2(value, home) {
20523
+ function expandTilde3(value, home) {
18467
20524
  if (value === "~") return home;
18468
- if (value.startsWith("~/")) return join9(home, value.slice(2));
20525
+ if (value.startsWith("~/")) return join10(home, value.slice(2));
18469
20526
  return value;
18470
20527
  }
18471
20528
 
@@ -18487,7 +20544,9 @@ function readPackageMetadata() {
18487
20544
  }
18488
20545
  async function runCli(argv, options = {}) {
18489
20546
  await maybeNotifyForUpdates(argv);
18490
- maybeRegisterCurrentRepo(options.cwd ?? process.cwd());
20547
+ if (argv[0] !== "doctor") {
20548
+ maybeRegisterCurrentRepo(options.cwd ?? process.cwd());
20549
+ }
18491
20550
  const program2 = createProgram();
18492
20551
  const cwd = options.cwd ?? process.cwd();
18493
20552
  const stdout2 = options.stdout ?? (() => void 0);
@@ -18502,7 +20561,12 @@ async function runCli(argv, options = {}) {
18502
20561
  return { exitCode: 0 };
18503
20562
  }
18504
20563
  try {
18505
- attachCommandActions(program2, { cwd, stderr, stdout: stdout2 });
20564
+ attachCommandActions(program2, {
20565
+ cwd,
20566
+ prOpenDependencies: options.prOpenDependencies,
20567
+ stderr,
20568
+ stdout: stdout2
20569
+ });
18506
20570
  await program2.parseAsync(["node", "gji", ...argv], { from: "node" });
18507
20571
  return { exitCode: 0 };
18508
20572
  } catch (error) {
@@ -18548,9 +20612,12 @@ function registerCommands(program2) {
18548
20612
  "--json",
18549
20613
  "emit JSON on success or error instead of human-readable output"
18550
20614
  ).action(notImplemented("new"));
18551
- program2.command("init [shell]").description("print or install shell integration").option("--write", "write the integration to the shell config file").action(notImplemented("init"));
20615
+ program2.command("init [shell]").description(
20616
+ "set up shell integration interactively or print a shell wrapper"
20617
+ ).option("--write", "write the integration to the shell config file").option("--json", "emit a JSON error in non-interactive mode").action(notImplemented("init"));
20618
+ program2.command("doctor").description("check gji installation and configuration health").option("--fix", "apply safe automatic fixes after showing the plan").option("--yes", "apply --fix without prompting").option("--json", "emit diagnostic checks as JSON").action(notImplemented("doctor"));
18552
20619
  program2.command("completion [shell]").description("print shell completion definitions").action(notImplemented("completion"));
18553
- program2.command("pr <ref>").description(
20620
+ const prCommand = program2.command("pr <ref>").usage("[options] <ref>").description(
18554
20621
  "fetch a pull request by number, #number, or URL into a linked worktree"
18555
20622
  ).option(
18556
20623
  "--dry-run",
@@ -18559,6 +20626,7 @@ function registerCommands(program2) {
18559
20626
  "--json",
18560
20627
  "emit JSON on success or error instead of human-readable output"
18561
20628
  ).action(notImplemented("pr"));
20629
+ prCommand.command("open [target]").description("open an existing pull request in the default browser").option("--select", "choose a pull request from any linked worktree").action(notImplemented("pr open"));
18562
20630
  program2.command("back [n]").description(
18563
20631
  "navigate to the previously visited worktree, optionally N steps back"
18564
20632
  ).option("--print", "print the resolved worktree path explicitly").action(notImplemented("back"));
@@ -18616,16 +20684,16 @@ function registerCommands(program2) {
18616
20684
  }
18617
20685
  function attachCommandActions(program2, options) {
18618
20686
  program2.commands.find((command) => command.name() === "new")?.action(
18619
- async (branch, commandOptions) => {
20687
+ async (branch, commandOptions2) => {
18620
20688
  const exitCode = await runNewCommand({
18621
20689
  ...options,
18622
20690
  branch,
18623
- detached: commandOptions.detached,
18624
- dryRun: commandOptions.dryRun,
18625
- editor: commandOptions.editor,
18626
- force: commandOptions.force,
18627
- json: commandOptions.json,
18628
- open: commandOptions.open
20691
+ detached: commandOptions2.detached,
20692
+ dryRun: commandOptions2.dryRun,
20693
+ editor: commandOptions2.editor,
20694
+ force: commandOptions2.force,
20695
+ json: commandOptions2.json,
20696
+ open: commandOptions2.open
18629
20697
  });
18630
20698
  if (exitCode !== 0) {
18631
20699
  throw commanderExit(exitCode);
@@ -18633,13 +20701,29 @@ function attachCommandActions(program2, options) {
18633
20701
  }
18634
20702
  );
18635
20703
  program2.commands.find((command) => command.name() === "init")?.action(
18636
- async (shell, commandOptions) => {
20704
+ async (shell, commandOptions2) => {
18637
20705
  const exitCode = await runInitCommand({
18638
20706
  cwd: options.cwd,
20707
+ json: commandOptions2.json,
18639
20708
  shell,
18640
20709
  stderr: options.stderr,
18641
20710
  stdout: options.stdout,
18642
- write: commandOptions.write
20711
+ write: commandOptions2.write
20712
+ });
20713
+ if (exitCode !== 0) {
20714
+ throw commanderExit(exitCode);
20715
+ }
20716
+ }
20717
+ );
20718
+ program2.commands.find((command) => command.name() === "doctor")?.action(
20719
+ async (commandOptions2) => {
20720
+ const exitCode = await runDoctorCommand({
20721
+ cwd: options.cwd,
20722
+ fix: commandOptions2.fix,
20723
+ json: commandOptions2.json,
20724
+ stderr: options.stderr,
20725
+ stdout: options.stdout,
20726
+ yes: commandOptions2.yes
18643
20727
  });
18644
20728
  if (exitCode !== 0) {
18645
20729
  throw commanderExit(exitCode);
@@ -18657,11 +20741,11 @@ function attachCommandActions(program2, options) {
18657
20741
  }
18658
20742
  });
18659
20743
  program2.commands.find((command) => command.name() === "pr")?.action(
18660
- async (number, commandOptions) => {
20744
+ async (number, commandOptions2) => {
18661
20745
  const exitCode = await runPrCommand({
18662
20746
  cwd: options.cwd,
18663
- dryRun: commandOptions.dryRun,
18664
- json: commandOptions.json,
20747
+ dryRun: commandOptions2.dryRun,
20748
+ json: commandOptions2.json,
18665
20749
  number,
18666
20750
  stderr: options.stderr,
18667
20751
  stdout: options.stdout
@@ -18671,8 +20755,24 @@ function attachCommandActions(program2, options) {
18671
20755
  }
18672
20756
  }
18673
20757
  );
20758
+ const prOpenCommand = program2.commands.find((command) => command.name() === "pr")?.commands.find((command) => command.name() === "open");
20759
+ const runPrOpenCommand2 = createPrOpenCommand(options.prOpenDependencies);
20760
+ prOpenCommand?.action(
20761
+ async (target, commandOptions2) => {
20762
+ const exitCode = await runPrOpenCommand2({
20763
+ cwd: options.cwd,
20764
+ stderr: options.stderr,
20765
+ stdout: options.stdout,
20766
+ select: commandOptions2.select,
20767
+ target
20768
+ });
20769
+ if (exitCode !== 0) {
20770
+ throw commanderExit(exitCode);
20771
+ }
20772
+ }
20773
+ );
18674
20774
  program2.commands.find((command) => command.name() === "back")?.action(
18675
- async (n, commandOptions) => {
20775
+ async (n, commandOptions2) => {
18676
20776
  if (n !== void 0 && !/^\d+$/.test(n)) {
18677
20777
  options.stderr(`gji back: invalid step count: ${n}
18678
20778
  `);
@@ -18682,7 +20782,7 @@ function attachCommandActions(program2, options) {
18682
20782
  const exitCode = await runBackCommand({
18683
20783
  cwd: options.cwd,
18684
20784
  n: steps,
18685
- print: commandOptions.print,
20785
+ print: commandOptions2.print,
18686
20786
  stderr: options.stderr,
18687
20787
  stdout: options.stdout
18688
20788
  });
@@ -18691,10 +20791,10 @@ function attachCommandActions(program2, options) {
18691
20791
  }
18692
20792
  }
18693
20793
  );
18694
- program2.commands.find((command) => command.name() === "history")?.action(async (commandOptions) => {
20794
+ program2.commands.find((command) => command.name() === "history")?.action(async (commandOptions2) => {
18695
20795
  const exitCode = await runHistoryCommand({
18696
20796
  cwd: options.cwd,
18697
- json: commandOptions.json,
20797
+ json: commandOptions2.json,
18698
20798
  stdout: options.stdout
18699
20799
  });
18700
20800
  if (exitCode !== 0) {
@@ -18702,15 +20802,15 @@ function attachCommandActions(program2, options) {
18702
20802
  }
18703
20803
  });
18704
20804
  program2.commands.find((command) => command.name() === "open")?.action(
18705
- async (branch, commandOptions) => {
20805
+ async (branch, commandOptions2) => {
18706
20806
  const exitCode = await runOpenCommand({
18707
20807
  branch,
18708
20808
  cwd: options.cwd,
18709
- editor: commandOptions.editor,
18710
- save: commandOptions.save,
20809
+ editor: commandOptions2.editor,
20810
+ save: commandOptions2.save,
18711
20811
  stderr: options.stderr,
18712
20812
  stdout: options.stdout,
18713
- workspace: commandOptions.workspace
20813
+ workspace: commandOptions2.workspace
18714
20814
  });
18715
20815
  if (exitCode !== 0) {
18716
20816
  throw commanderExit(exitCode);
@@ -18718,11 +20818,11 @@ function attachCommandActions(program2, options) {
18718
20818
  }
18719
20819
  );
18720
20820
  program2.commands.find((command) => command.name() === "go")?.action(
18721
- async (branch, commandOptions) => {
20821
+ async (branch, commandOptions2) => {
18722
20822
  const exitCode = await runGoCommand({
18723
20823
  branch,
18724
20824
  cwd: options.cwd,
18725
- print: commandOptions.print,
20825
+ print: commandOptions2.print,
18726
20826
  stderr: options.stderr,
18727
20827
  stdout: options.stdout
18728
20828
  });
@@ -18731,31 +20831,31 @@ function attachCommandActions(program2, options) {
18731
20831
  }
18732
20832
  }
18733
20833
  );
18734
- program2.commands.find((command) => command.name() === "root")?.action(async (commandOptions) => {
20834
+ program2.commands.find((command) => command.name() === "root")?.action(async (commandOptions2) => {
18735
20835
  const exitCode = await runRootCommand({
18736
20836
  cwd: options.cwd,
18737
- print: commandOptions.print,
20837
+ print: commandOptions2.print,
18738
20838
  stdout: options.stdout
18739
20839
  });
18740
20840
  if (exitCode !== 0) {
18741
20841
  throw commanderExit(exitCode);
18742
20842
  }
18743
20843
  });
18744
- program2.commands.find((command) => command.name() === "status")?.action(async (commandOptions) => {
20844
+ program2.commands.find((command) => command.name() === "status")?.action(async (commandOptions2) => {
18745
20845
  const exitCode = await runStatusCommand({
18746
20846
  cwd: options.cwd,
18747
- json: commandOptions.json,
20847
+ json: commandOptions2.json,
18748
20848
  stdout: options.stdout
18749
20849
  });
18750
20850
  if (exitCode !== 0) {
18751
20851
  throw commanderExit(exitCode);
18752
20852
  }
18753
20853
  });
18754
- program2.commands.find((command) => command.name() === "sync")?.action(async (commandOptions) => {
20854
+ program2.commands.find((command) => command.name() === "sync")?.action(async (commandOptions2) => {
18755
20855
  const exitCode = await runSyncCommand({
18756
- all: commandOptions.all,
20856
+ all: commandOptions2.all,
18757
20857
  cwd: options.cwd,
18758
- json: commandOptions.json,
20858
+ json: commandOptions2.json,
18759
20859
  stderr: options.stderr,
18760
20860
  stdout: options.stdout
18761
20861
  });
@@ -18766,11 +20866,11 @@ function attachCommandActions(program2, options) {
18766
20866
  const syncFilesCommand = program2.commands.find(
18767
20867
  (command) => command.name() === "sync-files"
18768
20868
  );
18769
- syncFilesCommand?.action(async (commandOptions) => {
20869
+ syncFilesCommand?.action(async (commandOptions2) => {
18770
20870
  const exitCode = await runSyncFilesCommand({
18771
20871
  action: "list",
18772
20872
  cwd: options.cwd,
18773
- json: commandOptions.json,
20873
+ json: commandOptions2.json,
18774
20874
  stderr: options.stderr,
18775
20875
  stdout: options.stdout
18776
20876
  });
@@ -18778,11 +20878,11 @@ function attachCommandActions(program2, options) {
18778
20878
  throw commanderExit(exitCode);
18779
20879
  }
18780
20880
  });
18781
- syncFilesCommand?.commands.find((command) => command.name() === "list")?.action(async (commandOptions) => {
20881
+ syncFilesCommand?.commands.find((command) => command.name() === "list")?.action(async (commandOptions2) => {
18782
20882
  const exitCode = await runSyncFilesCommand({
18783
20883
  action: "list",
18784
20884
  cwd: options.cwd,
18785
- json: commandOptions.json || syncFilesCommand?.opts().json,
20885
+ json: commandOptions2.json || syncFilesCommand?.opts().json,
18786
20886
  stderr: options.stderr,
18787
20887
  stdout: options.stdout
18788
20888
  });
@@ -18790,11 +20890,11 @@ function attachCommandActions(program2, options) {
18790
20890
  throw commanderExit(exitCode);
18791
20891
  }
18792
20892
  });
18793
- syncFilesCommand?.commands.find((command) => command.name() === "add")?.action(async (paths, commandOptions) => {
20893
+ syncFilesCommand?.commands.find((command) => command.name() === "add")?.action(async (paths, commandOptions2) => {
18794
20894
  const exitCode = await runSyncFilesCommand({
18795
20895
  action: "add",
18796
20896
  cwd: options.cwd,
18797
- json: commandOptions.json || syncFilesCommand?.opts().json,
20897
+ json: commandOptions2.json || syncFilesCommand?.opts().json,
18798
20898
  paths,
18799
20899
  stderr: options.stderr,
18800
20900
  stdout: options.stdout
@@ -18803,11 +20903,11 @@ function attachCommandActions(program2, options) {
18803
20903
  throw commanderExit(exitCode);
18804
20904
  }
18805
20905
  });
18806
- const runSyncFilesRemoveCommand = async (paths, commandOptions) => {
20906
+ const runSyncFilesRemoveCommand = async (paths, commandOptions2) => {
18807
20907
  const exitCode = await runSyncFilesCommand({
18808
20908
  action: "remove",
18809
20909
  cwd: options.cwd,
18810
- json: commandOptions.json || syncFilesCommand?.opts().json,
20910
+ json: commandOptions2.json || syncFilesCommand?.opts().json,
18811
20911
  paths,
18812
20912
  stderr: options.stderr,
18813
20913
  stdout: options.stdout
@@ -18817,11 +20917,11 @@ function attachCommandActions(program2, options) {
18817
20917
  }
18818
20918
  };
18819
20919
  syncFilesCommand?.commands.find((command) => command.name() === "remove")?.action(runSyncFilesRemoveCommand);
18820
- program2.commands.find((command) => command.name() === "ls")?.action(async (commandOptions) => {
20920
+ program2.commands.find((command) => command.name() === "ls")?.action(async (commandOptions2) => {
18821
20921
  const exitCode = await runLsCommand({
18822
- compact: commandOptions.compact,
20922
+ compact: commandOptions2.compact,
18823
20923
  cwd: options.cwd,
18824
- json: commandOptions.json,
20924
+ json: commandOptions2.json,
18825
20925
  stdout: options.stdout
18826
20926
  });
18827
20927
  if (exitCode !== 0) {
@@ -18829,13 +20929,13 @@ function attachCommandActions(program2, options) {
18829
20929
  }
18830
20930
  });
18831
20931
  program2.commands.find((command) => command.name() === "clean")?.action(
18832
- async (commandOptions) => {
20932
+ async (commandOptions2) => {
18833
20933
  const exitCode = await runCleanCommand({
18834
20934
  cwd: options.cwd,
18835
- dryRun: commandOptions.dryRun,
18836
- force: commandOptions.force,
18837
- json: commandOptions.json,
18838
- stale: commandOptions.stale,
20935
+ dryRun: commandOptions2.dryRun,
20936
+ force: commandOptions2.force,
20937
+ json: commandOptions2.json,
20938
+ stale: commandOptions2.stale,
18839
20939
  stderr: options.stderr,
18840
20940
  stdout: options.stdout
18841
20941
  });
@@ -18844,13 +20944,13 @@ function attachCommandActions(program2, options) {
18844
20944
  }
18845
20945
  }
18846
20946
  );
18847
- const runRemovalCommand = async (branch, commandOptions = {}) => {
20947
+ const runRemovalCommand = async (branch, commandOptions2 = {}) => {
18848
20948
  const exitCode = await runRemoveCommand({
18849
20949
  branch,
18850
20950
  cwd: options.cwd,
18851
- dryRun: commandOptions.dryRun,
18852
- force: commandOptions.force,
18853
- json: commandOptions.json,
20951
+ dryRun: commandOptions2.dryRun,
20952
+ force: commandOptions2.force,
20953
+ json: commandOptions2.json,
18854
20954
  stderr: options.stderr,
18855
20955
  stdout: options.stdout
18856
20956
  });
@@ -18870,14 +20970,14 @@ function attachCommandActions(program2, options) {
18870
20970
  }
18871
20971
  });
18872
20972
  program2.commands.find((command) => command.name() === "warp")?.action(
18873
- async (branch, commandOptions) => {
18874
- const newFlag = commandOptions.new;
20973
+ async (branch, commandOptions2) => {
20974
+ const newFlag = commandOptions2.new;
18875
20975
  const newWorktree = newFlag !== void 0 && newFlag !== false;
18876
20976
  const newBranch = typeof newFlag === "string" ? newFlag : void 0;
18877
20977
  const exitCode = await runWarpCommand({
18878
20978
  branch: newWorktree ? newBranch ?? branch : branch,
18879
20979
  cwd: options.cwd,
18880
- json: commandOptions.json,
20980
+ json: commandOptions2.json,
18881
20981
  newWorktree,
18882
20982
  stderr: options.stderr,
18883
20983
  stdout: options.stdout
@@ -18971,18 +21071,21 @@ async function main() {
18971
21071
  }
18972
21072
  async function warnIfMissingShellIntegration() {
18973
21073
  try {
18974
- const { config } = await loadGlobalConfig(homedir7());
21074
+ const { config } = await loadGlobalConfig(homedir8());
18975
21075
  if (!config.shellIntegration) {
18976
- const shellBin = (process.env.SHELL ?? "").split("/").at(-1);
18977
- const shellArg = shellBin && ["bash", "zsh", "fish"].includes(shellBin) ? ` ${shellBin}` : "";
21076
+ const setupCommand = isHeadless() ? legacyShellSetupCommand() : "gji init";
18978
21077
  process.stderr.write(
18979
- `gji: shell integration not set up \u2014 run \`gji init${shellArg} --write\` to enable automatic cd.
21078
+ `gji: shell integration not set up \u2014 run \`${setupCommand}\` to enable automatic cd.
18980
21079
  `
18981
21080
  );
18982
21081
  }
18983
21082
  } catch {
18984
21083
  }
18985
21084
  }
21085
+ function legacyShellSetupCommand() {
21086
+ const shell = (process.env.SHELL ?? "").split("/").at(-1);
21087
+ return shell && ["bash", "fish", "zsh"].includes(shell) ? `gji init ${shell} --write` : "gji init <shell> --write";
21088
+ }
18986
21089
  void main();
18987
21090
  /*! Bundled license information:
18988
21091