@opentig/cli 0.2.0 → 0.2.2

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 (118) hide show
  1. package/dist/bin.mjs +2 -1
  2. package/dist/chunks/{cli-Bi6ET5UT.mjs → cli-DNzGBqmL.mjs} +1 -1
  3. package/dist/client/assets/App-BbT5H97Z.js +138 -0
  4. package/dist/client/assets/CreatePullRequestDialog-DRLeMY4e.js +2 -0
  5. package/dist/client/assets/{DiffWorkspace-RDG_Y70N.js → DiffWorkspace-DJX00otP.js} +13 -13
  6. package/dist/client/assets/{EditableFileViewer-CsbifqBO.js → EditableFileViewer-Cmbomx5y.js} +1 -1
  7. package/dist/client/assets/{File-CHjvYpb2.js → File-4o4egqlz.js} +1 -1
  8. package/dist/client/assets/FilesView-TmUF-VLr.js +1 -0
  9. package/dist/client/assets/GitHubAvatar-azKPrTMv.js +1 -0
  10. package/dist/client/assets/HistoryView-Go6rXO0P.js +1 -0
  11. package/dist/client/assets/{HtmlFileViewer-h8ULU8Gy.js → HtmlFileViewer-WpYnLdMW.js} +1 -1
  12. package/dist/client/assets/{IconEdit-Cg8S2wqW.js → IconEdit-B8LZHB8l.js} +1 -1
  13. package/dist/client/assets/{IconFolderSymlink-BZSLX1ez.js → IconFolderSymlink-D29EhQPC.js} +1 -1
  14. package/dist/client/assets/{ImageFileViewer-Cjw58ckv.js → ImageFileViewer-1ObUacJu.js} +1 -1
  15. package/dist/client/assets/LocalRefsDialog-VcrlFgxR.js +1 -0
  16. package/dist/client/assets/{MarkdownFileViewer-kLiduag6.js → MarkdownFileViewer-CvsKPnvF.js} +2 -2
  17. package/dist/client/assets/PairingPage-cSoKZi8t.js +1 -0
  18. package/dist/client/assets/{PierreDiffViewer-eAcugmoF.js → PierreDiffViewer-DVTrOwX4.js} +1 -1
  19. package/dist/client/assets/{PierreEditBoundary-DlGd1pqO.js → PierreEditBoundary-DPtaTiUB.js} +1 -1
  20. package/dist/client/assets/{PierreWorkerPool-Col4k7qz.js → PierreWorkerPool-BxP-s6jD.js} +1 -1
  21. package/dist/client/assets/{PullRequestDiff-DhKTXyIF.js → PullRequestDiff-DeQznd9n.js} +1 -1
  22. package/dist/client/assets/PullRequestViewer-IhGZZs_A.js +2 -0
  23. package/dist/client/assets/PullRequestsView-BG0xrok4.js +1 -0
  24. package/dist/client/assets/QuickOpenDialog-DpmcAzTx.js +1 -0
  25. package/dist/client/assets/RepositoryProjectsDialog-DAzbUB3X.js +1 -0
  26. package/dist/client/assets/SearchView-HoiWKG8z.js +1 -0
  27. package/dist/client/assets/SettingsDialog-pmJKOaiK.js +9 -0
  28. package/dist/client/assets/{Viewer-o1hDU33O.js → Viewer-BCk8FHrc.js} +3 -3
  29. package/dist/client/assets/{abnfDiagram-N423BO3Z-BvrnK_R-.js → abnfDiagram-N423BO3Z-BvfKOanh.js} +1 -1
  30. package/dist/client/assets/{arc-DLa3_7ua.js → arc-DB2nIH2Y.js} +1 -1
  31. package/dist/client/assets/{architectureDiagram-T3A2C74G-BexwhnyX.js → architectureDiagram-T3A2C74G-Bqh4uY3R.js} +1 -1
  32. package/dist/client/assets/base-ui-DIP2rZxz.js +1 -0
  33. package/dist/client/assets/{blockDiagram-VBNYF7ZC-CoOttUUo.js → blockDiagram-VBNYF7ZC-DMW415Ms.js} +1 -1
  34. package/dist/client/assets/{c4Diagram-5PPSVZJV-Bgp6ppUh.js → c4Diagram-5PPSVZJV-BSNPes7l.js} +1 -1
  35. package/dist/client/assets/channel-VcDernYy.js +1 -0
  36. package/dist/client/assets/checkbox-Dta4gqy2.js +1 -0
  37. package/dist/client/assets/{chunk-2GRJ4B5K-C_kzp38w.js → chunk-2GRJ4B5K-D-86JbWu.js} +1 -1
  38. package/dist/client/assets/{chunk-2Q5K7J3B-CysYnRkJ.js → chunk-2Q5K7J3B-BNI-SD-k.js} +1 -1
  39. package/dist/client/assets/{chunk-5RXB4S5H-oMS6x_ag.js → chunk-5RXB4S5H-OFFYyML8.js} +1 -1
  40. package/dist/client/assets/{chunk-5VM5RSS4-C5-dW5wf.js → chunk-5VM5RSS4-DpE1dtnL.js} +1 -1
  41. package/dist/client/assets/{chunk-6Q2QTUOP-DxahInnA.js → chunk-6Q2QTUOP-DxAnzBXJ.js} +1 -1
  42. package/dist/client/assets/{chunk-GF5L2VYU-D7V-3eRg.js → chunk-GF5L2VYU-DNrTNRgr.js} +1 -1
  43. package/dist/client/assets/{chunk-JWPE2WC7-BrLmjbVP.js → chunk-JWPE2WC7-D-jfKyIh.js} +1 -1
  44. package/dist/client/assets/{chunk-KBJHAD2P-CyOvijwM.js → chunk-KBJHAD2P-CIyQl_ts.js} +1 -1
  45. package/dist/client/assets/{chunk-RYQCIY6F-BvEjVjQs.js → chunk-RYQCIY6F-CgCYQccM.js} +1 -1
  46. package/dist/client/assets/{chunk-XXDRQBXY-C_IY9A64.js → chunk-XXDRQBXY-B31oXtqU.js} +1 -1
  47. package/dist/client/assets/classDiagram-JCYQIIEL-CUuEGRCt.js +1 -0
  48. package/dist/client/assets/classDiagram-v2-OCEON4UE-CUuEGRCt.js +1 -0
  49. package/dist/client/assets/{cose-bilkent-JH36ORCC-Cu7qZi7h.js → cose-bilkent-JH36ORCC-CrX-LDNw.js} +1 -1
  50. package/dist/client/assets/{cynefin-VYW2F7L2-B4M1t9lL.js → cynefin-VYW2F7L2-DnkWlsqW.js} +1 -1
  51. package/dist/client/assets/{cynefinDiagram-MW4NZA55-CAosOLmT.js → cynefinDiagram-MW4NZA55-bUDQ9ElF.js} +1 -1
  52. package/dist/client/assets/{dagre-VZM6K2ZE-q354V538.js → dagre-VZM6K2ZE-BW5Kni4n.js} +1 -1
  53. package/dist/client/assets/{diagram-7IWD3JNH-C_CzkBPt.js → diagram-7IWD3JNH-1EXGwguC.js} +1 -1
  54. package/dist/client/assets/{diagram-B4RE2ZJO-VvvYHE9c.js → diagram-B4RE2ZJO-CzpH5ieN.js} +1 -1
  55. package/dist/client/assets/{diagram-LBJQPF4R-D8vRQwb5.js → diagram-LBJQPF4R-BnKszJ_B.js} +1 -1
  56. package/dist/client/assets/{diagram-Q27KOJAE-Bul4aDKg.js → diagram-Q27KOJAE-DbQlD3OM.js} +1 -1
  57. package/dist/client/assets/{diagram-UB23O5K3-DNadEE58.js → diagram-UB23O5K3-BqZ-RSlm.js} +1 -1
  58. package/dist/client/assets/{ebnfDiagram-BXEA7PRR-Bf-68MMZ.js → ebnfDiagram-BXEA7PRR-B2iPWDvr.js} +1 -1
  59. package/dist/client/assets/{erDiagram-JOGREHBK-CJ-jHFsf.js → erDiagram-JOGREHBK-aMqHrKCk.js} +1 -1
  60. package/dist/client/assets/{flowDiagram-UKHOOZJN-DMOdinBU.js → flowDiagram-UKHOOZJN-ivrTsn4D.js} +1 -1
  61. package/dist/client/assets/{ganttDiagram-PKOTCBZU-DzezS3Zy.js → ganttDiagram-PKOTCBZU-IMfIqkXw.js} +1 -1
  62. package/dist/client/assets/{gitGraphDiagram-DS77QQ5N-Dftpvns8.js → gitGraphDiagram-DS77QQ5N-BNURRh2N.js} +1 -1
  63. package/dist/client/assets/{index-DAUK3rnz.js → index-B3QJsvjR.js} +4 -4
  64. package/dist/client/assets/index-Cx9LaJW8.css +1 -0
  65. package/dist/client/assets/{infoDiagram-6WML65LV-DmgUBq17.js → infoDiagram-6WML65LV-BKEENkUQ.js} +1 -1
  66. package/dist/client/assets/{ishikawaDiagram-WSZJBQD7-CL0PQhpQ.js → ishikawaDiagram-WSZJBQD7-HrCSmiTr.js} +1 -1
  67. package/dist/client/assets/{journeyDiagram-NVQOT4AX-Ck4pQx_A.js → journeyDiagram-NVQOT4AX-CQIuWA6F.js} +1 -1
  68. package/dist/client/assets/{kanban-definition-27J2QSJJ-DuRZxBql.js → kanban-definition-27J2QSJJ-h4fSAkel.js} +1 -1
  69. package/dist/client/assets/{linear-C2JDCaz4.js → linear-BAdz-uC9.js} +1 -1
  70. package/dist/client/assets/{mermaid.core-9dt2ugQA.js → mermaid.core-vLn7mMZB.js} +4 -4
  71. package/dist/client/assets/{mindmap-definition-FAOFIHXS-vy0fdDF0.js → mindmap-definition-FAOFIHXS-DOrZl827.js} +1 -1
  72. package/dist/client/assets/{pegDiagram-VL7TDLO6-DjVJy-Gm.js → pegDiagram-VL7TDLO6-_Vs7p633.js} +1 -1
  73. package/dist/client/assets/{pieDiagram-7S7Q4E2Y-ATbd_8vw.js → pieDiagram-7S7Q4E2Y-7qbB_Z16.js} +1 -1
  74. package/dist/client/assets/{quadrantDiagram-CIZ2JOQS-C8o6ofcl.js → quadrantDiagram-CIZ2JOQS-DNH8SosA.js} +1 -1
  75. package/dist/client/assets/{railroadDiagram-AXF67PYL-Qvij9Ubo.js → railroadDiagram-AXF67PYL-Ctazrrpa.js} +1 -1
  76. package/dist/client/assets/{render-markdown-BtCybfvQ.js → render-markdown-dvHhHbC0.js} +6 -6
  77. package/dist/client/assets/{requirementDiagram-LRYGKXZP-yLfrba2h.js → requirementDiagram-LRYGKXZP-BAH8dYer.js} +1 -1
  78. package/dist/client/assets/{sankeyDiagram-W5VNT64P-BTXRtTyG.js → sankeyDiagram-W5VNT64P-CuffrpdF.js} +1 -1
  79. package/dist/client/assets/{sequenceDiagram-SI44F4Z6-BN1CCZIX.js → sequenceDiagram-SI44F4Z6-CaO4fJnS.js} +1 -1
  80. package/dist/client/assets/{sizeCapture-X5ZJPWSS-BfXjBBYo.js → sizeCapture-X5ZJPWSS-B_LramBU.js} +1 -1
  81. package/dist/client/assets/{stateDiagram-OKZ733FA-DMAWiBsP.js → stateDiagram-OKZ733FA-bc7zBYHn.js} +1 -1
  82. package/dist/client/assets/stateDiagram-v2-UEYNNEHI-C-UGmA2d.js +1 -0
  83. package/dist/client/assets/{swimlanes-SLNWSIFB-BD20m5cw.js → swimlanes-SLNWSIFB-DQzg1IPn.js} +2 -2
  84. package/dist/client/assets/swimlanesDiagram-ULZ7WXOC-DnopIiWa.js +8 -0
  85. package/dist/client/assets/tanstack-BysN2Oew.js +1 -0
  86. package/dist/client/assets/{timeline-definition-Z64GVDOM-gtc0-AHB.js → timeline-definition-Z64GVDOM-CtbuIB6K.js} +1 -1
  87. package/dist/client/assets/{vennDiagram-T6HMQDX7-Cdr4AXTp.js → vennDiagram-T6HMQDX7-pYJ0ZfFS.js} +1 -1
  88. package/dist/client/assets/{viewer-tabs-D-T0xQaq.js → viewer-tabs-Cnfyd6rg.js} +1 -1
  89. package/dist/client/assets/{wardleyDiagram-T6FBY63Y-BcU4vxO8.js → wardleyDiagram-T6FBY63Y-DYl_Oi-Y.js} +1 -1
  90. package/dist/client/assets/{xychartDiagram-ELKLHX3M-B3Zuadj8.js → xychartDiagram-ELKLHX3M-Cj5YUfP5.js} +1 -1
  91. package/dist/client/index.html +4 -4
  92. package/dist/dev.mjs +3 -2
  93. package/dist/manifest.json +2 -2
  94. package/dist/server.mjs +2219 -508
  95. package/dist/utility.mjs +12 -5
  96. package/package.json +1 -1
  97. package/dist/client/assets/App-BXZLGT86.js +0 -74
  98. package/dist/client/assets/CreatePullRequestDialog-DFonMYEx.js +0 -2
  99. package/dist/client/assets/FilesView-Dl8QFbFA.js +0 -1
  100. package/dist/client/assets/GitHubAvatar-CnBaloV0.js +0 -1
  101. package/dist/client/assets/HistoryView-BgCZU2JO.js +0 -1
  102. package/dist/client/assets/LocalRefsDialog-DNgrd7Qp.js +0 -1
  103. package/dist/client/assets/PairingPage-C97sv-8s.js +0 -1
  104. package/dist/client/assets/PullRequestViewer-BgI8HFMe.js +0 -2
  105. package/dist/client/assets/PullRequestsView-PYWLy8We.js +0 -1
  106. package/dist/client/assets/QuickOpenDialog-BFDDZVCL.js +0 -1
  107. package/dist/client/assets/RepositoryProjectsDialog-CvB8KcFp.js +0 -1
  108. package/dist/client/assets/SearchView-BwF_MsxK.js +0 -1
  109. package/dist/client/assets/SettingsDialog-DIcLmXSE.js +0 -9
  110. package/dist/client/assets/base-ui-CJ8ZIx3d.js +0 -1
  111. package/dist/client/assets/channel-1k1AYpyV.js +0 -1
  112. package/dist/client/assets/checkbox-BRH0ZNIC.js +0 -1
  113. package/dist/client/assets/classDiagram-JCYQIIEL-ChyNFpay.js +0 -1
  114. package/dist/client/assets/classDiagram-v2-OCEON4UE-ChyNFpay.js +0 -1
  115. package/dist/client/assets/index-BfA4tAc1.css +0 -1
  116. package/dist/client/assets/stateDiagram-v2-UEYNNEHI-D_ofZeaa.js +0 -1
  117. package/dist/client/assets/swimlanesDiagram-ULZ7WXOC-CF9m5JSB.js +0 -8
  118. package/dist/client/assets/tanstack-B0Sf7wNU.js +0 -1
package/dist/server.mjs CHANGED
@@ -17,8 +17,12 @@ import { Transform, getDefaultHighWaterMark, Duplex, Writable, Readable, PassThr
17
17
  import { finished } from "node:stream/promises";
18
18
  import path$1 from "node:path/win32";
19
19
  import { createServer, isIP } from "node:net";
20
+ import { AsyncLocalStorage } from "node:async_hooks";
20
21
  import { createServer as createServer$1 } from "node:http";
21
22
  import WebSocket, { WebSocketServer } from "ws";
23
+ function aiExecutablePathsKey(paths) {
24
+ return JSON.stringify(["codex", "claude", "opencode", "grok"].map((id) => paths[id] ?? ""));
25
+ }
22
26
  function isPlainObject$1(value) {
23
27
  if (typeof value !== "object" || value === null) {
24
28
  return false;
@@ -5709,7 +5713,7 @@ const createReadable = ({ subprocess, concurrentStreams, encoding }, { from, bin
5709
5713
  const binary = binaryOption || BINARY_ENCODINGS.has(encoding);
5710
5714
  const { subprocessStdout, waitReadableDestroy } = getSubprocessStdout(subprocess, from, concurrentStreams);
5711
5715
  const { readableEncoding, readableObjectMode, readableHighWaterMark } = getReadableOptions(subprocessStdout, binary);
5712
- const { read, onStdoutDataDone } = getReadableMethods({
5716
+ const { read: read2, onStdoutDataDone } = getReadableMethods({
5713
5717
  subprocessStdout,
5714
5718
  subprocess,
5715
5719
  binary,
@@ -5717,7 +5721,7 @@ const createReadable = ({ subprocess, concurrentStreams, encoding }, { from, bin
5717
5721
  preserveNewlines
5718
5722
  });
5719
5723
  const readable2 = new Readable({
5720
- read,
5724
+ read: read2,
5721
5725
  destroy: callbackify(onReadableDestroy.bind(void 0, { subprocessStdout, subprocess, waitReadableDestroy })),
5722
5726
  highWaterMark: readableHighWaterMark,
5723
5727
  objectMode: readableObjectMode,
@@ -6555,7 +6559,7 @@ const createDuplex = ({ subprocess, concurrentStreams, encoding }, { from, to, b
6555
6559
  const { subprocessStdout, waitReadableDestroy } = getSubprocessStdout(subprocess, from, concurrentStreams);
6556
6560
  const { subprocessStdin, waitWritableFinal, waitWritableDestroy } = getSubprocessStdin(subprocess, to, concurrentStreams);
6557
6561
  const { readableEncoding, readableObjectMode, readableHighWaterMark } = getReadableOptions(subprocessStdout, binary);
6558
- const { read, onStdoutDataDone } = getReadableMethods({
6562
+ const { read: read2, onStdoutDataDone } = getReadableMethods({
6559
6563
  subprocessStdout,
6560
6564
  subprocess,
6561
6565
  binary,
@@ -6563,7 +6567,7 @@ const createDuplex = ({ subprocess, concurrentStreams, encoding }, { from, to, b
6563
6567
  preserveNewlines
6564
6568
  });
6565
6569
  const duplex2 = new Duplex({
6566
- read,
6570
+ read: read2,
6567
6571
  ...getWritableMethods(subprocessStdin, subprocess, waitWritableFinal),
6568
6572
  destroy: callbackify(onDuplexDestroy.bind(void 0, {
6569
6573
  subprocessStdout,
@@ -6900,9 +6904,9 @@ function pathDirectories(value, platform2) {
6900
6904
  });
6901
6905
  }
6902
6906
  class CliEnvironment {
6903
- constructor(host = currentCliHost(), read = readUserEnvironment) {
6907
+ constructor(host = currentCliHost(), read2 = readUserEnvironment) {
6904
6908
  this.host = host;
6905
- this.read = read;
6909
+ this.read = read2;
6906
6910
  }
6907
6911
  host;
6908
6912
  read;
@@ -6947,7 +6951,7 @@ async function readUserEnvironment(host) {
6947
6951
  env: host.env,
6948
6952
  extendEnv: false,
6949
6953
  input: "",
6950
- timeout: 5e3,
6954
+ timeout: host.platform === "win32" ? 15e3 : 5e3,
6951
6955
  maxBuffer: 128 * 1024,
6952
6956
  cleanup: true,
6953
6957
  killDescendants: true,
@@ -6961,7 +6965,8 @@ async function readUserEnvironment(host) {
6961
6965
  const shell2 = path.win32.join(root, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
6962
6966
  const script = '$ErrorActionPreference="Stop"; $r=@{}; foreach($scope in @("Machine","User")){ $r[$scope]=[Environment]::GetEnvironmentVariables($scope) }; ConvertTo-Json -InputObject $r -Compress';
6963
6967
  const result = await execa(shell2, ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", script], options);
6964
- if (result.exitCode !== 0 || result.timedOut || result.isMaxBuffer) throw new Error("Environment query failed");
6968
+ if (result.timedOut) throw new Error("Environment query timed out");
6969
+ if (result.exitCode !== 0 || result.isMaxBuffer) throw new Error("Environment query failed");
6965
6970
  return windowsLocations(JSON.parse(result.stdout), host.env);
6966
6971
  }
6967
6972
  const shell = host.shell;
@@ -7529,14 +7534,14 @@ const NUMBER_FORMAT_RANGES = {
7529
7534
  float32: [-34028234663852886e22, 34028234663852886e22],
7530
7535
  float64: [-Number.MAX_VALUE, Number.MAX_VALUE]
7531
7536
  };
7532
- function pick(schema, mask) {
7533
- const currDef = schema._zod.def;
7537
+ function pick(schema2, mask) {
7538
+ const currDef = schema2._zod.def;
7534
7539
  const checks = currDef.checks;
7535
7540
  const hasChecks = checks && checks.length > 0;
7536
7541
  if (hasChecks) {
7537
7542
  throw new Error(".pick() cannot be used on object schemas containing refinements");
7538
7543
  }
7539
- const def = mergeDefs(schema._zod.def, {
7544
+ const def = mergeDefs(schema2._zod.def, {
7540
7545
  get shape() {
7541
7546
  const newShape = {};
7542
7547
  for (const key in mask) {
@@ -7552,18 +7557,18 @@ function pick(schema, mask) {
7552
7557
  },
7553
7558
  checks: []
7554
7559
  });
7555
- return clone(schema, def);
7560
+ return clone(schema2, def);
7556
7561
  }
7557
- function omit(schema, mask) {
7558
- const currDef = schema._zod.def;
7562
+ function omit(schema2, mask) {
7563
+ const currDef = schema2._zod.def;
7559
7564
  const checks = currDef.checks;
7560
7565
  const hasChecks = checks && checks.length > 0;
7561
7566
  if (hasChecks) {
7562
7567
  throw new Error(".omit() cannot be used on object schemas containing refinements");
7563
7568
  }
7564
- const def = mergeDefs(schema._zod.def, {
7569
+ const def = mergeDefs(schema2._zod.def, {
7565
7570
  get shape() {
7566
- const newShape = { ...schema._zod.def.shape };
7571
+ const newShape = { ...schema2._zod.def.shape };
7567
7572
  for (const key in mask) {
7568
7573
  if (!(key in currDef.shape)) {
7569
7574
  throw new Error(`Unrecognized key: "${key}"`);
@@ -7577,43 +7582,43 @@ function omit(schema, mask) {
7577
7582
  },
7578
7583
  checks: []
7579
7584
  });
7580
- return clone(schema, def);
7585
+ return clone(schema2, def);
7581
7586
  }
7582
- function extend(schema, shape) {
7587
+ function extend(schema2, shape) {
7583
7588
  if (!isPlainObject(shape)) {
7584
7589
  throw new Error("Invalid input to extend: expected a plain object");
7585
7590
  }
7586
- const checks = schema._zod.def.checks;
7591
+ const checks = schema2._zod.def.checks;
7587
7592
  const hasChecks = checks && checks.length > 0;
7588
7593
  if (hasChecks) {
7589
- const existingShape = schema._zod.def.shape;
7594
+ const existingShape = schema2._zod.def.shape;
7590
7595
  for (const key in shape) {
7591
7596
  if (Object.getOwnPropertyDescriptor(existingShape, key) !== void 0) {
7592
7597
  throw new Error("Cannot overwrite keys on object schemas containing refinements. Use `.safeExtend()` instead.");
7593
7598
  }
7594
7599
  }
7595
7600
  }
7596
- const def = mergeDefs(schema._zod.def, {
7601
+ const def = mergeDefs(schema2._zod.def, {
7597
7602
  get shape() {
7598
- const _shape = { ...schema._zod.def.shape, ...shape };
7603
+ const _shape = { ...schema2._zod.def.shape, ...shape };
7599
7604
  assignProp(this, "shape", _shape);
7600
7605
  return _shape;
7601
7606
  }
7602
7607
  });
7603
- return clone(schema, def);
7608
+ return clone(schema2, def);
7604
7609
  }
7605
- function safeExtend(schema, shape) {
7610
+ function safeExtend(schema2, shape) {
7606
7611
  if (!isPlainObject(shape)) {
7607
7612
  throw new Error("Invalid input to safeExtend: expected a plain object");
7608
7613
  }
7609
- const def = mergeDefs(schema._zod.def, {
7614
+ const def = mergeDefs(schema2._zod.def, {
7610
7615
  get shape() {
7611
- const _shape = { ...schema._zod.def.shape, ...shape };
7616
+ const _shape = { ...schema2._zod.def.shape, ...shape };
7612
7617
  assignProp(this, "shape", _shape);
7613
7618
  return _shape;
7614
7619
  }
7615
7620
  });
7616
- return clone(schema, def);
7621
+ return clone(schema2, def);
7617
7622
  }
7618
7623
  function merge(a2, b) {
7619
7624
  if (a2._zod.def.checks?.length) {
@@ -7632,16 +7637,16 @@ function merge(a2, b) {
7632
7637
  });
7633
7638
  return clone(a2, def);
7634
7639
  }
7635
- function partial(Class, schema, mask) {
7636
- const currDef = schema._zod.def;
7640
+ function partial(Class, schema2, mask) {
7641
+ const currDef = schema2._zod.def;
7637
7642
  const checks = currDef.checks;
7638
7643
  const hasChecks = checks && checks.length > 0;
7639
7644
  if (hasChecks) {
7640
7645
  throw new Error(".partial() cannot be used on object schemas containing refinements");
7641
7646
  }
7642
- const def = mergeDefs(schema._zod.def, {
7647
+ const def = mergeDefs(schema2._zod.def, {
7643
7648
  get shape() {
7644
- const oldShape = schema._zod.def.shape;
7649
+ const oldShape = schema2._zod.def.shape;
7645
7650
  const shape = { ...oldShape };
7646
7651
  if (mask) {
7647
7652
  for (const key in mask) {
@@ -7668,12 +7673,12 @@ function partial(Class, schema, mask) {
7668
7673
  },
7669
7674
  checks: []
7670
7675
  });
7671
- return clone(schema, def);
7676
+ return clone(schema2, def);
7672
7677
  }
7673
- function required(Class, schema, mask) {
7674
- const def = mergeDefs(schema._zod.def, {
7678
+ function required(Class, schema2, mask) {
7679
+ const def = mergeDefs(schema2._zod.def, {
7675
7680
  get shape() {
7676
- const oldShape = schema._zod.def.shape;
7681
+ const oldShape = schema2._zod.def.shape;
7677
7682
  const shape = { ...oldShape };
7678
7683
  if (mask) {
7679
7684
  for (const key in mask) {
@@ -7699,7 +7704,7 @@ function required(Class, schema, mask) {
7699
7704
  return shape;
7700
7705
  }
7701
7706
  });
7702
- return clone(schema, def);
7707
+ return clone(schema2, def);
7703
7708
  }
7704
7709
  function aborted(x, startIndex = 0) {
7705
7710
  if (x.aborted === true)
@@ -7828,9 +7833,9 @@ function formatError(error, mapper = (issue2) => issue2.message) {
7828
7833
  processError(error);
7829
7834
  return fieldErrors;
7830
7835
  }
7831
- const _parse = (_Err) => (schema, value, _ctx, _params) => {
7836
+ const _parse = (_Err) => (schema2, value, _ctx, _params) => {
7832
7837
  const ctx = _ctx ? { ..._ctx, async: false } : { async: false };
7833
- const result = schema._zod.run({ value, issues: [] }, ctx);
7838
+ const result = schema2._zod.run({ value, issues: [] }, ctx);
7834
7839
  if (result instanceof Promise) {
7835
7840
  throw new $ZodAsyncError();
7836
7841
  }
@@ -7841,9 +7846,9 @@ const _parse = (_Err) => (schema, value, _ctx, _params) => {
7841
7846
  }
7842
7847
  return result.value;
7843
7848
  };
7844
- const _parseAsync = (_Err) => async (schema, value, _ctx, params) => {
7849
+ const _parseAsync = (_Err) => async (schema2, value, _ctx, params) => {
7845
7850
  const ctx = _ctx ? { ..._ctx, async: true } : { async: true };
7846
- let result = schema._zod.run({ value, issues: [] }, ctx);
7851
+ let result = schema2._zod.run({ value, issues: [] }, ctx);
7847
7852
  if (result instanceof Promise)
7848
7853
  result = await result;
7849
7854
  if (result.issues.length) {
@@ -7853,9 +7858,9 @@ const _parseAsync = (_Err) => async (schema, value, _ctx, params) => {
7853
7858
  }
7854
7859
  return result.value;
7855
7860
  };
7856
- const _safeParse = (_Err) => (schema, value, _ctx) => {
7861
+ const _safeParse = (_Err) => (schema2, value, _ctx) => {
7857
7862
  const ctx = _ctx ? { ..._ctx, async: false } : { async: false };
7858
- const result = schema._zod.run({ value, issues: [] }, ctx);
7863
+ const result = schema2._zod.run({ value, issues: [] }, ctx);
7859
7864
  if (result instanceof Promise) {
7860
7865
  throw new $ZodAsyncError();
7861
7866
  }
@@ -7865,9 +7870,9 @@ const _safeParse = (_Err) => (schema, value, _ctx) => {
7865
7870
  } : { success: true, data: result.value };
7866
7871
  };
7867
7872
  const safeParse$1 = /* @__PURE__ */ _safeParse($ZodRealError);
7868
- const _safeParseAsync = (_Err) => async (schema, value, _ctx) => {
7873
+ const _safeParseAsync = (_Err) => async (schema2, value, _ctx) => {
7869
7874
  const ctx = _ctx ? { ..._ctx, async: true } : { async: true };
7870
- let result = schema._zod.run({ value, issues: [] }, ctx);
7875
+ let result = schema2._zod.run({ value, issues: [] }, ctx);
7871
7876
  if (result instanceof Promise)
7872
7877
  result = await result;
7873
7878
  return result.issues.length ? {
@@ -7876,33 +7881,33 @@ const _safeParseAsync = (_Err) => async (schema, value, _ctx) => {
7876
7881
  } : { success: true, data: result.value };
7877
7882
  };
7878
7883
  const safeParseAsync$1 = /* @__PURE__ */ _safeParseAsync($ZodRealError);
7879
- const _encode = (_Err) => (schema, value, _ctx) => {
7884
+ const _encode = (_Err) => (schema2, value, _ctx) => {
7880
7885
  const ctx = _ctx ? { ..._ctx, direction: "backward" } : { direction: "backward" };
7881
- return _parse(_Err)(schema, value, ctx);
7886
+ return _parse(_Err)(schema2, value, ctx);
7882
7887
  };
7883
- const _decode = (_Err) => (schema, value, _ctx) => {
7884
- return _parse(_Err)(schema, value, _ctx);
7888
+ const _decode = (_Err) => (schema2, value, _ctx) => {
7889
+ return _parse(_Err)(schema2, value, _ctx);
7885
7890
  };
7886
- const _encodeAsync = (_Err) => async (schema, value, _ctx) => {
7891
+ const _encodeAsync = (_Err) => async (schema2, value, _ctx) => {
7887
7892
  const ctx = _ctx ? { ..._ctx, direction: "backward" } : { direction: "backward" };
7888
- return _parseAsync(_Err)(schema, value, ctx);
7893
+ return _parseAsync(_Err)(schema2, value, ctx);
7889
7894
  };
7890
- const _decodeAsync = (_Err) => async (schema, value, _ctx) => {
7891
- return _parseAsync(_Err)(schema, value, _ctx);
7895
+ const _decodeAsync = (_Err) => async (schema2, value, _ctx) => {
7896
+ return _parseAsync(_Err)(schema2, value, _ctx);
7892
7897
  };
7893
- const _safeEncode = (_Err) => (schema, value, _ctx) => {
7898
+ const _safeEncode = (_Err) => (schema2, value, _ctx) => {
7894
7899
  const ctx = _ctx ? { ..._ctx, direction: "backward" } : { direction: "backward" };
7895
- return _safeParse(_Err)(schema, value, ctx);
7900
+ return _safeParse(_Err)(schema2, value, ctx);
7896
7901
  };
7897
- const _safeDecode = (_Err) => (schema, value, _ctx) => {
7898
- return _safeParse(_Err)(schema, value, _ctx);
7902
+ const _safeDecode = (_Err) => (schema2, value, _ctx) => {
7903
+ return _safeParse(_Err)(schema2, value, _ctx);
7899
7904
  };
7900
- const _safeEncodeAsync = (_Err) => async (schema, value, _ctx) => {
7905
+ const _safeEncodeAsync = (_Err) => async (schema2, value, _ctx) => {
7901
7906
  const ctx = _ctx ? { ..._ctx, direction: "backward" } : { direction: "backward" };
7902
- return _safeParseAsync(_Err)(schema, value, ctx);
7907
+ return _safeParseAsync(_Err)(schema2, value, ctx);
7903
7908
  };
7904
- const _safeDecodeAsync = (_Err) => async (schema, value, _ctx) => {
7905
- return _safeParseAsync(_Err)(schema, value, _ctx);
7909
+ const _safeDecodeAsync = (_Err) => async (schema2, value, _ctx) => {
7910
+ return _safeParseAsync(_Err)(schema2, value, _ctx);
7906
7911
  };
7907
7912
  const cuid = /^[cC][0-9a-z]{6,}$/;
7908
7913
  const cuid2 = /^[0-9a-z]+$/;
@@ -9072,9 +9077,9 @@ const $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def)
9072
9077
  for (const key of normalized.keys) {
9073
9078
  const id = ids[key];
9074
9079
  const k = esc(key);
9075
- const schema = shape[key];
9076
- const isOptionalIn = schema?._zod?.optin === "optional";
9077
- const isOptionalOut = schema?._zod?.optout === "optional";
9080
+ const schema2 = shape[key];
9081
+ const isOptionalIn = schema2?._zod?.optin === "optional";
9082
+ const isOptionalOut = schema2?._zod?.optout === "optional";
9078
9083
  doc.write(`const ${id} = ${parseStr(key)};`);
9079
9084
  if (isOptionalIn && isOptionalOut) {
9080
9085
  doc.write(`
@@ -9243,6 +9248,73 @@ const $ZodUnion = /* @__PURE__ */ $constructor("$ZodUnion", (inst, def) => {
9243
9248
  });
9244
9249
  };
9245
9250
  });
9251
+ const $ZodDiscriminatedUnion = /* @__PURE__ */ $constructor("$ZodDiscriminatedUnion", (inst, def) => {
9252
+ def.inclusive = false;
9253
+ $ZodUnion.init(inst, def);
9254
+ const _super = inst._zod.parse;
9255
+ defineLazy(inst._zod, "propValues", () => {
9256
+ const propValues = {};
9257
+ for (const option of def.options) {
9258
+ const pv = option._zod.propValues;
9259
+ if (!pv || Object.keys(pv).length === 0)
9260
+ throw new Error(`Invalid discriminated union option at index "${def.options.indexOf(option)}"`);
9261
+ for (const [k, v] of Object.entries(pv)) {
9262
+ if (!propValues[k])
9263
+ propValues[k] = /* @__PURE__ */ new Set();
9264
+ for (const val of v) {
9265
+ propValues[k].add(val);
9266
+ }
9267
+ }
9268
+ }
9269
+ return propValues;
9270
+ });
9271
+ const disc = cached(() => {
9272
+ const opts = def.options;
9273
+ const map = /* @__PURE__ */ new Map();
9274
+ for (const o2 of opts) {
9275
+ const values = o2._zod.propValues?.[def.discriminator];
9276
+ if (!values || values.size === 0)
9277
+ throw new Error(`Invalid discriminated union option at index "${def.options.indexOf(o2)}"`);
9278
+ for (const v of values) {
9279
+ if (map.has(v)) {
9280
+ throw new Error(`Duplicate discriminator value "${String(v)}"`);
9281
+ }
9282
+ map.set(v, o2);
9283
+ }
9284
+ }
9285
+ return map;
9286
+ });
9287
+ inst._zod.parse = (payload, ctx) => {
9288
+ const input = payload.value;
9289
+ if (!isObject(input)) {
9290
+ payload.issues.push({
9291
+ code: "invalid_type",
9292
+ expected: "object",
9293
+ input,
9294
+ inst
9295
+ });
9296
+ return payload;
9297
+ }
9298
+ const opt = disc.value.get(input?.[def.discriminator]);
9299
+ if (opt) {
9300
+ return opt._zod.run(payload, ctx);
9301
+ }
9302
+ if (def.unionFallback || ctx.direction === "backward") {
9303
+ return _super(payload, ctx);
9304
+ }
9305
+ payload.issues.push({
9306
+ code: "invalid_union",
9307
+ errors: [],
9308
+ note: "No matching discriminator",
9309
+ discriminator: def.discriminator,
9310
+ options: Array.from(disc.value.keys()),
9311
+ input,
9312
+ path: [def.discriminator],
9313
+ inst
9314
+ });
9315
+ return payload;
9316
+ };
9317
+ });
9246
9318
  const $ZodIntersection = /* @__PURE__ */ $constructor("$ZodIntersection", (inst, def) => {
9247
9319
  $ZodType.init(inst, def);
9248
9320
  inst._zod.parse = (payload, ctx) => {
@@ -9487,6 +9559,28 @@ const $ZodEnum = /* @__PURE__ */ $constructor("$ZodEnum", (inst, def) => {
9487
9559
  return payload;
9488
9560
  };
9489
9561
  });
9562
+ const $ZodLiteral = /* @__PURE__ */ $constructor("$ZodLiteral", (inst, def) => {
9563
+ $ZodType.init(inst, def);
9564
+ if (def.values.length === 0) {
9565
+ throw new Error("Cannot create literal schema with no valid values");
9566
+ }
9567
+ const values = new Set(def.values);
9568
+ inst._zod.values = values;
9569
+ inst._zod.pattern = new RegExp(`^(${def.values.map((o2) => typeof o2 === "string" ? escapeRegex(o2) : o2 ? escapeRegex(o2.toString()) : String(o2)).join("|")})$`);
9570
+ inst._zod.parse = (payload, _ctx) => {
9571
+ const input = payload.value;
9572
+ if (values.has(input)) {
9573
+ return payload;
9574
+ }
9575
+ payload.issues.push({
9576
+ code: "invalid_value",
9577
+ values: def.values,
9578
+ input,
9579
+ inst
9580
+ });
9581
+ return payload;
9582
+ };
9583
+ });
9490
9584
  const $ZodTransform = /* @__PURE__ */ $constructor("$ZodTransform", (inst, def) => {
9491
9585
  $ZodType.init(inst, def);
9492
9586
  inst._zod.optin = "optional";
@@ -9761,11 +9855,11 @@ class $ZodRegistry {
9761
9855
  this._map = /* @__PURE__ */ new WeakMap();
9762
9856
  this._idmap = /* @__PURE__ */ new Map();
9763
9857
  }
9764
- add(schema, ..._meta) {
9858
+ add(schema2, ..._meta) {
9765
9859
  const meta = _meta[0];
9766
- this._map.set(schema, meta);
9860
+ this._map.set(schema2, meta);
9767
9861
  if (meta && typeof meta === "object" && "id" in meta) {
9768
- this._idmap.set(meta.id, schema);
9862
+ this._idmap.set(meta.id, schema2);
9769
9863
  }
9770
9864
  return this;
9771
9865
  }
@@ -9774,26 +9868,26 @@ class $ZodRegistry {
9774
9868
  this._idmap = /* @__PURE__ */ new Map();
9775
9869
  return this;
9776
9870
  }
9777
- remove(schema) {
9778
- const meta = this._map.get(schema);
9871
+ remove(schema2) {
9872
+ const meta = this._map.get(schema2);
9779
9873
  if (meta && typeof meta === "object" && "id" in meta) {
9780
9874
  this._idmap.delete(meta.id);
9781
9875
  }
9782
- this._map.delete(schema);
9876
+ this._map.delete(schema2);
9783
9877
  return this;
9784
9878
  }
9785
- get(schema) {
9786
- const p = schema._zod.parent;
9879
+ get(schema2) {
9880
+ const p = schema2._zod.parent;
9787
9881
  if (p) {
9788
9882
  const pm = { ...this.get(p) ?? {} };
9789
9883
  delete pm.id;
9790
- const f = { ...pm, ...this._map.get(schema) };
9884
+ const f = { ...pm, ...this._map.get(schema2) };
9791
9885
  return Object.keys(f).length ? f : void 0;
9792
9886
  }
9793
- return this._map.get(schema);
9887
+ return this._map.get(schema2);
9794
9888
  }
9795
- has(schema) {
9796
- return this._map.has(schema);
9889
+ has(schema2) {
9890
+ return this._map.has(schema2);
9797
9891
  }
9798
9892
  }
9799
9893
  function registry() {
@@ -10270,13 +10364,13 @@ function _array(Class, element, params) {
10270
10364
  }
10271
10365
  // @__NO_SIDE_EFFECTS__
10272
10366
  function _refine(Class, fn, _params) {
10273
- const schema = new Class({
10367
+ const schema2 = new Class({
10274
10368
  type: "custom",
10275
10369
  check: "custom",
10276
10370
  fn,
10277
10371
  ...normalizeParams(_params)
10278
10372
  });
10279
- return schema;
10373
+ return schema2;
10280
10374
  }
10281
10375
  // @__NO_SIDE_EFFECTS__
10282
10376
  function _superRefine(fn, params) {
@@ -10329,40 +10423,40 @@ function initializeContext(params) {
10329
10423
  external: params?.external ?? void 0
10330
10424
  };
10331
10425
  }
10332
- function process$1(schema, ctx, _params = { path: [], schemaPath: [] }) {
10426
+ function process$1(schema2, ctx, _params = { path: [], schemaPath: [] }) {
10333
10427
  var _a2;
10334
- const def = schema._zod.def;
10335
- const seen = ctx.seen.get(schema);
10428
+ const def = schema2._zod.def;
10429
+ const seen = ctx.seen.get(schema2);
10336
10430
  if (seen) {
10337
10431
  seen.count++;
10338
- const isCycle = _params.schemaPath.includes(schema);
10432
+ const isCycle = _params.schemaPath.includes(schema2);
10339
10433
  if (isCycle) {
10340
10434
  seen.cycle = _params.path;
10341
10435
  }
10342
10436
  return seen.schema;
10343
10437
  }
10344
10438
  const result = { schema: {}, count: 1, cycle: void 0, path: _params.path };
10345
- ctx.seen.set(schema, result);
10346
- const overrideSchema = schema._zod.toJSONSchema?.();
10439
+ ctx.seen.set(schema2, result);
10440
+ const overrideSchema = schema2._zod.toJSONSchema?.();
10347
10441
  if (overrideSchema) {
10348
10442
  result.schema = overrideSchema;
10349
10443
  } else {
10350
10444
  const params = {
10351
10445
  ..._params,
10352
- schemaPath: [..._params.schemaPath, schema],
10446
+ schemaPath: [..._params.schemaPath, schema2],
10353
10447
  path: _params.path
10354
10448
  };
10355
- if (schema._zod.processJSONSchema) {
10356
- schema._zod.processJSONSchema(ctx, result.schema, params);
10449
+ if (schema2._zod.processJSONSchema) {
10450
+ schema2._zod.processJSONSchema(ctx, result.schema, params);
10357
10451
  } else {
10358
10452
  const _json = result.schema;
10359
10453
  const processor = ctx.processors[def.type];
10360
10454
  if (!processor) {
10361
10455
  throw new Error(`[toJSONSchema]: Non-representable type encountered: ${def.type}`);
10362
10456
  }
10363
- processor(schema, ctx, _json, params);
10457
+ processor(schema2, ctx, _json, params);
10364
10458
  }
10365
- const parent = schema._zod.parent;
10459
+ const parent = schema2._zod.parent;
10366
10460
  if (parent) {
10367
10461
  if (!result.ref)
10368
10462
  result.ref = parent;
@@ -10370,21 +10464,21 @@ function process$1(schema, ctx, _params = { path: [], schemaPath: [] }) {
10370
10464
  ctx.seen.get(parent).isParent = true;
10371
10465
  }
10372
10466
  }
10373
- const meta = ctx.metadataRegistry.get(schema);
10467
+ const meta = ctx.metadataRegistry.get(schema2);
10374
10468
  if (meta)
10375
10469
  Object.assign(result.schema, meta);
10376
- if (ctx.io === "input" && isTransforming(schema)) {
10470
+ if (ctx.io === "input" && isTransforming(schema2)) {
10377
10471
  delete result.schema.examples;
10378
10472
  delete result.schema.default;
10379
10473
  }
10380
10474
  if (ctx.io === "input" && "_prefault" in result.schema)
10381
10475
  (_a2 = result.schema).default ?? (_a2.default = result.schema._prefault);
10382
10476
  delete result.schema._prefault;
10383
- const _result = ctx.seen.get(schema);
10477
+ const _result = ctx.seen.get(schema2);
10384
10478
  return _result.schema;
10385
10479
  }
10386
- function extractDefs(ctx, schema) {
10387
- const root = ctx.seen.get(schema);
10480
+ function extractDefs(ctx, schema2) {
10481
+ const root = ctx.seen.get(schema2);
10388
10482
  if (!root)
10389
10483
  throw new Error("Unprocessed schema. This is a bug in Zod.");
10390
10484
  const idToSchema = /* @__PURE__ */ new Map();
@@ -10427,11 +10521,11 @@ function extractDefs(ctx, schema) {
10427
10521
  seen.def = { ...seen.schema };
10428
10522
  if (defId)
10429
10523
  seen.defId = defId;
10430
- const schema2 = seen.schema;
10431
- for (const key in schema2) {
10432
- delete schema2[key];
10524
+ const schema3 = seen.schema;
10525
+ for (const key in schema3) {
10526
+ delete schema3[key];
10433
10527
  }
10434
- schema2.$ref = ref;
10528
+ schema3.$ref = ref;
10435
10529
  };
10436
10530
  if (ctx.cycles === "throw") {
10437
10531
  for (const entry of ctx.seen.entries()) {
@@ -10445,13 +10539,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10445
10539
  }
10446
10540
  for (const entry of ctx.seen.entries()) {
10447
10541
  const seen = entry[1];
10448
- if (schema === entry[0]) {
10542
+ if (schema2 === entry[0]) {
10449
10543
  extractToDef(entry);
10450
10544
  continue;
10451
10545
  }
10452
10546
  if (ctx.external) {
10453
10547
  const ext = ctx.external.registry.get(entry[0])?.id;
10454
- if (schema !== entry[0] && ext) {
10548
+ if (schema2 !== entry[0] && ext) {
10455
10549
  extractToDef(entry);
10456
10550
  continue;
10457
10551
  }
@@ -10473,16 +10567,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10473
10567
  }
10474
10568
  }
10475
10569
  }
10476
- function finalize(ctx, schema) {
10477
- const root = ctx.seen.get(schema);
10570
+ function finalize(ctx, schema2) {
10571
+ const root = ctx.seen.get(schema2);
10478
10572
  if (!root)
10479
10573
  throw new Error("Unprocessed schema. This is a bug in Zod.");
10480
10574
  const flattenRef = (zodSchema) => {
10481
10575
  const seen = ctx.seen.get(zodSchema);
10482
10576
  if (seen.ref === null)
10483
10577
  return;
10484
- const schema2 = seen.def ?? seen.schema;
10485
- const _cached = { ...schema2 };
10578
+ const schema3 = seen.def ?? seen.schema;
10579
+ const _cached = { ...schema3 };
10486
10580
  const ref = seen.ref;
10487
10581
  seen.ref = null;
10488
10582
  if (ref) {
@@ -10490,28 +10584,28 @@ function finalize(ctx, schema) {
10490
10584
  const refSeen = ctx.seen.get(ref);
10491
10585
  const refSchema = refSeen.schema;
10492
10586
  if (refSchema.$ref && (ctx.target === "draft-07" || ctx.target === "draft-04" || ctx.target === "openapi-3.0")) {
10493
- schema2.allOf = schema2.allOf ?? [];
10494
- schema2.allOf.push(refSchema);
10587
+ schema3.allOf = schema3.allOf ?? [];
10588
+ schema3.allOf.push(refSchema);
10495
10589
  } else {
10496
- Object.assign(schema2, refSchema);
10590
+ Object.assign(schema3, refSchema);
10497
10591
  }
10498
- Object.assign(schema2, _cached);
10592
+ Object.assign(schema3, _cached);
10499
10593
  const isParentRef = zodSchema._zod.parent === ref;
10500
10594
  if (isParentRef) {
10501
- for (const key in schema2) {
10595
+ for (const key in schema3) {
10502
10596
  if (key === "$ref" || key === "allOf")
10503
10597
  continue;
10504
10598
  if (!(key in _cached)) {
10505
- delete schema2[key];
10599
+ delete schema3[key];
10506
10600
  }
10507
10601
  }
10508
10602
  }
10509
10603
  if (refSchema.$ref && refSeen.def) {
10510
- for (const key in schema2) {
10604
+ for (const key in schema3) {
10511
10605
  if (key === "$ref" || key === "allOf")
10512
10606
  continue;
10513
- if (key in refSeen.def && JSON.stringify(schema2[key]) === JSON.stringify(refSeen.def[key])) {
10514
- delete schema2[key];
10607
+ if (key in refSeen.def && JSON.stringify(schema3[key]) === JSON.stringify(refSeen.def[key])) {
10608
+ delete schema3[key];
10515
10609
  }
10516
10610
  }
10517
10611
  }
@@ -10521,13 +10615,13 @@ function finalize(ctx, schema) {
10521
10615
  flattenRef(parent);
10522
10616
  const parentSeen = ctx.seen.get(parent);
10523
10617
  if (parentSeen?.schema.$ref) {
10524
- schema2.$ref = parentSeen.schema.$ref;
10618
+ schema3.$ref = parentSeen.schema.$ref;
10525
10619
  if (parentSeen.def) {
10526
- for (const key in schema2) {
10620
+ for (const key in schema3) {
10527
10621
  if (key === "$ref" || key === "allOf")
10528
10622
  continue;
10529
- if (key in parentSeen.def && JSON.stringify(schema2[key]) === JSON.stringify(parentSeen.def[key])) {
10530
- delete schema2[key];
10623
+ if (key in parentSeen.def && JSON.stringify(schema3[key]) === JSON.stringify(parentSeen.def[key])) {
10624
+ delete schema3[key];
10531
10625
  }
10532
10626
  }
10533
10627
  }
@@ -10535,7 +10629,7 @@ function finalize(ctx, schema) {
10535
10629
  }
10536
10630
  ctx.override({
10537
10631
  zodSchema,
10538
- jsonSchema: schema2,
10632
+ jsonSchema: schema3,
10539
10633
  path: seen.path ?? []
10540
10634
  });
10541
10635
  };
@@ -10552,13 +10646,13 @@ function finalize(ctx, schema) {
10552
10646
  } else if (ctx.target === "openapi-3.0") ;
10553
10647
  else ;
10554
10648
  if (ctx.external?.uri) {
10555
- const id = ctx.external.registry.get(schema)?.id;
10649
+ const id = ctx.external.registry.get(schema2)?.id;
10556
10650
  if (!id)
10557
10651
  throw new Error("Schema is missing an `id` property");
10558
10652
  result.$id = ctx.external.uri(id);
10559
10653
  }
10560
10654
  Object.assign(result, root.def ?? root.schema);
10561
- const rootMetaId = ctx.metadataRegistry.get(schema)?.id;
10655
+ const rootMetaId = ctx.metadataRegistry.get(schema2)?.id;
10562
10656
  if (rootMetaId !== void 0 && result.id === rootMetaId)
10563
10657
  delete result.id;
10564
10658
  const defs = ctx.external?.defs ?? {};
@@ -10584,10 +10678,10 @@ function finalize(ctx, schema) {
10584
10678
  const finalized = JSON.parse(JSON.stringify(result));
10585
10679
  Object.defineProperty(finalized, "~standard", {
10586
10680
  value: {
10587
- ...schema["~standard"],
10681
+ ...schema2["~standard"],
10588
10682
  jsonSchema: {
10589
- input: createStandardJSONSchemaMethod(schema, "input", ctx.processors),
10590
- output: createStandardJSONSchemaMethod(schema, "output", ctx.processors)
10683
+ input: createStandardJSONSchemaMethod(schema2, "input", ctx.processors),
10684
+ output: createStandardJSONSchemaMethod(schema2, "output", ctx.processors)
10591
10685
  }
10592
10686
  },
10593
10687
  enumerable: false,
@@ -10651,18 +10745,18 @@ function isTransforming(_schema, _ctx) {
10651
10745
  }
10652
10746
  return false;
10653
10747
  }
10654
- const createToJSONSchemaMethod = (schema, processors = {}) => (params) => {
10748
+ const createToJSONSchemaMethod = (schema2, processors = {}) => (params) => {
10655
10749
  const ctx = initializeContext({ ...params, processors });
10656
- process$1(schema, ctx);
10657
- extractDefs(ctx, schema);
10658
- return finalize(ctx, schema);
10750
+ process$1(schema2, ctx);
10751
+ extractDefs(ctx, schema2);
10752
+ return finalize(ctx, schema2);
10659
10753
  };
10660
- const createStandardJSONSchemaMethod = (schema, io, processors = {}) => (params) => {
10754
+ const createStandardJSONSchemaMethod = (schema2, io, processors = {}) => (params) => {
10661
10755
  const { libraryOptions, target } = params ?? {};
10662
10756
  const ctx = initializeContext({ ...libraryOptions ?? {}, target, io, processors });
10663
- process$1(schema, ctx);
10664
- extractDefs(ctx, schema);
10665
- return finalize(ctx, schema);
10757
+ process$1(schema2, ctx);
10758
+ extractDefs(ctx, schema2);
10759
+ return finalize(ctx, schema2);
10666
10760
  };
10667
10761
  const formatMap = {
10668
10762
  guid: "uuid",
@@ -10672,10 +10766,10 @@ const formatMap = {
10672
10766
  regex: ""
10673
10767
  // do not set
10674
10768
  };
10675
- const stringProcessor = (schema, ctx, _json, _params) => {
10769
+ const stringProcessor = (schema2, ctx, _json, _params) => {
10676
10770
  const json2 = _json;
10677
10771
  json2.type = "string";
10678
- const { minimum, maximum, format: format2, patterns, contentEncoding } = schema._zod.bag;
10772
+ const { minimum, maximum, format: format2, patterns, contentEncoding } = schema2._zod.bag;
10679
10773
  if (typeof minimum === "number")
10680
10774
  json2.minLength = minimum;
10681
10775
  if (typeof maximum === "number")
@@ -10704,9 +10798,9 @@ const stringProcessor = (schema, ctx, _json, _params) => {
10704
10798
  }
10705
10799
  }
10706
10800
  };
10707
- const numberProcessor = (schema, ctx, _json, _params) => {
10801
+ const numberProcessor = (schema2, ctx, _json, _params) => {
10708
10802
  const json2 = _json;
10709
- const { minimum, maximum, format: format2, multipleOf, exclusiveMaximum, exclusiveMinimum } = schema._zod.bag;
10803
+ const { minimum, maximum, format: format2, multipleOf, exclusiveMaximum, exclusiveMinimum } = schema2._zod.bag;
10710
10804
  if (typeof format2 === "string" && format2.includes("int"))
10711
10805
  json2.type = "integer";
10712
10806
  else
@@ -10781,8 +10875,8 @@ const dateProcessor = (_schema, ctx, _json, _params) => {
10781
10875
  throw new Error("Date cannot be represented in JSON Schema");
10782
10876
  }
10783
10877
  };
10784
- const enumProcessor = (schema, _ctx, json2, _params) => {
10785
- const def = schema._zod.def;
10878
+ const enumProcessor = (schema2, _ctx, json2, _params) => {
10879
+ const def = schema2._zod.def;
10786
10880
  const values = getEnumValues(def.entries);
10787
10881
  if (values.every((v) => typeof v === "number"))
10788
10882
  json2.type = "number";
@@ -10790,8 +10884,8 @@ const enumProcessor = (schema, _ctx, json2, _params) => {
10790
10884
  json2.type = "string";
10791
10885
  json2.enum = values;
10792
10886
  };
10793
- const literalProcessor = (schema, ctx, json2, _params) => {
10794
- const def = schema._zod.def;
10887
+ const literalProcessor = (schema2, ctx, json2, _params) => {
10888
+ const def = schema2._zod.def;
10795
10889
  const vals = [];
10796
10890
  for (const val of def.values) {
10797
10891
  if (val === void 0) {
@@ -10834,22 +10928,22 @@ const nanProcessor = (_schema, ctx, _json, _params) => {
10834
10928
  throw new Error("NaN cannot be represented in JSON Schema");
10835
10929
  }
10836
10930
  };
10837
- const templateLiteralProcessor = (schema, _ctx, json2, _params) => {
10931
+ const templateLiteralProcessor = (schema2, _ctx, json2, _params) => {
10838
10932
  const _json = json2;
10839
- const pattern = schema._zod.pattern;
10933
+ const pattern = schema2._zod.pattern;
10840
10934
  if (!pattern)
10841
10935
  throw new Error("Pattern not found in template literal");
10842
10936
  _json.type = "string";
10843
10937
  _json.pattern = pattern.source;
10844
10938
  };
10845
- const fileProcessor = (schema, _ctx, json2, _params) => {
10939
+ const fileProcessor = (schema2, _ctx, json2, _params) => {
10846
10940
  const _json = json2;
10847
10941
  const file = {
10848
10942
  type: "string",
10849
10943
  format: "binary",
10850
10944
  contentEncoding: "binary"
10851
10945
  };
10852
- const { minimum, maximum, mime } = schema._zod.bag;
10946
+ const { minimum, maximum, mime } = schema2._zod.bag;
10853
10947
  if (minimum !== void 0)
10854
10948
  file.minLength = minimum;
10855
10949
  if (maximum !== void 0)
@@ -10894,10 +10988,10 @@ const setProcessor = (_schema, ctx, _json, _params) => {
10894
10988
  throw new Error("Set cannot be represented in JSON Schema");
10895
10989
  }
10896
10990
  };
10897
- const arrayProcessor = (schema, ctx, _json, params) => {
10991
+ const arrayProcessor = (schema2, ctx, _json, params) => {
10898
10992
  const json2 = _json;
10899
- const def = schema._zod.def;
10900
- const { minimum, maximum } = schema._zod.bag;
10993
+ const def = schema2._zod.def;
10994
+ const { minimum, maximum } = schema2._zod.bag;
10901
10995
  if (typeof minimum === "number")
10902
10996
  json2.minItems = minimum;
10903
10997
  if (typeof maximum === "number")
@@ -10908,9 +11002,9 @@ const arrayProcessor = (schema, ctx, _json, params) => {
10908
11002
  path: [...params.path, "items"]
10909
11003
  });
10910
11004
  };
10911
- const objectProcessor = (schema, ctx, _json, params) => {
11005
+ const objectProcessor = (schema2, ctx, _json, params) => {
10912
11006
  const json2 = _json;
10913
- const def = schema._zod.def;
11007
+ const def = schema2._zod.def;
10914
11008
  json2.type = "object";
10915
11009
  json2.properties = {};
10916
11010
  const shape = def.shape;
@@ -10944,8 +11038,8 @@ const objectProcessor = (schema, ctx, _json, params) => {
10944
11038
  });
10945
11039
  }
10946
11040
  };
10947
- const unionProcessor = (schema, ctx, json2, params) => {
10948
- const def = schema._zod.def;
11041
+ const unionProcessor = (schema2, ctx, json2, params) => {
11042
+ const def = schema2._zod.def;
10949
11043
  const isExclusive = def.inclusive === false;
10950
11044
  const options = def.options.map((x, i2) => process$1(x, ctx, {
10951
11045
  ...params,
@@ -10957,8 +11051,8 @@ const unionProcessor = (schema, ctx, json2, params) => {
10957
11051
  json2.anyOf = options;
10958
11052
  }
10959
11053
  };
10960
- const intersectionProcessor = (schema, ctx, json2, params) => {
10961
- const def = schema._zod.def;
11054
+ const intersectionProcessor = (schema2, ctx, json2, params) => {
11055
+ const def = schema2._zod.def;
10962
11056
  const a2 = process$1(def.left, ctx, {
10963
11057
  ...params,
10964
11058
  path: [...params.path, "allOf", 0]
@@ -10974,9 +11068,9 @@ const intersectionProcessor = (schema, ctx, json2, params) => {
10974
11068
  ];
10975
11069
  json2.allOf = allOf;
10976
11070
  };
10977
- const tupleProcessor = (schema, ctx, _json, params) => {
11071
+ const tupleProcessor = (schema2, ctx, _json, params) => {
10978
11072
  const json2 = _json;
10979
- const def = schema._zod.def;
11073
+ const def = schema2._zod.def;
10980
11074
  json2.type = "array";
10981
11075
  const prefixPath = ctx.target === "draft-2020-12" ? "prefixItems" : "items";
10982
11076
  const restPath = ctx.target === "draft-2020-12" ? "items" : ctx.target === "openapi-3.0" ? "items" : "additionalItems";
@@ -11010,15 +11104,15 @@ const tupleProcessor = (schema, ctx, _json, params) => {
11010
11104
  json2.additionalItems = rest;
11011
11105
  }
11012
11106
  }
11013
- const { minimum, maximum } = schema._zod.bag;
11107
+ const { minimum, maximum } = schema2._zod.bag;
11014
11108
  if (typeof minimum === "number")
11015
11109
  json2.minItems = minimum;
11016
11110
  if (typeof maximum === "number")
11017
11111
  json2.maxItems = maximum;
11018
11112
  };
11019
- const recordProcessor = (schema, ctx, _json, params) => {
11113
+ const recordProcessor = (schema2, ctx, _json, params) => {
11020
11114
  const json2 = _json;
11021
- const def = schema._zod.def;
11115
+ const def = schema2._zod.def;
11022
11116
  json2.type = "object";
11023
11117
  const keyType = def.keyType;
11024
11118
  const keyBag = keyType._zod.bag;
@@ -11052,10 +11146,10 @@ const recordProcessor = (schema, ctx, _json, params) => {
11052
11146
  }
11053
11147
  }
11054
11148
  };
11055
- const nullableProcessor = (schema, ctx, json2, params) => {
11056
- const def = schema._zod.def;
11149
+ const nullableProcessor = (schema2, ctx, json2, params) => {
11150
+ const def = schema2._zod.def;
11057
11151
  const inner = process$1(def.innerType, ctx, params);
11058
- const seen = ctx.seen.get(schema);
11152
+ const seen = ctx.seen.get(schema2);
11059
11153
  if (ctx.target === "openapi-3.0") {
11060
11154
  seen.ref = def.innerType;
11061
11155
  json2.nullable = true;
@@ -11063,31 +11157,31 @@ const nullableProcessor = (schema, ctx, json2, params) => {
11063
11157
  json2.anyOf = [inner, { type: "null" }];
11064
11158
  }
11065
11159
  };
11066
- const nonoptionalProcessor = (schema, ctx, _json, params) => {
11067
- const def = schema._zod.def;
11160
+ const nonoptionalProcessor = (schema2, ctx, _json, params) => {
11161
+ const def = schema2._zod.def;
11068
11162
  process$1(def.innerType, ctx, params);
11069
- const seen = ctx.seen.get(schema);
11163
+ const seen = ctx.seen.get(schema2);
11070
11164
  seen.ref = def.innerType;
11071
11165
  };
11072
- const defaultProcessor = (schema, ctx, json2, params) => {
11073
- const def = schema._zod.def;
11166
+ const defaultProcessor = (schema2, ctx, json2, params) => {
11167
+ const def = schema2._zod.def;
11074
11168
  process$1(def.innerType, ctx, params);
11075
- const seen = ctx.seen.get(schema);
11169
+ const seen = ctx.seen.get(schema2);
11076
11170
  seen.ref = def.innerType;
11077
11171
  json2.default = JSON.parse(JSON.stringify(def.defaultValue));
11078
11172
  };
11079
- const prefaultProcessor = (schema, ctx, json2, params) => {
11080
- const def = schema._zod.def;
11173
+ const prefaultProcessor = (schema2, ctx, json2, params) => {
11174
+ const def = schema2._zod.def;
11081
11175
  process$1(def.innerType, ctx, params);
11082
- const seen = ctx.seen.get(schema);
11176
+ const seen = ctx.seen.get(schema2);
11083
11177
  seen.ref = def.innerType;
11084
11178
  if (ctx.io === "input")
11085
11179
  json2._prefault = JSON.parse(JSON.stringify(def.defaultValue));
11086
11180
  };
11087
- const catchProcessor = (schema, ctx, json2, params) => {
11088
- const def = schema._zod.def;
11181
+ const catchProcessor = (schema2, ctx, json2, params) => {
11182
+ const def = schema2._zod.def;
11089
11183
  process$1(def.innerType, ctx, params);
11090
- const seen = ctx.seen.get(schema);
11184
+ const seen = ctx.seen.get(schema2);
11091
11185
  seen.ref = def.innerType;
11092
11186
  let catchValue;
11093
11187
  try {
@@ -11097,37 +11191,37 @@ const catchProcessor = (schema, ctx, json2, params) => {
11097
11191
  }
11098
11192
  json2.default = catchValue;
11099
11193
  };
11100
- const pipeProcessor = (schema, ctx, _json, params) => {
11101
- const def = schema._zod.def;
11194
+ const pipeProcessor = (schema2, ctx, _json, params) => {
11195
+ const def = schema2._zod.def;
11102
11196
  const inIsTransform = def.in._zod.traits.has("$ZodTransform");
11103
11197
  const innerType = ctx.io === "input" ? inIsTransform ? def.out : def.in : def.out;
11104
11198
  process$1(innerType, ctx, params);
11105
- const seen = ctx.seen.get(schema);
11199
+ const seen = ctx.seen.get(schema2);
11106
11200
  seen.ref = innerType;
11107
11201
  };
11108
- const readonlyProcessor = (schema, ctx, json2, params) => {
11109
- const def = schema._zod.def;
11202
+ const readonlyProcessor = (schema2, ctx, json2, params) => {
11203
+ const def = schema2._zod.def;
11110
11204
  process$1(def.innerType, ctx, params);
11111
- const seen = ctx.seen.get(schema);
11205
+ const seen = ctx.seen.get(schema2);
11112
11206
  seen.ref = def.innerType;
11113
11207
  json2.readOnly = true;
11114
11208
  };
11115
- const promiseProcessor = (schema, ctx, _json, params) => {
11116
- const def = schema._zod.def;
11209
+ const promiseProcessor = (schema2, ctx, _json, params) => {
11210
+ const def = schema2._zod.def;
11117
11211
  process$1(def.innerType, ctx, params);
11118
- const seen = ctx.seen.get(schema);
11212
+ const seen = ctx.seen.get(schema2);
11119
11213
  seen.ref = def.innerType;
11120
11214
  };
11121
- const optionalProcessor = (schema, ctx, _json, params) => {
11122
- const def = schema._zod.def;
11215
+ const optionalProcessor = (schema2, ctx, _json, params) => {
11216
+ const def = schema2._zod.def;
11123
11217
  process$1(def.innerType, ctx, params);
11124
- const seen = ctx.seen.get(schema);
11218
+ const seen = ctx.seen.get(schema2);
11125
11219
  seen.ref = def.innerType;
11126
11220
  };
11127
- const lazyProcessor = (schema, ctx, _json, params) => {
11128
- const innerType = schema._zod.innerType;
11221
+ const lazyProcessor = (schema2, ctx, _json, params) => {
11222
+ const innerType = schema2._zod.innerType;
11129
11223
  process$1(innerType, ctx, params);
11130
- const seen = ctx.seen.get(schema);
11224
+ const seen = ctx.seen.get(schema2);
11131
11225
  seen.ref = innerType;
11132
11226
  };
11133
11227
  const allProcessors = {
@@ -11177,8 +11271,8 @@ function toJSONSchema(input, params) {
11177
11271
  const ctx2 = initializeContext({ ...params, processors: allProcessors });
11178
11272
  const defs = {};
11179
11273
  for (const entry of registry2._idmap.entries()) {
11180
- const [_, schema] = entry;
11181
- process$1(schema, ctx2);
11274
+ const [_, schema2] = entry;
11275
+ process$1(schema2, ctx2);
11182
11276
  }
11183
11277
  const schemas = {};
11184
11278
  const external = {
@@ -11188,9 +11282,9 @@ function toJSONSchema(input, params) {
11188
11282
  };
11189
11283
  ctx2.external = external;
11190
11284
  for (const entry of registry2._idmap.entries()) {
11191
- const [key, schema] = entry;
11192
- extractDefs(ctx2, schema);
11193
- schemas[key] = finalize(ctx2, schema);
11285
+ const [key, schema2] = entry;
11286
+ extractDefs(ctx2, schema2);
11287
+ schemas[key] = finalize(ctx2, schema2);
11194
11288
  }
11195
11289
  if (Object.keys(defs).length > 0) {
11196
11290
  const defsSegment = ctx2.target === "draft-2020-12" ? "$defs" : "definitions";
@@ -11814,6 +11908,18 @@ function union(options, params) {
11814
11908
  ...normalizeParams(params)
11815
11909
  });
11816
11910
  }
11911
+ const ZodDiscriminatedUnion = /* @__PURE__ */ $constructor("ZodDiscriminatedUnion", (inst, def) => {
11912
+ ZodUnion.init(inst, def);
11913
+ $ZodDiscriminatedUnion.init(inst, def);
11914
+ });
11915
+ function discriminatedUnion(discriminator, options, params) {
11916
+ return new ZodDiscriminatedUnion({
11917
+ type: "union",
11918
+ options,
11919
+ discriminator,
11920
+ ...normalizeParams(params)
11921
+ });
11922
+ }
11817
11923
  const ZodIntersection = /* @__PURE__ */ $constructor("ZodIntersection", (inst, def) => {
11818
11924
  $ZodIntersection.init(inst, def);
11819
11925
  ZodType.init(inst, def);
@@ -11895,6 +12001,27 @@ function _enum(values, params) {
11895
12001
  ...normalizeParams(params)
11896
12002
  });
11897
12003
  }
12004
+ const ZodLiteral = /* @__PURE__ */ $constructor("ZodLiteral", (inst, def) => {
12005
+ $ZodLiteral.init(inst, def);
12006
+ ZodType.init(inst, def);
12007
+ inst._zod.processJSONSchema = (ctx, json2, params) => literalProcessor(inst, ctx, json2);
12008
+ inst.values = new Set(def.values);
12009
+ Object.defineProperty(inst, "value", {
12010
+ get() {
12011
+ if (def.values.length > 1) {
12012
+ throw new Error("This schema contains multiple valid literal values. Use `.values` instead.");
12013
+ }
12014
+ return def.values[0];
12015
+ }
12016
+ });
12017
+ });
12018
+ function literal(value, params) {
12019
+ return new ZodLiteral({
12020
+ type: "literal",
12021
+ values: Array.isArray(value) ? value : [value],
12022
+ ...normalizeParams(params)
12023
+ });
12024
+ }
11898
12025
  const ZodTransform = /* @__PURE__ */ $constructor("ZodTransform", (inst, def) => {
11899
12026
  $ZodTransform.init(inst, def);
11900
12027
  ZodType.init(inst, def);
@@ -12068,7 +12195,7 @@ function superRefine(fn, params) {
12068
12195
  return /* @__PURE__ */ _superRefine(fn, params);
12069
12196
  }
12070
12197
  function redactSensitiveText(value) {
12071
- const withoutHeaders = value.replace(/\b(authorization|cookie|set-cookie)\s*[:=]\s*[^\s,;]+/gi, "$1=[redacted]").replace(/\b(token|secret|password)\s*[:=]\s*[^\s,;]+/gi, "$1=[redacted]");
12198
+ const withoutHeaders = value.replace(/\b(?:github_pat_|gh[opusr]_)[A-Za-z0-9_]{16,}/g, "[redacted]").replace(/\bauthorization\s*[:=]\s*(?:bearer|basic)\s+[^\s,;]+/gi, "authorization=[redacted]").replace(/\b(authorization|cookie|set-cookie)\s*[:=]\s*[^\s,;]+/gi, "$1=[redacted]").replace(/\b(token|secret|password)\s*[:=]\s*[^\s,;]+/gi, "$1=[redacted]");
12072
12199
  return withoutHeaders.replace(/https?:\/\/[^\s"'<>]+/gi, (candidate) => redactUrl(candidate));
12073
12200
  }
12074
12201
  function redactUrl(candidate) {
@@ -12460,51 +12587,71 @@ function cleanText(value, maxLength) {
12460
12587
  }
12461
12588
  const DEFAULT_MODEL$1 = { id: "default", label: "Default (CLI)" };
12462
12589
  class CommitMessageService {
12463
- constructor(operations, providers, log, invalidateDiscovery) {
12590
+ constructor(operations, providers, log, invalidateDiscovery, statusPersistence) {
12464
12591
  this.operations = operations;
12465
12592
  this.log = log;
12466
12593
  this.invalidateDiscovery = invalidateDiscovery;
12594
+ this.statusPersistence = statusPersistence;
12467
12595
  for (const provider of providers) this.providers.set(provider.id, provider);
12468
12596
  }
12469
12597
  operations;
12470
12598
  log;
12471
12599
  invalidateDiscovery;
12600
+ statusPersistence;
12472
12601
  providers = /* @__PURE__ */ new Map();
12473
12602
  active = /* @__PURE__ */ new Map();
12474
12603
  statusCache = null;
12604
+ statusInvalidated = false;
12475
12605
  statusGeneration = 0;
12476
12606
  pendingStatus;
12477
12607
  invalidateStatuses() {
12478
12608
  this.statusGeneration++;
12479
12609
  this.statusCache = null;
12610
+ this.statusInvalidated = true;
12480
12611
  this.pendingStatus = void 0;
12481
12612
  this.invalidateDiscovery?.();
12482
12613
  }
12483
12614
  async statuses(forceRefresh = false) {
12484
- if (!forceRefresh && this.statusCache && Date.now() - this.statusCache.at < 3e4) return this.statusCache.value;
12485
- if (this.pendingStatus && (!forceRefresh || this.pendingStatus.force)) return this.pendingStatus.promise;
12615
+ const key = this.statusPersistence?.key() ?? "";
12616
+ if (!forceRefresh && this.statusCache?.key === key) return this.statusCache.value;
12617
+ if (this.pendingStatus?.key === key && (!forceRefresh || this.pendingStatus.force)) return this.pendingStatus.promise;
12486
12618
  const generation = ++this.statusGeneration;
12487
- const promise = Promise.all([...this.providers.values()].map(async (provider) => {
12488
- try {
12489
- return await provider.status(forceRefresh);
12490
- } catch {
12491
- return {
12492
- id: provider.id,
12493
- label: { codex: "Codex", claude: "Claude Code", opencode: "OpenCode", grok: "Grok Build" }[provider.id],
12494
- installed: false,
12495
- availability: "error",
12496
- authStatus: "unknown",
12497
- installationStatus: "inspection-failed",
12498
- message: "Could not inspect this CLI. Check again or review its executable path.",
12499
- models: [DEFAULT_MODEL$1],
12500
- checkedAt: (/* @__PURE__ */ new Date()).toISOString()
12501
- };
12619
+ const promise = (async () => {
12620
+ if (!forceRefresh && !this.statusInvalidated && this.statusPersistence) {
12621
+ const saved = await this.statusPersistence.store.load(key).catch(() => null);
12622
+ if (saved?.length === this.providers.size && saved.every((status) => this.providers.has(status.id))) {
12623
+ if (generation === this.statusGeneration) this.statusCache = { key, value: saved };
12624
+ return saved;
12625
+ }
12626
+ }
12627
+ const value = await Promise.all([...this.providers.values()].map(async (provider) => {
12628
+ try {
12629
+ return { ...await provider.status(forceRefresh), checkedAt: (/* @__PURE__ */ new Date()).toISOString() };
12630
+ } catch {
12631
+ return {
12632
+ id: provider.id,
12633
+ label: { codex: "Codex", claude: "Claude Code", opencode: "OpenCode", grok: "Grok Build" }[provider.id],
12634
+ installed: false,
12635
+ availability: "error",
12636
+ authStatus: "unknown",
12637
+ installationStatus: "inspection-failed",
12638
+ message: "Could not inspect this CLI. Check again or review its executable path.",
12639
+ models: [DEFAULT_MODEL$1],
12640
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString()
12641
+ };
12642
+ }
12643
+ }));
12644
+ if (generation === this.statusGeneration) {
12645
+ this.statusCache = { key, value };
12646
+ this.statusInvalidated = false;
12647
+ try {
12648
+ await this.statusPersistence?.store.save(key, value);
12649
+ } catch {
12650
+ }
12502
12651
  }
12503
- })).then((value) => {
12504
- if (generation === this.statusGeneration) this.statusCache = { at: Date.now(), value };
12505
12652
  return value;
12506
- });
12507
- this.pendingStatus = { force: forceRefresh, promise };
12653
+ })();
12654
+ this.pendingStatus = { force: forceRefresh, key, promise };
12508
12655
  try {
12509
12656
  return await promise;
12510
12657
  } finally {
@@ -12896,8 +13043,8 @@ function label(harness) {
12896
13043
  function stripAnsi(value) {
12897
13044
  return value.replace(new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g"), "");
12898
13045
  }
12899
- function toCodexOutputSchema(schema) {
12900
- return strictify(structuredClone(schema));
13046
+ function toCodexOutputSchema(schema2) {
13047
+ return strictify(structuredClone(schema2));
12901
13048
  }
12902
13049
  function strictify(value) {
12903
13050
  if (!value || typeof value !== "object") return value;
@@ -13051,9 +13198,9 @@ class CodexProvider {
13051
13198
  const failure2 = detectionFailure(detected, { id: this.id, label: "Codex", models: [DEFAULT_MODEL$1], checkedAt });
13052
13199
  if (failure2) return failure2;
13053
13200
  const executable = requireCandidate(detected, this.id);
13054
- const login = await runCandidate(this.runner, executable, ["login", "status"]);
13055
- if (login.exitCode !== 0 || !/logged in/i.test(`${login.stdout}
13056
- ${login.stderr}`)) {
13201
+ const login2 = await runCandidate(this.runner, executable, ["login", "status"]);
13202
+ if (login2.exitCode !== 0 || !/logged in/i.test(`${login2.stdout}
13203
+ ${login2.stderr}`)) {
13057
13204
  return { ...detectionFields(detected), id: this.id, label: "Codex", availability: "error", installed: true, authStatus: "unauthenticated", version: detected.version ?? "", message: "Run codex login.", models: [DEFAULT_MODEL$1], checkedAt };
13058
13205
  }
13059
13206
  const catalog = await runCandidate(this.runner, executable, ["debug", "models", "--bundled"]);
@@ -13547,11 +13694,10 @@ class OpenCodeProvider {
13547
13694
  const executable = requireCandidate(detected, this.id);
13548
13695
  const version2 = detected.version ?? "";
13549
13696
  const cliName = openCodeCliName(executable.executable);
13550
- const [auth, catalog] = await Promise.all([
13697
+ const [auth, models] = await Promise.all([
13551
13698
  runCandidate(this.runner, executable, ["auth", "list", "--format", "json", "--standalone"]),
13552
- runCandidate(this.runner, executable, ["models", "--standalone"], { timeoutMs: 3e4 })
13699
+ this.loadModels(executable)
13553
13700
  ]);
13554
- const models = catalog.exitCode === 0 ? parseOpenCodeModels(catalog.stdout) : [DEFAULT_MODEL$1];
13555
13701
  let authStatus = parseOpenCodeAuthList(auth.stdout, auth.exitCode);
13556
13702
  if (authStatus === "unauthenticated" && models.length > 1) authStatus = "unknown";
13557
13703
  const confirmed = authStatus === "authenticated";
@@ -13566,9 +13712,20 @@ class OpenCodeProvider {
13566
13712
  version: version2,
13567
13713
  models,
13568
13714
  checkedAt,
13569
- ...authStatus === "unauthenticated" ? { message: `Run ${openCodeLoginCommand(cliName)}.` } : !confirmed ? { message: "Could not confirm authentication for the selected provider." } : {}
13715
+ ...authStatus === "unauthenticated" ? { message: `Run ${openCodeLoginCommand(cliName)}.` } : models.length === 1 ? { message: `Could not load the OpenCode model catalog. Check again or verify that ${cliName} models lists your models in a terminal.` } : !confirmed ? { message: "Could not confirm authentication for the selected provider." } : {}
13570
13716
  };
13571
13717
  }
13718
+ async loadModels(executable) {
13719
+ for (const args of [["models", "--standalone"], ["models"]]) {
13720
+ try {
13721
+ const catalog = await runCandidate(this.runner, executable, args, { timeoutMs: 3e4 });
13722
+ const models = catalog.exitCode === 0 ? parseOpenCodeModels(catalog.stdout) : [DEFAULT_MODEL$1];
13723
+ if (models.length > 1) return models;
13724
+ } catch {
13725
+ }
13726
+ }
13727
+ return [DEFAULT_MODEL$1];
13728
+ }
13572
13729
  async generate(input) {
13573
13730
  const detected = await this.detect(false, input.signal);
13574
13731
  const executable = requireCandidate(detected, this.id);
@@ -14679,6 +14836,450 @@ class RepositoryWatcher {
14679
14836
  this.onChange(this.active.id, scope);
14680
14837
  }
14681
14838
  }
14839
+ const OWNER_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?$/;
14840
+ const REPO_PATTERN = /^[A-Za-z0-9._-]+$/;
14841
+ function parseGitHubRemote(url) {
14842
+ const trimmed = url.trim();
14843
+ if (!trimmed) return null;
14844
+ let rest = null;
14845
+ const https = /^https?:\/\/(?:[^@/\s]+@)?github\.com\/(.+)$/i.exec(trimmed);
14846
+ const ssh = /^ssh:\/\/(?:[^@/\s]+@)?github\.com(?::\d+)?\/(.+)$/i.exec(trimmed);
14847
+ const scp = /^(?:[^@\s]+)@github\.com:(.+)$/i.exec(trimmed);
14848
+ const git = /^git:\/\/github\.com\/(.+)$/i.exec(trimmed);
14849
+ if (https) rest = https[1] ?? null;
14850
+ else if (ssh) rest = ssh[1] ?? null;
14851
+ else if (scp) rest = scp[1] ?? null;
14852
+ else if (git) rest = git[1] ?? null;
14853
+ if (!rest) return null;
14854
+ const segments = rest.replace(/\/+$/, "").split("/");
14855
+ if (segments.length !== 2) return null;
14856
+ const owner = segments[0] ?? "";
14857
+ const repo = (segments[1] ?? "").replace(/\.git$/i, "");
14858
+ if (!OWNER_PATTERN.test(owner) || !repo || repo === "." || repo === ".." || !REPO_PATTERN.test(repo)) return null;
14859
+ return { nameWithOwner: `${owner}/${repo}` };
14860
+ }
14861
+ function parseSshRemote(url) {
14862
+ const trimmed = url.trim();
14863
+ const ssh = /^ssh:\/\/(?:(\w[\w.-]*)@)?([A-Za-z0-9][A-Za-z0-9._-]*)(?::(\d+))?\/(.+)$/i.exec(trimmed);
14864
+ const scp = /^(?:(\w[\w.-]*)@)?([A-Za-z0-9][A-Za-z0-9._-]*):(.+)$/.exec(trimmed);
14865
+ const match = ssh ?? scp;
14866
+ if (!match) return null;
14867
+ const parsed = parseGitHubRemote(`https://github.com/${ssh ? match[4] : match[3]}`);
14868
+ if (!parsed) return null;
14869
+ return { destination: `${match[1] ? `${match[1]}@` : ""}${match[2]}`, port: ssh ? match[3] ?? null : null, ...parsed };
14870
+ }
14871
+ const GITHUB_LOGIN = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/;
14872
+ function parseGitHubAuthStatus(raw) {
14873
+ const input = JSON.parse(raw);
14874
+ if (!input || typeof input !== "object" || !("hosts" in input) || !input.hosts || typeof input.hosts !== "object") throw new Error("Invalid gh status");
14875
+ const entries = input.hosts["github.com"] ?? [];
14876
+ if (!Array.isArray(entries) || entries.length > 100) throw new Error("Invalid gh accounts");
14877
+ const accounts = [];
14878
+ for (const entry of entries) {
14879
+ if (!entry || typeof entry !== "object" || typeof entry.login !== "string" || !GITHUB_LOGIN.test(entry.login)) continue;
14880
+ accounts.push({
14881
+ host: "github.com",
14882
+ login: entry.login,
14883
+ active: entry.active === true,
14884
+ state: entry.state === "success" ? "authenticated" : entry.state === "error" ? "invalid" : "unknown",
14885
+ storage: entry.tokenSource === "keyring" ? "keyring" : typeof entry.tokenSource === "string" && (entry.tokenSource === "oauth_token" || /(?:^|[/\\])hosts\.yml$/.test(entry.tokenSource)) ? "file" : "unknown",
14886
+ ...typeof entry.scopes === "string" && /^[A-Za-z0-9_:, -]{0,500}$/.test(entry.scopes) ? { scopes: entry.scopes } : {}
14887
+ });
14888
+ }
14889
+ return { accounts, activeLogin: accounts.find((account) => account.active)?.login ?? null };
14890
+ }
14891
+ const GH_ENV = { GH_HOST: "github.com", GH_PROMPT_DISABLED: "1", GH_NO_UPDATE_NOTIFIER: "1", GH_PAGER: "cat", NO_COLOR: "1" };
14892
+ const GH_PRIVATE_ENV = ["GITHUB_TOKEN", "GH_DEBUG", "DEBUG"];
14893
+ async function identifySshAccount(remote, runner) {
14894
+ const ssh = parseSshRemote(remote);
14895
+ if (!ssh) return null;
14896
+ try {
14897
+ const config2 = await runner.run("ssh", ["-G", ...ssh.port ? ["-p", ssh.port] : [], "--", ssh.destination], {
14898
+ cwd: os.homedir(),
14899
+ timeoutMs: 5e3,
14900
+ maxOutputBytes: 64 * 1024
14901
+ });
14902
+ const hostname = config2.stdout.split(/\r?\n/).find((line) => /^hostname\s/i.test(line))?.trim().split(/\s+/)[1];
14903
+ if (config2.exitCode !== 0 || hostname?.toLowerCase() !== "github.com") return null;
14904
+ const result = await runner.run("ssh", [
14905
+ "-T",
14906
+ "-o",
14907
+ "BatchMode=yes",
14908
+ "-o",
14909
+ "StrictHostKeyChecking=yes",
14910
+ "-o",
14911
+ "ConnectTimeout=5",
14912
+ "-o",
14913
+ "ConnectionAttempts=1",
14914
+ "-o",
14915
+ "RemoteCommand=none",
14916
+ "-o",
14917
+ "UpdateHostKeys=no",
14918
+ ...ssh.port ? ["-p", ssh.port] : [],
14919
+ "--",
14920
+ ssh.destination
14921
+ ], { cwd: os.homedir(), timeoutMs: 1e4, maxOutputBytes: 64 * 1024 });
14922
+ if (result.exitCode !== 0 && result.exitCode !== 1) return null;
14923
+ const login2 = /^Hi ([A-Za-z0-9-]+)! You've successfully authenticated, but GitHub does not provide shell access\.\s*$/m.exec(`${result.stdout}
14924
+ ${result.stderr}`)?.[1];
14925
+ return login2 && GITHUB_LOGIN.test(login2) ? login2 : null;
14926
+ } catch {
14927
+ return null;
14928
+ }
14929
+ }
14930
+ async function accountAuthentication(login2, executable, runner, operation) {
14931
+ try {
14932
+ const result = await runner.run(executable.executable, ["auth", "token", "--hostname", "github.com", ...login2 ? ["--user", login2] : []], {
14933
+ cwd: os.homedir(),
14934
+ env: { ...executable.env, ...GH_ENV },
14935
+ removeEnv: login2 ? ["GH_TOKEN", ...GH_PRIVATE_ENV] : ["GH_DEBUG", "DEBUG"],
14936
+ timeoutMs: 1e4,
14937
+ maxOutputBytes: 8 * 1024
14938
+ });
14939
+ const token = result.stdout.trim();
14940
+ if (result.exitCode === 0 && /^[A-Za-z0-9_]{20,4096}$/.test(token)) return { env: { GH_TOKEN: token }, removeEnv: GH_PRIVATE_ENV };
14941
+ if (/unknown flag.*user/i.test(result.stderr)) throw new GhOperationError({
14942
+ code: "GH_CLI_INCOMPATIBLE",
14943
+ operation,
14944
+ message: "Update GitHub CLI to a version supporting multiple accounts and auth token --user."
14945
+ });
14946
+ } catch (error) {
14947
+ if (error instanceof GhOperationError) throw error;
14948
+ }
14949
+ throw new GhOperationError({
14950
+ code: login2 ? "GH_ACCOUNT_MISSING" : "GH_AUTH_REQUIRED",
14951
+ operation,
14952
+ message: login2 ? `GitHub CLI has no usable saved credentials for @${login2}. Sign in as ${login2} on the backend host and check Settings → GitHub again. SSH and GitHub CLI authentication are separate.` : "GitHub CLI has no usable credentials. Sign in on the backend host and check Settings → GitHub again."
14953
+ });
14954
+ }
14955
+ function redactCommandToken(value, auth) {
14956
+ const token = auth.env?.GH_TOKEN;
14957
+ return token ? value.split(token).join("[redacted]") : value;
14958
+ }
14959
+ function gitShellQuote(value) {
14960
+ return `'${value.replace(/\\/g, "/").replace(/'/g, `'\\''`)}'`;
14961
+ }
14962
+ function managedGitHubPath(value) {
14963
+ try {
14964
+ const url = new URL(value);
14965
+ if (!/^https:\/\/github\.com\//i.test(value) || url.protocol !== "https:" || url.hostname !== "github.com" || url.port || url.username || url.password || url.search || url.hash) return null;
14966
+ if (!/^\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(url.pathname)) return null;
14967
+ return url.pathname.slice(1);
14968
+ } catch {
14969
+ return null;
14970
+ }
14971
+ }
14972
+ async function createCredentialBroker(url, login2, token) {
14973
+ const allowedPath = managedGitHubPath(url);
14974
+ if (!allowedPath) throw new Error("Managed authentication requires one plain github.com HTTPS repository URL.");
14975
+ const directory = await mkdtemp(path.join(os.tmpdir(), "opentig-git-auth-"));
14976
+ const key = randomBytes(32).toString("hex");
14977
+ const server = createServer$1((request, response) => {
14978
+ response.setHeader("Connection", "close");
14979
+ if (request.method !== "POST" || request.url !== "/" || request.headers["x-opentig-key"] !== key) {
14980
+ response.writeHead(403).end();
14981
+ return;
14982
+ }
14983
+ let body = "";
14984
+ request.setTimeout(5e3, () => request.destroy());
14985
+ request.on("data", (data) => {
14986
+ body += data.toString("utf8");
14987
+ if (body.length > 8192) request.destroy();
14988
+ });
14989
+ request.on("end", () => {
14990
+ const fields = body.trim().split("\n").map((line) => line.replace(/\r$/, "").split(/=(.*)/s));
14991
+ const value = Object.fromEntries(fields.map(([name, field]) => [name, field]));
14992
+ const unique = ["protocol", "host", "path"].every((name) => fields.filter(([key2]) => key2 === name).length === 1);
14993
+ if (!unique || value.protocol !== "https" || value.host !== "github.com" || value.path !== allowedPath) {
14994
+ response.writeHead(403).end();
14995
+ return;
14996
+ }
14997
+ response.end(`username=${login2}
14998
+ password=${token}
14999
+
15000
+ `);
15001
+ });
15002
+ });
15003
+ const dispose = async () => {
15004
+ server.closeAllConnections();
15005
+ if (server.listening) await new Promise((resolve2) => server.close(() => resolve2()));
15006
+ await rm(directory, { recursive: true, force: true });
15007
+ };
15008
+ try {
15009
+ server.listen(0, "127.0.0.1");
15010
+ await once(server, "listening");
15011
+ const port = server.address().port;
15012
+ const headers = path.join(directory, "headers");
15013
+ const helper = path.join(directory, "helper.sh");
15014
+ await writeFile(headers, `X-OpenTig-Key: ${key}
15015
+ `, { mode: 384 });
15016
+ await writeFile(helper, `#!/bin/sh
15017
+ [ "$1" = get ] || exit 0
15018
+ curl --disable --silent --fail --noproxy '*' --max-time 10 --proto '=http' --header @${gitShellQuote(headers)} --data-binary @- http://127.0.0.1:${port}/ || exit 1
15019
+ `, { mode: 448 });
15020
+ return { helper: `!sh ${gitShellQuote(helper)}`, directory, dispose };
15021
+ } catch (error) {
15022
+ await dispose();
15023
+ throw error;
15024
+ }
15025
+ }
15026
+ const read$1 = { operation: "repository-access", readOnly: true, maxOutputBytes: 1024 * 1024 };
15027
+ const unchecked = () => ({ state: "unchecked" });
15028
+ const digest = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
15029
+ class RepositoryGitAccess {
15030
+ constructor(git, repositories, accounts, runner) {
15031
+ this.git = git;
15032
+ this.repositories = repositories;
15033
+ this.accounts = accounts;
15034
+ this.runner = runner;
15035
+ }
15036
+ git;
15037
+ repositories;
15038
+ accounts;
15039
+ runner;
15040
+ sessions = new AsyncLocalStorage();
15041
+ checks = /* @__PURE__ */ new Map();
15042
+ latest = /* @__PURE__ */ new Map();
15043
+ install() {
15044
+ this.git.network = (cwd, args, options, execute) => this.scope(cwd, () => this.execute(args, options, execute));
15045
+ this.git.authenticationKey = (cwd) => {
15046
+ const repository = this.repositories.recents().find((item) => item.path === cwd);
15047
+ return repository ? digest([this.accounts.selection(repository), this.accounts.revisionFor(repository)]) : "";
15048
+ };
15049
+ this.git.networkScope = (cwd, task) => this.scope(cwd, task);
15050
+ }
15051
+ async scope(cwd, task) {
15052
+ if (this.sessions.getStore()?.repository.path === cwd) return task();
15053
+ const repository = this.repositories.recents().find((item) => item.path === cwd);
15054
+ if (!repository) throw new Error("The repository must be open before remote operations.");
15055
+ return this.sessions.run({ repository, selection: this.accounts.selection(repository), revision: this.accounts.revisionFor(repository) }, task);
15056
+ }
15057
+ async config(repository, key, all = false) {
15058
+ try {
15059
+ return (await this.git.run(repository.path, ["config", all ? "--get-all" : "--get", key], read$1)).stdout.toString("utf8").trim();
15060
+ } catch (error) {
15061
+ if (error instanceof GitOperationError && error.detail.exitCode === 1) return "";
15062
+ throw error;
15063
+ }
15064
+ }
15065
+ async resolve(repository, push, selected) {
15066
+ const branch = (await this.git.run(repository.path, ["branch", "--show-current"], read$1)).stdout.toString("utf8").trim() || null;
15067
+ const remotes = (await this.git.run(repository.path, ["remote"], read$1)).stdout.toString("utf8").trim().split(/\r?\n/).filter(Boolean);
15068
+ let remote = selected;
15069
+ if (!remote) for (const key of [...push ? [`branch.${branch}.pushRemote`, "remote.pushDefault"] : [], `branch.${branch}.remote`]) {
15070
+ remote = await this.config(repository, key);
15071
+ if (remote) break;
15072
+ }
15073
+ if (!remote && !push && remotes.includes("origin")) remote = "origin";
15074
+ remote ||= remotes.length === 1 ? remotes[0] : void 0;
15075
+ let urls = [];
15076
+ if (remote === ".") urls = ["."];
15077
+ else if (remote && remotes.includes(remote)) urls = (await this.git.run(repository.path, ["remote", "get-url", ...push ? ["--push", "--all"] : [], "--", remote], read$1)).stdout.toString("utf8").trim().split(/\r?\n/).filter(Boolean);
15078
+ else if (selected && !remotes.includes(selected)) {
15079
+ urls = [(await this.git.run(repository.path, ["ls-remote", "--get-url", "--", selected], { ...read$1, operation: "resolve-git-url" })).stdout.toString("utf8").trim()];
15080
+ }
15081
+ return { branch, remotes, remote: remote ?? null, urls };
15082
+ }
15083
+ async publication(repositoryId, selected) {
15084
+ const repository = this.repositories.get(repositoryId);
15085
+ const selection = this.accounts.selection(repository);
15086
+ const revision2 = this.accounts.revisionFor(repository);
15087
+ const target = await this.resolve(repository, true, selected);
15088
+ let oid = null;
15089
+ try {
15090
+ oid = (await this.git.run(repository.path, ["rev-parse", "--verify", "HEAD"], read$1)).stdout.toString("utf8").trim();
15091
+ } catch {
15092
+ }
15093
+ const managed = selection.mode === "account" && selection.gitMode === "managed";
15094
+ const compatible = target.urls.length === 1 && !!managedGitHubPath(target.urls[0]);
15095
+ const configuration = (await this.git.run(repository.path, ["config", "--null", "--list"], read$1)).stdout.toString("utf8");
15096
+ const upstreamRemote = await this.config(repository, `branch.${target.branch}.remote`);
15097
+ const merge2 = await this.config(repository, `branch.${target.branch}.merge`, true);
15098
+ const pushDefault = await this.config(repository, "push.default") || "simple";
15099
+ const targetRef = target.branch ? target.remote === upstreamRemote && merge2 && ["simple", "upstream"].includes(pushDefault) ? merge2 : `refs/heads/${target.branch}` : null;
15100
+ const unsafe = merge2 && (merge2.includes("\n") || !["simple", "upstream", "current"].includes(pushDefault) || pushDefault === "simple" && target.remote === upstreamRemote && merge2 !== `refs/heads/${target.branch}` || pushDefault === "upstream" && target.remote !== upstreamRemote || await this.config(repository, `remote.${target.remote}.push`) || await this.config(repository, `remote.${target.remote}.mirror`) === "true");
15101
+ const blocked = selected && selected !== "." && !target.remotes.includes(selected) ? "The selected push remote is not configured. Review the repository remotes." : unsafe ? "The push refspec, mirror or push.default configuration requires review in Git before publishing." : target.urls.length > 1 ? "This remote has multiple push URLs. Choose a single destination in Git configuration before publishing." : target.remote && !target.urls.length ? "The configured push remote is unavailable." : managed && target.urls.some(isGitHubHttp) && !compatible ? "Managed Git requires a plain github.com HTTPS destination. Review the URL or use external authentication." : void 0;
15102
+ return {
15103
+ ...target,
15104
+ urls: target.urls.map(redactSensitiveText),
15105
+ repositoryId,
15106
+ oid,
15107
+ targetRef,
15108
+ id: digest([repository.commonDir, selection, revision2, target, oid, configuration]),
15109
+ mode: managed && compatible ? "managed" : "external",
15110
+ login: managed && compatible && selection.mode === "account" ? selection.login : null,
15111
+ ...blocked ? { blocked } : {}
15112
+ };
15113
+ }
15114
+ async assertPublication(repositoryId, expected, remote) {
15115
+ const current = await this.publication(repositoryId, remote);
15116
+ if (expected && expected !== current.id) throw new GitOperationError({ code: "INVALID_ARGUMENT", operation: "push", message: "The account, branch or destination changed. Review the publication context and try again." });
15117
+ if (current.blocked) throw new GitOperationError({ code: "INVALID_ARGUMENT", operation: "push", message: current.blocked });
15118
+ return current;
15119
+ }
15120
+ async status(repositoryId, force = false, apiContext) {
15121
+ const publication = await this.publication(repositoryId);
15122
+ const repository = this.repositories.get(repositoryId);
15123
+ const previous = this.checks.get(repositoryId);
15124
+ const value = previous?.publication.id === publication.id ? structuredClone(previous) : {
15125
+ publication,
15126
+ identity: unchecked(),
15127
+ api: unchecked(),
15128
+ read: publication.mode === "external" ? { state: "external" } : unchecked(),
15129
+ write: unchecked(),
15130
+ checkedAt: null
15131
+ };
15132
+ const last = this.latest.get(repositoryId);
15133
+ if (last) value.lastOperation = last;
15134
+ if (!force) {
15135
+ if (value.checkedAt && Date.now() - Date.parse(value.checkedAt) > 6e4) for (const key of ["identity", "api", "read", "write"]) {
15136
+ if (value[key].state === "ok") value[key] = { state: "stale", message: value[key].message ?? "Check access again for a current result." };
15137
+ }
15138
+ return value;
15139
+ }
15140
+ value.identity = unchecked();
15141
+ value.api = unchecked();
15142
+ value.write = unchecked();
15143
+ value.read = publication.mode === "external" ? { state: "external" } : unchecked();
15144
+ const selection = this.accounts.selection(repository);
15145
+ if (selection.mode !== "account") {
15146
+ value.identity = { state: "unchecked", message: "Choose a saved account to pin access for this repository." };
15147
+ } else {
15148
+ try {
15149
+ this.accounts.refreshAuthentication();
15150
+ const { auth, executable } = await this.accounts.authenticate(repository, apiContext?.remote ?? "", apiContext?.nameWithOwner ?? "", "repository-access");
15151
+ value.identity = { state: "ok" };
15152
+ const origin = await this.resolve(repository, false, "origin");
15153
+ const apiUrl = origin.urls[0] ?? "";
15154
+ const apiPath = apiContext?.nameWithOwner ?? managedGitHubPath(apiUrl) ?? /^git@github\.com:([\w.-]+\/[\w.-]+)$/.exec(apiUrl)?.[1];
15155
+ const checkApi = async (name) => {
15156
+ try {
15157
+ const result = await this.runner.run(executable.executable, ["api", `repos/${name.replace(/\.git$/, "")}`], {
15158
+ cwd: repository.path,
15159
+ env: { ...executable.env, ...GH_ENV, ...auth.env },
15160
+ ...auth.removeEnv ? { removeEnv: auth.removeEnv } : {},
15161
+ timeoutMs: 15e3,
15162
+ maxOutputBytes: 128 * 1024
15163
+ });
15164
+ if (result.exitCode !== 0) {
15165
+ const check = accessError(redactCommandToken(result.stderr, auth));
15166
+ return { check, write: check };
15167
+ }
15168
+ const data = JSON.parse(result.stdout);
15169
+ return { check: { state: "ok" }, write: typeof data.permissions?.push === "boolean" ? { state: data.permissions.push ? "ok" : "inaccessible", message: data.permissions.push ? "Write permission declared by GitHub; branch policies still apply." : "GitHub does not declare write permission." } : unchecked() };
15170
+ } catch (error) {
15171
+ const check = accessError(redactCommandToken(messageOf(error), auth));
15172
+ return { check, write: check };
15173
+ }
15174
+ };
15175
+ if (apiPath) value.api = (await checkApi(apiPath)).check;
15176
+ const destination = publication.urls[0];
15177
+ const pushPath = destination && managedGitHubPath(destination);
15178
+ if (pushPath) value.write = (await checkApi(pushPath)).write;
15179
+ if (publication.mode === "managed" && destination && !publication.blocked) {
15180
+ try {
15181
+ await this.git.withNetworkContext(repository.path, () => this.git.run(repository.path, ["ls-remote", "--", destination, "HEAD"], { ...read$1, timeoutMs: 3e4 }));
15182
+ value.read = { state: "ok", message: "Read verified at the push destination. This does not guarantee a push." };
15183
+ } catch (error) {
15184
+ value.read = accessError(messageOf(error));
15185
+ }
15186
+ }
15187
+ } catch (error) {
15188
+ if (value.identity.state === "ok") value.api = accessError(messageOf(error));
15189
+ else value.identity = accessError(messageOf(error));
15190
+ }
15191
+ }
15192
+ if ((await this.publication(repositoryId)).id !== publication.id) return this.status(repositoryId, false, apiContext);
15193
+ value.checkedAt = (/* @__PURE__ */ new Date()).toISOString();
15194
+ this.checks.set(repositoryId, value);
15195
+ return value;
15196
+ }
15197
+ async execute(args, options, execute) {
15198
+ const session = this.sessions.getStore();
15199
+ const { repository, selection } = session;
15200
+ let index = 0;
15201
+ while (args[index] === "-c") index += 2;
15202
+ const command2 = args[index];
15203
+ if (command2 === "ls-remote" && args.includes("--get-url")) return execute(args, options);
15204
+ const selected = args.slice(index + 1).find((arg) => !arg.startsWith("-"));
15205
+ const target = await this.resolve(repository, command2 === "push", selected);
15206
+ if (target.urls.length !== 1) throw new GitOperationError({ code: "INVALID_ARGUMENT", operation: options.operation, message: "The remote destination is ambiguous. Configure or choose a single remote URL." });
15207
+ const url = target.urls[0];
15208
+ if (selected?.includes("://") && selected !== url) throw new GitOperationError({ code: "INVALID_ARGUMENT", operation: options.operation, message: "URL rewriting changes the reviewed destination again. Resolve the chained rewrite before checking or publishing." });
15209
+ const managed = selection.mode === "account" && selection.gitMode === "managed" && isGitHubHttp(url);
15210
+ let broker;
15211
+ let token = "";
15212
+ try {
15213
+ if (managed) {
15214
+ if (!managedGitHubPath(url)) throw new Error("Managed Git cannot send credentials to this destination. Use external authentication for unsupported URLs.");
15215
+ if (!session.auth) {
15216
+ if (session.revision !== this.accounts.revisionFor(repository)) throw new Error("The repository account changed before the operation started. Review and retry.");
15217
+ session.auth = await this.accounts.authenticate(repository, url, managedGitHubPath(url), options.operation);
15218
+ if (session.revision !== this.accounts.revisionFor(repository) || digest(session.auth.context.selection) !== digest(selection)) throw new Error("The repository account changed before authentication completed. Review and retry.");
15219
+ }
15220
+ if (command2 === "fetch" && !selected && target.remote) args = [...args, "--", target.remote];
15221
+ token = session.auth.auth.env?.GH_TOKEN ?? "";
15222
+ broker = await createCredentialBroker(url, selection.login, token);
15223
+ const config2 = (await this.git.run(repository.path, ["config", "--name-only", "--list"], read$1)).stdout.toString("utf8").trim().split(/\r?\n/);
15224
+ const overrides = [...new Set(config2)].flatMap((key) => {
15225
+ if (/^credential\..*helper$/i.test(key)) return ["-c", `${key}=`];
15226
+ if (/^http\..*extraheader$/i.test(key)) return ["-c", `${key}=`];
15227
+ if (/^http\..*followredirects$/i.test(key)) return ["-c", `${key}=false`];
15228
+ if (/^http\..*sslverify$/i.test(key)) return ["-c", `${key}=true`];
15229
+ if (/^credential\..*usehttppath$/i.test(key)) return ["-c", `${key}=true`];
15230
+ return [];
15231
+ });
15232
+ args = [
15233
+ ...overrides,
15234
+ "-c",
15235
+ "credential.helper=",
15236
+ "-c",
15237
+ `credential.helper=${broker.helper}`,
15238
+ "-c",
15239
+ "credential.useHttpPath=true",
15240
+ "-c",
15241
+ "core.askPass=",
15242
+ "-c",
15243
+ "http.extraHeader=",
15244
+ "-c",
15245
+ "http.followRedirects=false",
15246
+ "-c",
15247
+ "http.sslVerify=true",
15248
+ "-c",
15249
+ "fetch.recurseSubmodules=false",
15250
+ "-c",
15251
+ "push.recurseSubmodules=no",
15252
+ ...args
15253
+ ];
15254
+ }
15255
+ if (options.publication) await this.assertPublication(options.publication.repositoryId, options.publication.id, options.publication.remote);
15256
+ const result = await execute(args, { ...options, ...managed ? { managed: true, managedSecrets: [token] } : {} });
15257
+ this.latest.set(repository.id, { operation: options.operation, url: redactSensitiveText(url), login: managed && selection.mode === "account" ? selection.login : null, at: (/* @__PURE__ */ new Date()).toISOString(), ok: true });
15258
+ return result;
15259
+ } catch (error) {
15260
+ const message = redactSensitiveText(token ? messageOf(error).split(token).join("[redacted]") : messageOf(error));
15261
+ this.latest.set(repository.id, { operation: options.operation, url: redactSensitiveText(url), login: managed && selection.mode === "account" ? selection.login : null, at: (/* @__PURE__ */ new Date()).toISOString(), ok: false, message });
15262
+ if (error instanceof GitOperationError) throw error;
15263
+ throw new GitOperationError({ code: "UNKNOWN", operation: options.operation, message });
15264
+ } finally {
15265
+ await broker?.dispose();
15266
+ }
15267
+ }
15268
+ }
15269
+ function messageOf(error) {
15270
+ return error instanceof Error ? error.message : "Could not check remote access.";
15271
+ }
15272
+ function accessError(message) {
15273
+ return { state: /401|bad credentials|expired|revoked|no usable saved credentials/i.test(message) ? "expired" : /404|403|not found|denied|not accessible/i.test(message) ? "inaccessible" : "offline", message: redactSensitiveText(message).slice(0, 1500) };
15274
+ }
15275
+ function isGitHubHttp(value) {
15276
+ try {
15277
+ const url = new URL(value);
15278
+ return ["http:", "https:"].includes(url.protocol) && url.hostname === "github.com";
15279
+ } catch {
15280
+ return false;
15281
+ }
15282
+ }
14682
15283
  function normalizeLockKey(value) {
14683
15284
  const unified = value.replace(/\\/g, "/").replace(/\/+$/, "");
14684
15285
  return process.platform === "win32" ? unified.toLowerCase() : unified;
@@ -14689,7 +15290,21 @@ class GitProcess {
14689
15290
  activeControllers = /* @__PURE__ */ new Set();
14690
15291
  idleWaiters = /* @__PURE__ */ new Set();
14691
15292
  closePromise = null;
15293
+ authenticationKey;
15294
+ network;
15295
+ networkScope;
15296
+ withNetworkContext(cwd, task) {
15297
+ return this.networkScope ? this.networkScope(cwd, task) : task();
15298
+ }
14692
15299
  run(cwd, args, options) {
15300
+ let index = 0;
15301
+ while (args[index] === "-c") index += 2;
15302
+ if (this.network && ["fetch", "push", "ls-remote"].includes(args[index] ?? "")) {
15303
+ return this.network(cwd, args, options, (next, settings) => this.runDirect(cwd, next, settings));
15304
+ }
15305
+ return this.runDirect(cwd, args, options);
15306
+ }
15307
+ runDirect(cwd, args, options) {
14693
15308
  const globalArgs = ["--no-pager"];
14694
15309
  if (options.readOnly) globalArgs.push("--no-optional-locks");
14695
15310
  return this.spawnGit(cwd, [...globalArgs, ...args], options);
@@ -14745,6 +15360,10 @@ class GitProcess {
14745
15360
  LC_ALL: "C"
14746
15361
  }).filter((entry) => entry[1] !== void 0)
14747
15362
  );
15363
+ if (options.managed) for (const key of Object.keys(environment)) {
15364
+ if (/^(GH_TOKEN|GITHUB_TOKEN|GH_DEBUG|SSLKEYLOGFILE|GIT_SSL_NO_VERIFY|GIT_ASKPASS|SSH_ASKPASS|GIT_CONFIG.*|GIT_TRACE.*|GIT_CURL_VERBOSE)$/i.test(key)) delete environment[key];
15365
+ }
15366
+ const sanitize = (value) => redactSensitiveText((options.managedSecrets ?? []).reduce((text2, secret) => text2.split(secret).join("[redacted]"), value));
14748
15367
  const resolvedCommand = resolveProcessCommand("git", cwd, environment);
14749
15368
  const outputLimit = new AbortController();
14750
15369
  const lifecycle = new AbortController();
@@ -14784,8 +15403,8 @@ class GitProcess {
14784
15403
  childPid = subprocess.pid;
14785
15404
  if (childPid) this.activeChildren.add(childPid);
14786
15405
  const result = await subprocess;
14787
- const stdout = Buffer.from(result.stdout);
14788
- const stderr = Buffer.from(result.stderr);
15406
+ const stdout = options.managed ? Buffer.from(sanitize(Buffer.from(result.stdout).toString("utf8"))) : Buffer.from(result.stdout);
15407
+ const stderr = Buffer.from(sanitize(Buffer.from(result.stderr).toString("utf8")));
14789
15408
  if (overLimit || result.isMaxBuffer) {
14790
15409
  if (options.truncateOverflow) return { stdout, stderr, exitCode: 0, truncated: true };
14791
15410
  throw new GitOperationError({ code: "OUTPUT_LIMIT", operation: options.operation, message: "Git output exceeded the safety limit." });
@@ -14796,11 +15415,11 @@ class GitProcess {
14796
15415
  if (result.exitCode === void 0 || !resolvedCommand.found) {
14797
15416
  throw this.mapError(options.operation, Object.assign(new Error("Git was not found in PATH."), { code: "ENOENT" }));
14798
15417
  }
14799
- if (result.exitCode !== 0) throw this.fromExit(options.operation, result.exitCode, stderr.toString("utf8"));
15418
+ if (result.exitCode !== 0) throw this.fromExit(options.operation, result.exitCode, sanitize(`${stderr.toString("utf8")}${options.operation.includes("push") || options.operation === "publish-branch" ? "\n" + stdout.toString("utf8") : ""}`));
14800
15419
  return { stdout, stderr, exitCode: result.exitCode };
14801
15420
  } catch (error) {
14802
15421
  if (error instanceof GitOperationError) throw error;
14803
- throw this.mapError(options.operation, error);
15422
+ throw this.mapError(options.operation, Object.assign(new Error(sanitize(error instanceof Error ? error.message : "Could not start Git.")), { code: error?.code }));
14804
15423
  } finally {
14805
15424
  if (childPid) this.activeChildren.delete(childPid);
14806
15425
  this.activeControllers.delete(lifecycle);
@@ -15140,47 +15759,381 @@ function parseWorktrees(buffer) {
15140
15759
  if (current) worktrees.push(current);
15141
15760
  return worktrees;
15142
15761
  }
15143
- const AI_PATCH_BUDGET = 4e5;
15144
- const AI_SUMMARY_BUDGET = 24e3;
15145
- class GitRepositoryOperations {
15146
- constructor(git, repositories, files) {
15762
+ const commitAuthorshipInputSchema = object({
15763
+ name: string().trim().min(1).max(120).regex(/^[^<>]+$/).refine(noControls),
15764
+ email: string().trim().max(254).regex(/^[^\s<>@]+@[^\s<>@]+$/).refine(noControls),
15765
+ expectedRevision: string().regex(/^[a-f0-9]{64}$/)
15766
+ });
15767
+ function noControls(value) {
15768
+ return [...value].every((character) => character.charCodeAt(0) >= 32 && character.charCodeAt(0) !== 127);
15769
+ }
15770
+ const environmentKeys = ["GIT_AUTHOR_NAME", "GIT_AUTHOR_EMAIL", "GIT_COMMITTER_NAME", "GIT_COMMITTER_EMAIL", "EMAIL"];
15771
+ const identityPattern = "^(user|author|committer)\\.(name|email)$";
15772
+ class GitCommitAuthorship {
15773
+ constructor(git, repositories) {
15147
15774
  this.git = git;
15148
15775
  this.repositories = repositories;
15149
- this.files = files;
15150
15776
  }
15151
15777
  git;
15152
15778
  repositories;
15153
- files;
15154
- knownOids = /* @__PURE__ */ new Map();
15155
- async diff(request) {
15156
- const repository = this.repositories.get(request.repositoryId);
15157
- const paths = this.repositories.validatePaths(request.repositoryId, [request.path]);
15158
- const tracked = (await this.git.run(
15159
- repository.path,
15160
- ["--literal-pathspecs", "ls-files", "-z", "--", ...paths],
15161
- { operation: "diff-tracked-check", readOnly: true, maxOutputBytes: 1024 * 1024 }
15162
- )).stdout.length > 0;
15163
- if (request.kind === "unstaged" && !tracked) return this.untrackedDiff(request.repositoryId, request.path);
15164
- const args = ["--literal-pathspecs", "diff"];
15165
- if (request.kind === "staged") args.push("--cached");
15166
- args.push("--no-color", "--no-ext-diff", "--unified=3", "--", ...paths);
15167
- const output = await this.git.run(repository.path, args, { operation: `diff-${request.kind}`, readOnly: true, maxOutputBytes: 32 * 1024 * 1024 });
15168
- return makeDiffResult(request.path, output.stdout);
15169
- }
15170
- async commitDiff(repositoryId, oid) {
15171
- this.assertKnownOid(repositoryId, oid);
15779
+ revisionKey = randomBytes(32);
15780
+ async get(repositoryId) {
15172
15781
  const repository = this.repositories.get(repositoryId);
15173
- const output = await this.git.run(repository.path, ["show", "--format=", "--no-color", "--no-ext-diff", oid, "--"], { operation: "commit-diff", readOnly: true, maxOutputBytes: 32 * 1024 * 1024 });
15174
- return makeDiffResult(oid, output.stdout);
15782
+ return this.git.runWriteTask(repository.path, () => this.read(repositoryId), repository.commonDir);
15175
15783
  }
15176
- async commitFiles(repositoryId, oid) {
15177
- this.assertKnownOid(repositoryId, oid);
15784
+ async set(repositoryId, input) {
15785
+ const parsed = commitAuthorshipInputSchema.safeParse(input);
15786
+ if (!parsed.success) throw invalid$1("Enter a valid name, email and reviewed identity.");
15787
+ const { name, email: email2, expectedRevision } = parsed.data;
15178
15788
  const repository = this.repositories.get(repositoryId);
15179
- const common2 = ["show", "--format=", "--no-color", "--no-ext-diff", "-M", "-z"];
15180
- const [nameStatus, numstat] = await Promise.all([
15181
- this.git.run(repository.path, [...common2, "--name-status", oid, "--"], { operation: "commit-files", readOnly: true, maxOutputBytes: 8 * 1024 * 1024 }),
15182
- this.git.run(repository.path, [...common2, "--numstat", oid, "--"], { operation: "commit-files", readOnly: true, maxOutputBytes: 8 * 1024 * 1024 })
15183
- ]);
15789
+ const configPath = path.join(repository.commonDir, "config");
15790
+ const lockPath = `${configPath}.lock`;
15791
+ return this.git.runWriteTask(repository.path, async (run) => {
15792
+ const current = await this.read(repositoryId);
15793
+ if (!current.editable) throw invalid$1(current.blockers.join(" "));
15794
+ if (current.revision !== expectedRevision) throw invalid$1("Git identity or repository settings changed. Reopen the editor and review them again.");
15795
+ let locked = false;
15796
+ try {
15797
+ const file = await open(lockPath, "wx", 384);
15798
+ locked = true;
15799
+ try {
15800
+ await file.writeFile(await readFile(configPath));
15801
+ } finally {
15802
+ await file.close();
15803
+ }
15804
+ const reviewed = await this.read(repositoryId);
15805
+ if (!reviewed.editable || reviewed.revision !== expectedRevision) throw invalid$1("Git settings changed before saving. Reopen the editor and review them again.");
15806
+ const mode = (await lstat(configPath)).mode & 511;
15807
+ await run(["config", "--file", lockPath, "--replace-all", "--", "user.name", name], { operation: "commit-authorship-set" });
15808
+ await run(["config", "--file", lockPath, "--replace-all", "--", "user.email", email2], { operation: "commit-authorship-set" });
15809
+ const candidate = await this.identities(repository.path, ["-c", `include.path=${lockPath}`]);
15810
+ if (candidate.author?.name !== name || candidate.author.email !== email2 || candidate.committer?.name !== name || candidate.committer.email !== email2) {
15811
+ throw invalid$1("An included Git setting overrides this identity. Edit that setting outside OpenTig before applying a repository identity.");
15812
+ }
15813
+ const latest = await this.read(repositoryId);
15814
+ if (!latest.editable || latest.revision !== expectedRevision) throw invalid$1("Git settings changed while saving. Reopen the editor and review them again.");
15815
+ await chmod(lockPath, mode);
15816
+ await rename(lockPath, configPath);
15817
+ locked = false;
15818
+ } catch (error) {
15819
+ if (error.code === "EEXIST") {
15820
+ throw invalid$1("Another Git process is editing repository settings. Wait for it to finish and try again.");
15821
+ }
15822
+ throw error;
15823
+ } finally {
15824
+ if (locked) await rm(lockPath, { force: true });
15825
+ }
15826
+ return this.read(repositoryId);
15827
+ }, repository.commonDir);
15828
+ }
15829
+ async read(repositoryId) {
15830
+ const repository = this.repositories.get(repositoryId);
15831
+ const configPath = path.join(repository.commonDir, "config");
15832
+ const [entries, identities, config2, info] = await Promise.all([
15833
+ this.config(repository.path),
15834
+ this.identities(repository.path),
15835
+ readFile(configPath),
15836
+ lstat(configPath)
15837
+ ]);
15838
+ const environment = environmentKeys.filter((key) => process.env[key] !== void 0);
15839
+ const custom = entries.some((entry) => entry.key.startsWith("author.") || entry.key.startsWith("committer."));
15840
+ const worktree = entries.some((entry) => entry.scope === "worktree");
15841
+ const command2 = entries.some((entry) => entry.scope === "command");
15842
+ const blockers = [];
15843
+ if (environment.length) blockers.push("Backend environment variables override Git identity. Manage them outside OpenTig before editing repository authorship.");
15844
+ if (custom) blockers.push("Git author.* or committer.* settings override user.name/email. Manage those settings outside OpenTig first.");
15845
+ if (worktree) blockers.push("This worktree has its own identity. Manage its worktree settings outside OpenTig first.");
15846
+ if (command2) blockers.push("Backend command configuration overrides Git identity. Manage it outside OpenTig first.");
15847
+ if (!info.isFile() || info.nlink !== 1) blockers.push("The repository config is linked or shared. Edit it outside OpenTig to avoid changing another repository.");
15848
+ const scopes = ["user.name", "user.email"].map((key) => entries.filter((entry) => entry.key === key).at(-1)?.scope ?? "inferred");
15849
+ const source = environment.length || command2 ? "environment" : custom ? "custom" : worktree ? "worktree" : !identities.author ? "unset" : scopes[0] !== scopes[1] ? "mixed" : scopes[0] === "local" || scopes[0] === "global" || scopes[0] === "system" ? scopes[0] : "inferred";
15850
+ const revision2 = createHmac("sha256", this.revisionKey).update(repository.path).update(config2).update(JSON.stringify({ entries, identities, environment, mode: info.mode, linked: info.nlink, blockers })).digest("hex");
15851
+ return { ...identities, source, editable: blockers.length === 0, blockers, revision: revision2 };
15852
+ }
15853
+ async config(cwd) {
15854
+ try {
15855
+ const output = await this.git.run(cwd, ["config", "--null", "--show-scope", "--get-regexp", identityPattern], { operation: "commit-authorship-read", readOnly: true });
15856
+ const fields = output.stdout.toString("utf8").split("\0");
15857
+ const entries = [];
15858
+ for (let index = 0; index + 1 < fields.length; index += 2) {
15859
+ const field = fields[index + 1];
15860
+ const separator = field.indexOf("\n");
15861
+ if (separator >= 0) entries.push({ scope: fields[index], key: field.slice(0, separator), value: field.slice(separator + 1) });
15862
+ }
15863
+ return entries;
15864
+ } catch (error) {
15865
+ if (error instanceof GitOperationError && error.detail.exitCode === 1) return [];
15866
+ throw error;
15867
+ }
15868
+ }
15869
+ async identities(cwd, prefix = []) {
15870
+ const [author, committer] = await Promise.all(["GIT_AUTHOR_IDENT", "GIT_COMMITTER_IDENT"].map(async (variable) => {
15871
+ try {
15872
+ const output = await this.git.run(cwd, [...prefix, "var", variable], { operation: "commit-authorship-read", readOnly: true });
15873
+ const match = /^(.*?) <([^<>]*)> \d+ [+-]\d{4}$/.exec(output.stdout.toString("utf8").trim());
15874
+ if (!match) throw invalid$1("Git returned an unreadable commit identity.");
15875
+ return { name: match[1], email: match[2] };
15876
+ } catch (error) {
15877
+ if (error instanceof GitOperationError && error.detail.exitCode === 128 && /identity unknown|unable to auto-detect|empty ident|no email was given|no name was given/i.test(error.detail.stderr ?? "")) return null;
15878
+ throw error;
15879
+ }
15880
+ }));
15881
+ return { author, committer };
15882
+ }
15883
+ }
15884
+ function invalid$1(message) {
15885
+ return new GitOperationError({ code: "INVALID_ARGUMENT", operation: "commit-authorship-set", message });
15886
+ }
15887
+ const read = { operation: "remote-branches", readOnly: true, maxOutputBytes: 4 * 1024 * 1024 };
15888
+ const network = { ...read, timeoutMs: 6e4 };
15889
+ class GitRemoteBranches {
15890
+ constructor(git, repositories) {
15891
+ this.git = git;
15892
+ this.repositories = repositories;
15893
+ }
15894
+ git;
15895
+ repositories;
15896
+ reader(root) {
15897
+ return (args, options) => this.git.run(root, args, options);
15898
+ }
15899
+ async remotes(run) {
15900
+ const names = (await run(["remote"], read)).stdout.toString("utf8").trim().split(/\r?\n/).filter(Boolean);
15901
+ return Promise.all(names.map(async (name) => ({ name, refspecs: (await this.config(run, `remote.${name}.fetch`)).split(/\r?\n/).filter(Boolean) })));
15902
+ }
15903
+ async config(run, key) {
15904
+ try {
15905
+ return (await run(["config", "--get-all", key], read)).stdout.toString("utf8").trim();
15906
+ } catch (error) {
15907
+ if (error instanceof GitOperationError && error.detail.exitCode === 1) return "";
15908
+ throw error;
15909
+ }
15910
+ }
15911
+ mappings(fullName, remotes) {
15912
+ return remotes.flatMap((remote) => remote.refspecs.flatMap((spec) => {
15913
+ if (spec.startsWith("^")) return [];
15914
+ const [source, destination] = spec.replace(/^\+/, "").split(":");
15915
+ if (!source?.startsWith("refs/heads/") || !destination?.startsWith("refs/remotes/")) return [];
15916
+ const match = refspecMatch(destination, fullName);
15917
+ if (match === null) return [];
15918
+ const sourceRef = source.replace("*", match);
15919
+ if (remote.refspecs.some((excluded) => excluded.startsWith("^") && refspecMatch(excluded.slice(1), sourceRef) !== null)) return [];
15920
+ return [{ remote: remote.name, sourceRef }];
15921
+ }));
15922
+ }
15923
+ async snapshotBranches(repositoryId, branches) {
15924
+ const root = this.repositories.get(repositoryId).path;
15925
+ const run = this.reader(root);
15926
+ const remotes = await this.remotes(run);
15927
+ const aliases = new Set((await run(["for-each-ref", "--format=%(refname)%09%(symref)", "refs/remotes"], read)).stdout.toString("utf8").split(/\r?\n/).filter((line) => line.split(" ")[1]).map((line) => line.split(" ")[0]));
15928
+ return { remotes: remotes.map((remote) => remote.name), branches: branches.filter((branch) => !aliases.has(branch.fullName)).map((branch) => {
15929
+ if (!branch.remote) return branch;
15930
+ const mappings = this.mappings(branch.fullName, remotes);
15931
+ if (mappings.length !== 1) return branch;
15932
+ return { ...branch, remoteName: mappings[0].remote, remoteBranchName: mappings[0].sourceRef.slice("refs/heads/".length) };
15933
+ }) };
15934
+ }
15935
+ async target(fullName, run) {
15936
+ const branches = parseRefs((await run(["for-each-ref", `--format=${REF_FORMAT}`, "refs/remotes"], read)).stdout);
15937
+ const branch = branches.find((item) => item.fullName === fullName);
15938
+ if (!branch) return null;
15939
+ const aliases = (await run(["for-each-ref", "--format=%(refname)%09%(symref)", fullName], read)).stdout.toString("utf8").split(/\r?\n/).some((line) => line.split(" ")[0] === fullName && Boolean(line.split(" ")[1]));
15940
+ const mappings = this.mappings(fullName, await this.remotes(run));
15941
+ if (aliases || mappings.length !== 1) throw new GitOperationError({
15942
+ code: "INVALID_ARGUMENT",
15943
+ operation: "remote-branch",
15944
+ message: "This reference does not map to one configured remote branch. Check the remote fetch settings."
15945
+ });
15946
+ return { branch, ...mappings[0], branchName: mappings[0].sourceRef.slice("refs/heads/".length) };
15947
+ }
15948
+ async destination(target, run, root) {
15949
+ const fetchUrls = (await run(["remote", "get-url", "--all", "--", target.remote], read)).stdout.toString("utf8").trim().split(/\r?\n/);
15950
+ const pushUrls = (await run(["remote", "get-url", "--push", "--all", "--", target.remote], read)).stdout.toString("utf8").trim().split(/\r?\n/);
15951
+ const id = createHash("sha256").update(JSON.stringify([target.remote, target.sourceRef, fetchUrls, pushUrls, this.git.authenticationKey?.(root)])).digest("hex");
15952
+ const blocked = fetchUrls.length !== 1 || pushUrls.length !== 1 || fetchUrls[0] !== pushUrls[0] ? "Fetch and push destinations differ or have multiple URLs. Manage remote deletion outside OpenTig." : null;
15953
+ return { id, blocked, url: fetchUrls[0] };
15954
+ }
15955
+ async advertised(target, run, url) {
15956
+ const lines = (await run(["ls-remote", "--symref", "--", url, "HEAD", target.sourceRef], network)).stdout.toString("utf8").split(/\r?\n/);
15957
+ const defaultRef = lines.find((line) => line.startsWith("ref: refs/heads/") && line.endsWith(" HEAD"))?.split(" ")[0]?.slice(5) ?? null;
15958
+ const oid = lines.find((line) => line.split(" ")[1] === target.sourceRef)?.split(" ")[0] ?? null;
15959
+ return { defaultRef, oid };
15960
+ }
15961
+ async details(repositoryId, fullName) {
15962
+ const run = this.reader(this.repositories.get(repositoryId).path);
15963
+ const target = await this.target(fullName, run);
15964
+ if (!target) throw new GitOperationError({ code: "INVALID_ARGUMENT", operation: "remote-branch-details", message: "The remote reference no longer exists. Fetch branches again." });
15965
+ const destination = await this.destination(target, run, this.repositories.get(repositoryId).path);
15966
+ let blocked = destination.blocked;
15967
+ let defaultRef = null;
15968
+ let remoteState = "unchecked";
15969
+ if (!blocked) {
15970
+ try {
15971
+ const state = await this.advertised(target, run, destination.url);
15972
+ defaultRef = state.defaultRef;
15973
+ remoteState = !state.oid ? "missing" : state.oid === target.branch.oid ? "current" : "changed";
15974
+ blocked = !state.oid ? "The branch no longer exists on the remote. Fetch to remove the obsolete reference." : state.oid !== target.branch.oid ? "The remote branch changed since the last fetch. Fetch before deleting." : !state.defaultRef ? "The remote does not identify its default branch. Deletion is unavailable here." : state.defaultRef === target.sourceRef ? "This is the remote’s default branch and cannot be deleted here." : null;
15975
+ } catch {
15976
+ blocked = "Could not check the remote. Fetch again or check your connection and Git permissions.";
15977
+ }
15978
+ }
15979
+ const upstreams = (await run(["for-each-ref", "--format=%(refname)%00%(refname:short)%00%(upstream)%00%(worktreepath)", "refs/heads"], read)).stdout.toString("utf8");
15980
+ const localBranches = upstreams.split(/\r?\n/).map((line) => line.split("\0")).filter((fields) => fields[2] === fullName).map(([ref, name, , worktreePath]) => ({ fullName: ref, name, worktreePath: worktreePath || null }));
15981
+ return {
15982
+ ...target.branch,
15983
+ remote: target.remote,
15984
+ branchName: target.branchName,
15985
+ localBranches,
15986
+ defaultBranch: defaultRef?.slice("refs/heads/".length) ?? null,
15987
+ remoteState,
15988
+ destinationId: destination.id,
15989
+ deletionBlockedReason: blocked
15990
+ };
15991
+ }
15992
+ async create(request) {
15993
+ const repository = this.repositories.get(request.repositoryId);
15994
+ return this.git.runWriteTask(repository.path, async (run) => {
15995
+ const target = await this.target(request.fullName, run);
15996
+ if (!target) return { status: "missing" };
15997
+ if (target.branch.oid !== request.expectedOid) return { status: "stale" };
15998
+ if (request.localName.startsWith("-")) throw new GitOperationError({ code: "INVALID_ARGUMENT", operation: "create-tracking-branch", message: "Invalid local branch name." });
15999
+ await run(["check-ref-format", `refs/heads/${request.localName}`], read);
16000
+ const localRef = `refs/heads/${request.localName}`;
16001
+ const existing = (await run(["for-each-ref", "--format=%(refname)", "refs/heads"], read)).stdout.toString("utf8").trim().split(/\r?\n/);
16002
+ if (existing.includes(localRef)) return { status: "exists" };
16003
+ await run(["branch", "--track", "--", request.localName, target.branch.fullName], { operation: "create-tracking-branch" });
16004
+ return { status: "created", fullName: localRef };
16005
+ }, repository.commonDir);
16006
+ }
16007
+ async delete(request) {
16008
+ const repository = this.repositories.get(request.repositoryId);
16009
+ return this.git.runWriteTask(repository.path, async (run) => {
16010
+ const target = await this.target(request.fullName, run);
16011
+ if (!target) return { status: "missing" };
16012
+ if (target.branch.oid !== request.expectedOid) return { status: "stale" };
16013
+ const destination = await this.destination(target, run, repository.path);
16014
+ if (destination.id !== request.destinationId) return { status: "destination-changed" };
16015
+ if (destination.blocked) return { status: "rejected", message: destination.blocked };
16016
+ try {
16017
+ const state = await this.advertised(target, run, destination.url);
16018
+ if (!state.oid) return { status: "missing" };
16019
+ if (!state.defaultRef) return { status: "rejected", message: "Could not identify the remote default branch. Nothing was deleted." };
16020
+ if (state.defaultRef === target.sourceRef) return { status: "default" };
16021
+ if (state.oid !== request.expectedOid) return { status: "stale" };
16022
+ await run(
16023
+ [
16024
+ "-c",
16025
+ `remote.${target.remote}.mirror=false`,
16026
+ "push",
16027
+ "--porcelain",
16028
+ "--no-follow-tags",
16029
+ `--force-with-lease=${target.sourceRef}:${request.expectedOid}`,
16030
+ "--",
16031
+ destination.url,
16032
+ `:${target.sourceRef}`
16033
+ ],
16034
+ { operation: "delete-remote-branch", timeoutMs: 12e4, maxOutputBytes: 4 * 1024 * 1024 }
16035
+ );
16036
+ return { status: "deleted" };
16037
+ } catch {
16038
+ return { status: "rejected", message: "Git refused the remote deletion. Check permissions or branch protection, fetch, and try again. No local branch or worktree was removed." };
16039
+ }
16040
+ }, repository.commonDir);
16041
+ }
16042
+ async fetch(repositoryId) {
16043
+ const repository = this.repositories.get(repositoryId);
16044
+ try {
16045
+ await this.git.runWriteTask(repository.path, async (run) => {
16046
+ for (const remote of await this.remotes(run)) {
16047
+ const specs = remote.refspecs.filter((spec) => spec.startsWith("^refs/heads/") || /^\+?refs\/heads\/[^:]+:refs\/remotes\//.test(spec));
16048
+ if (!specs.some((spec) => !spec.startsWith("^"))) continue;
16049
+ await run(
16050
+ ["-c", "fetch.pruneTags=false", "fetch", "--prune", "--no-prune-tags", "--no-tags", "--no-recurse-submodules", "--", remote.name, ...specs],
16051
+ { operation: "fetch-branches", timeoutMs: 12e4, maxOutputBytes: 4 * 1024 * 1024 }
16052
+ );
16053
+ }
16054
+ }, repository.commonDir);
16055
+ } catch {
16056
+ return { status: "failed", message: "Could not fetch all remote branches. Check your connection and Git credentials, then fetch again." };
16057
+ }
16058
+ const status = await this.repositories.status(repositoryId, false);
16059
+ return { status: "success", ahead: status.ahead, behind: status.behind };
16060
+ }
16061
+ }
16062
+ function refspecMatch(pattern, ref) {
16063
+ const star = pattern.indexOf("*");
16064
+ if (star < 0) return pattern === ref ? "" : null;
16065
+ const prefix = pattern.slice(0, star);
16066
+ const suffix = pattern.slice(star + 1);
16067
+ return ref.startsWith(prefix) && ref.endsWith(suffix) && ref.length >= prefix.length + suffix.length ? ref.slice(prefix.length, suffix ? -suffix.length : void 0) : null;
16068
+ }
16069
+ const AI_PATCH_BUDGET = 4e5;
16070
+ const AI_SUMMARY_BUDGET = 24e3;
16071
+ class GitRepositoryOperations {
16072
+ constructor(git, repositories, files) {
16073
+ this.git = git;
16074
+ this.repositories = repositories;
16075
+ this.files = files;
16076
+ this.authorship = new GitCommitAuthorship(git, repositories);
16077
+ this.remoteBranches = new GitRemoteBranches(git, repositories);
16078
+ }
16079
+ git;
16080
+ repositories;
16081
+ files;
16082
+ access;
16083
+ publicationContext(repositoryId, remote) {
16084
+ if (!this.access) throw new Error("Repository access is unavailable.");
16085
+ return this.access.publication(repositoryId, remote);
16086
+ }
16087
+ knownOids = /* @__PURE__ */ new Map();
16088
+ authorship;
16089
+ remoteBranches;
16090
+ remoteBranchDetails(repositoryId, fullName) {
16091
+ return this.remoteBranches.details(repositoryId, fullName);
16092
+ }
16093
+ createTrackingBranch(request) {
16094
+ return this.remoteBranches.create(request);
16095
+ }
16096
+ deleteRemoteBranch(request) {
16097
+ return this.git.withNetworkContext(this.repositories.get(request.repositoryId).path, () => this.remoteBranches.delete(request));
16098
+ }
16099
+ fetchBranches(repositoryId) {
16100
+ return this.git.withNetworkContext(this.repositories.get(repositoryId).path, () => this.remoteBranches.fetch(repositoryId));
16101
+ }
16102
+ getCommitAuthorship(repositoryId) {
16103
+ return this.authorship.get(repositoryId);
16104
+ }
16105
+ setCommitAuthorship(repositoryId, input) {
16106
+ return this.authorship.set(repositoryId, input);
16107
+ }
16108
+ async diff(request) {
16109
+ const repository = this.repositories.get(request.repositoryId);
16110
+ const paths = this.repositories.validatePaths(request.repositoryId, [request.path]);
16111
+ const tracked = (await this.git.run(
16112
+ repository.path,
16113
+ ["--literal-pathspecs", "ls-files", "-z", "--", ...paths],
16114
+ { operation: "diff-tracked-check", readOnly: true, maxOutputBytes: 1024 * 1024 }
16115
+ )).stdout.length > 0;
16116
+ if (request.kind === "unstaged" && !tracked) return this.untrackedDiff(request.repositoryId, request.path);
16117
+ const args = ["--literal-pathspecs", "diff"];
16118
+ if (request.kind === "staged") args.push("--cached");
16119
+ args.push("--no-color", "--no-ext-diff", "--unified=3", "--", ...paths);
16120
+ const output = await this.git.run(repository.path, args, { operation: `diff-${request.kind}`, readOnly: true, maxOutputBytes: 32 * 1024 * 1024 });
16121
+ return makeDiffResult(request.path, output.stdout);
16122
+ }
16123
+ async commitDiff(repositoryId, oid) {
16124
+ this.assertKnownOid(repositoryId, oid);
16125
+ const repository = this.repositories.get(repositoryId);
16126
+ const output = await this.git.run(repository.path, ["show", "--format=", "--no-color", "--no-ext-diff", oid, "--"], { operation: "commit-diff", readOnly: true, maxOutputBytes: 32 * 1024 * 1024 });
16127
+ return makeDiffResult(oid, output.stdout);
16128
+ }
16129
+ async commitFiles(repositoryId, oid) {
16130
+ this.assertKnownOid(repositoryId, oid);
16131
+ const repository = this.repositories.get(repositoryId);
16132
+ const common2 = ["show", "--format=", "--no-color", "--no-ext-diff", "-M", "-z"];
16133
+ const [nameStatus, numstat] = await Promise.all([
16134
+ this.git.run(repository.path, [...common2, "--name-status", oid, "--"], { operation: "commit-files", readOnly: true, maxOutputBytes: 8 * 1024 * 1024 }),
16135
+ this.git.run(repository.path, [...common2, "--numstat", oid, "--"], { operation: "commit-files", readOnly: true, maxOutputBytes: 8 * 1024 * 1024 })
16136
+ ]);
15184
16137
  return parseCommitFiles(nameStatus.stdout.toString("utf8"), numstat.stdout.toString("utf8"));
15185
16138
  }
15186
16139
  async commitFileDiff(repositoryId, oid, filePath, oldPath) {
@@ -15555,6 +16508,9 @@ ${base}
15555
16508
  }, repository.commonDir);
15556
16509
  }
15557
16510
  async fetch(repositoryId) {
16511
+ return this.git.withNetworkContext(this.repositories.get(repositoryId).path, () => this.fetchInContext(repositoryId));
16512
+ }
16513
+ async fetchInContext(repositoryId) {
15558
16514
  const repository = this.repositories.get(repositoryId);
15559
16515
  try {
15560
16516
  await this.runFetch(repository.path, "fetch");
@@ -15566,6 +16522,9 @@ ${base}
15566
16522
  return { status: "success", ahead: status.ahead, behind: status.behind };
15567
16523
  }
15568
16524
  async pull(repositoryId) {
16525
+ return this.git.withNetworkContext(this.repositories.get(repositoryId).path, () => this.pullInContext(repositoryId));
16526
+ }
16527
+ async pullInContext(repositoryId) {
15569
16528
  const repository = this.repositories.get(repositoryId);
15570
16529
  let status = await this.repositories.status(repositoryId, false);
15571
16530
  const existingConflicts = conflictPaths(status);
@@ -15642,7 +16601,47 @@ ${base}
15642
16601
  const nextStatus = await this.repositories.status(repositoryId, false);
15643
16602
  return { status: "success", commits, restoredLocalChanges: hasLocalChanges, rebased, localCommits: nextStatus.ahead };
15644
16603
  }
15645
- async push(repositoryId, publish) {
16604
+ async push(repositoryId, publish, expectedContext) {
16605
+ if (this.access) return this.pushWithContext(repositoryId, publish, expectedContext);
16606
+ return this.legacyPush(repositoryId, publish);
16607
+ }
16608
+ async pushWithContext(repositoryId, publish, expectedContext) {
16609
+ const repository = this.repositories.get(repositoryId);
16610
+ try {
16611
+ const reviewed = await this.access.assertPublication(repositoryId, expectedContext, publish?.remote);
16612
+ return await this.git.withNetworkContext(repository.path, () => this.git.runWriteTask(repository.path, async (run) => {
16613
+ const context = await this.access.assertPublication(repositoryId, reviewed.id, publish?.remote);
16614
+ const status = await this.repositories.status(repositoryId, false);
16615
+ const conflicts = conflictPaths(status);
16616
+ if (conflicts.length) return { status: "blocked-conflicts", files: conflicts };
16617
+ if (status.operation) return { status: "blocked-operation", operation: status.operation };
16618
+ if (!context.branch || !context.oid || !context.targetRef) return { status: "rejected", reason: "configuration", message: "Check out a branch with a commit before publishing." };
16619
+ if (publish && (publish.expectedBranch !== context.branch || publish.expectedOid !== context.oid)) return { status: "rejected", reason: "configuration", message: "The branch changed. Review and try again." };
16620
+ if (!context.remote) return context.remotes.length ? { status: "remote-required", branch: context.branch, oid: context.oid, remotes: context.remotes } : { status: "rejected", reason: "configuration", message: "No remote is configured." };
16621
+ const result = await run([
16622
+ "-c",
16623
+ `remote.${context.remote}.mirror=false`,
16624
+ "push",
16625
+ "--porcelain",
16626
+ "--no-follow-tags",
16627
+ ...!status.upstream ? ["--set-upstream"] : [],
16628
+ "--",
16629
+ context.remote,
16630
+ `refs/heads/${context.branch}:${context.targetRef}`
16631
+ ], {
16632
+ operation: "push",
16633
+ timeoutMs: 12e4,
16634
+ maxOutputBytes: 4 * 1024 * 1024,
16635
+ publication: { repositoryId, id: context.id, ...publish?.remote ? { remote: publish.remote } : {} }
16636
+ });
16637
+ if (!status.upstream) return { status: "published", branch: context.branch, remote: context.remote };
16638
+ return result.stdout.toString("utf8").includes("[up to date]") ? { status: "up-to-date" } : { status: "success", commits: status.ahead };
16639
+ }, repository.commonDir));
16640
+ } catch (error) {
16641
+ return pushFailure(error);
16642
+ }
16643
+ }
16644
+ async legacyPush(repositoryId, publish) {
15646
16645
  const repository = this.repositories.get(repositoryId);
15647
16646
  let status = await this.repositories.status(repositoryId, false);
15648
16647
  const existingConflicts = conflictPaths(status);
@@ -15736,15 +16735,16 @@ ${base}
15736
16735
  return this.repositories.openPath(worktree.path);
15737
16736
  }
15738
16737
  /**
15739
- * Cheap enumeration for the local refs manager. Deliberately runs no status
16738
+ * Cheap enumeration for the refs manager. Deliberately runs no status
15740
16739
  * scan per worktree so ordinary refreshes stay as fast as the selectors.
15741
16740
  */
15742
16741
  async localRefsSnapshot(repositoryId) {
15743
16742
  const repository = this.repositories.get(repositoryId);
15744
16743
  const [branches, worktrees] = await Promise.all([this.branches(repositoryId), this.worktrees(repositoryId)]);
15745
- const locals = branches.filter((branch) => !branch.remote).sort(compareBranches);
16744
+ const managed = await this.remoteBranches.snapshotBranches(repositoryId, branches);
15746
16745
  return {
15747
- branches: locals,
16746
+ branches: managed.branches.sort(compareBranches),
16747
+ remotes: managed.remotes,
15748
16748
  worktrees: await Promise.all(worktrees.map(async (worktree) => ({ ...worktree, current: await samePath(worktree.path, repository.path) })))
15749
16749
  };
15750
16750
  }
@@ -16139,24 +17139,20 @@ function bounded(value, limit) {
16139
17139
  }
16140
17140
  function pushFailure(error) {
16141
17141
  const detail = error instanceof GitOperationError ? error.detail : null;
16142
- const raw = `${detail?.stderr ?? ""}
16143
- ${detail?.message ?? (error instanceof Error ? error.message : "")}`.toLowerCase();
16144
- if (/authentication failed|permission denied|could not read username|access denied|publickey/.test(raw)) {
16145
- return { status: "rejected", reason: "authentication", message: "The remote rejected the credentials or you do not have permission to push." };
16146
- }
16147
- if (/non-fast-forward|fetch first|stale info|failed to push some refs/.test(raw)) {
16148
- return { status: "rejected", reason: "remote-changed", message: "The remote contains new changes. Update the branch and try again." };
16149
- }
16150
- if (/hook declined|pre-receive hook|protected branch|remote rejected/.test(raw)) {
16151
- return { status: "rejected", reason: "hook", message: "The server rejected the push because of a rule or branch protection." };
16152
- }
16153
- if (/could not resolve host|unable to access|connection timed out|connection reset|network is unreachable/.test(raw)) {
16154
- return { status: "rejected", reason: "network", message: "Could not connect to the remote. Check your network and try again." };
16155
- }
16156
- if (/upstream branch .* does not match|no upstream branch|push\.default/.test(raw)) {
16157
- return { status: "rejected", reason: "configuration", message: "The local branch and its upstream do not allow a safe push with the current configuration." };
16158
- }
16159
- return { status: "rejected", reason: "unknown", message: "Git rejected the push. No force push was performed." };
17142
+ const message = redactSensitiveText(detail?.stderr || detail?.message || (error instanceof Error ? error.message : "Git rejected the push.")).trim().slice(0, 3e3);
17143
+ const raw = message.toLowerCase();
17144
+ let reason = "unknown";
17145
+ let explanation = "";
17146
+ if (/repository not found|repository .* not found|(?:http|error:)\s*404/.test(raw)) {
17147
+ reason = "inaccessible";
17148
+ explanation = "The repository is missing or inaccessible to this account. ";
17149
+ } else if (/authentication failed|could not read username|bad credentials|expired|revoked|no usable saved credentials|publickey|(?:http|error:)\s*401/.test(raw)) reason = "authentication";
17150
+ else if (/protected branch|hook declined|pre-receive hook|remote rejected|gh006|gh013|repository rule/.test(raw)) reason = "hook";
17151
+ else if (/permission.*denied|write access.*not granted|access denied|(?:http|error:)\s*403/.test(raw)) reason = "permission";
17152
+ else if (/non-fast-forward|fetch first|stale info/.test(raw)) reason = "remote-changed";
17153
+ else if (/could not resolve host|unable to access|connection.*(timed out|reset|refused)|network is unreachable|(?:http|error:)\s*50[0-4]/.test(raw)) reason = "network";
17154
+ else if (detail?.code === "INVALID_ARGUMENT" || /upstream branch .* does not match|no upstream branch|push\.default|context|account changed/.test(raw)) reason = "configuration";
17155
+ return { status: "rejected", reason, message: explanation + message };
16160
17156
  }
16161
17157
  function makeDiffResult(path2, buffer) {
16162
17158
  const patch = buffer.toString("utf8");
@@ -16168,7 +17164,6 @@ const MAX_REPOSITORY_PROJECTS = 50;
16168
17164
  const MAX_PROJECT_NAME_LENGTH = 60;
16169
17165
  const MAX_REPOSITORIES_PER_PROJECT = 200;
16170
17166
  const MAX_REPOSITORY_KEY_LENGTH = 2048;
16171
- const UNASSIGNED_RECENT_LIMIT = 10;
16172
17167
  function normalizeRepositoryKey(commonDir) {
16173
17168
  return commonDir.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
16174
17169
  }
@@ -16196,8 +17191,23 @@ class RepositoryService {
16196
17191
  return this.openPath(recent.path);
16197
17192
  }
16198
17193
  async openPath(selectedPath) {
17194
+ const repository = await this.preparePath(selectedPath);
17195
+ await this.settings.touchRepository(repository);
17196
+ return repository;
17197
+ }
17198
+ /** Inspect/cache only: no recent entry, active repository, or persistence. */
17199
+ async preparePath(selectedPath) {
16199
17200
  const repository = await this.inspectPath(selectedPath);
16200
17201
  this.repositories.set(repository.id, repository);
17202
+ return repository;
17203
+ }
17204
+ async completeSetup(id, validate2 = () => void 0) {
17205
+ const prepared = this.get(id);
17206
+ const repository = await this.inspectPath(prepared.path);
17207
+ if (repository.id !== prepared.id || repository.commonDir !== prepared.commonDir) {
17208
+ throw new Error("The repository location changed. Select the folder again.");
17209
+ }
17210
+ validate2();
16201
17211
  await this.settings.touchRepository(repository);
16202
17212
  return repository;
16203
17213
  }
@@ -16692,6 +17702,331 @@ function revision(bytes) {
16692
17702
  function invalidReplacement(message) {
16693
17703
  return new GitOperationError({ code: "INVALID_ARGUMENT", operation: "replace-search", message });
16694
17704
  }
17705
+ const githubAccountSelectionSchema = discriminatedUnion("mode", [
17706
+ object({ mode: literal("auto") }),
17707
+ object({ mode: literal("account"), host: literal("github.com"), login: string().regex(/^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/), gitMode: _enum(["external", "managed"]).optional(), useGlobalDefault: boolean().optional() })
17708
+ ]);
17709
+ const AUTOMATIC_GITHUB_ACCOUNT = { mode: "auto" };
17710
+ function githubRepositoryKey(commonDir, platform2 = process.platform) {
17711
+ const key = commonDir.replace(/\\/g, "/").replace(/\/+$/, "");
17712
+ return platform2 === "win32" ? key.toLowerCase() : key;
17713
+ }
17714
+ const CACHE_MS = 6e4;
17715
+ const emptyStatus = () => ({
17716
+ installationStatus: "unchecked",
17717
+ accounts: [],
17718
+ activeLogin: null,
17719
+ environment: { present: false, login: null, state: "unknown" },
17720
+ checkedAt: null
17721
+ });
17722
+ class GitHubAccountsService {
17723
+ constructor(resolver, runner, git, settings, store, changed = () => void 0) {
17724
+ this.resolver = resolver;
17725
+ this.runner = runner;
17726
+ this.git = git;
17727
+ this.settings = settings;
17728
+ this.store = store;
17729
+ this.changed = changed;
17730
+ }
17731
+ resolver;
17732
+ runner;
17733
+ git;
17734
+ settings;
17735
+ store;
17736
+ changed;
17737
+ inventory = null;
17738
+ loading = null;
17739
+ checking = null;
17740
+ detection = null;
17741
+ revision = 0;
17742
+ get authRevision() {
17743
+ return this.revision;
17744
+ }
17745
+ revisions = /* @__PURE__ */ new Map();
17746
+ defaultRevision = 0;
17747
+ revisionFor(repository) {
17748
+ const selection = this.selection(repository);
17749
+ return Math.max(
17750
+ this.revisions.get(githubRepositoryKey(repository.commonDir)) ?? 0,
17751
+ selection.mode === "account" && selection.useGlobalDefault ? this.defaultRevision : 0
17752
+ );
17753
+ }
17754
+ contexts = /* @__PURE__ */ new Map();
17755
+ ssh = /* @__PURE__ */ new Map();
17756
+ identities = /* @__PURE__ */ new Map();
17757
+ status(force = false) {
17758
+ if (force) {
17759
+ this.checking ??= this.check().finally(() => {
17760
+ this.checking = null;
17761
+ });
17762
+ return this.checking.then((value) => ({ ...structuredClone(value), defaultLogin: this.settings?.githubDefaultLogin ?? null }));
17763
+ }
17764
+ this.loading ??= (async () => {
17765
+ this.inventory ??= await this.store?.load() ?? emptyStatus();
17766
+ return this.inventory;
17767
+ })();
17768
+ return this.loading.then(() => ({ ...structuredClone(this.inventory ?? emptyStatus()), defaultLogin: this.settings?.githubDefaultLogin ?? null }));
17769
+ }
17770
+ selection(repository) {
17771
+ return this.settings?.githubAccount(repository.commonDir) ?? { ...AUTOMATIC_GITHUB_ACCOUNT };
17772
+ }
17773
+ context(repository, remote, nameWithOwner) {
17774
+ const cached2 = this.contexts.get(repository.id);
17775
+ if (cached2?.remote === remote && cached2.value.revision === this.revisionFor(repository)) return structuredClone(cached2.value);
17776
+ const selection = this.selection(repository);
17777
+ return {
17778
+ selection,
17779
+ nameWithOwner,
17780
+ login: selection.mode === "account" ? selection.login : null,
17781
+ source: selection.mode === "account" ? "explicit" : null,
17782
+ state: "unchecked",
17783
+ checkedAt: null,
17784
+ revision: this.revisionFor(repository)
17785
+ };
17786
+ }
17787
+ async setSelection(repository, selection) {
17788
+ if (!this.settings) throw new Error("GitHub account settings are unavailable.");
17789
+ const saving = this.settings.setGitHubAccount(repository.commonDir, selection);
17790
+ this.invalidate(repository);
17791
+ try {
17792
+ await saving;
17793
+ } catch (error) {
17794
+ this.invalidate(repository);
17795
+ throw error;
17796
+ }
17797
+ }
17798
+ async setDefaultAccount(login2) {
17799
+ if (!this.settings) throw new Error("GitHub account settings are unavailable.");
17800
+ this.defaultRevision = ++this.revision;
17801
+ try {
17802
+ await this.settings.setGitHubDefaultLogin(login2);
17803
+ } finally {
17804
+ this.changed(void 0, true);
17805
+ }
17806
+ return this.status();
17807
+ }
17808
+ invalidate(repository) {
17809
+ this.revision++;
17810
+ if (repository) {
17811
+ const key = githubRepositoryKey(repository.commonDir);
17812
+ this.revisions.set(key, this.revision);
17813
+ } else {
17814
+ this.contexts.clear();
17815
+ }
17816
+ this.ssh.clear();
17817
+ this.identities.clear();
17818
+ this.changed(repository);
17819
+ }
17820
+ refreshAuthentication() {
17821
+ this.ssh.clear();
17822
+ this.identities.clear();
17823
+ }
17824
+ async detect(force = false) {
17825
+ if (force || !this.detection || Date.now() - this.detection.at > CACHE_MS) {
17826
+ this.detection = { at: Date.now(), promise: selectCli(this.resolver, this.runner, "gh", {
17827
+ forceRefresh: force,
17828
+ runOptions: { cwd: os.homedir(), env: { ...GH_ENV }, removeEnv: ["GH_DEBUG", "DEBUG"] }
17829
+ }) };
17830
+ }
17831
+ return this.detection.promise;
17832
+ }
17833
+ async authenticate(repository, remote, nameWithOwner, operation) {
17834
+ const revision2 = this.revisionFor(repository);
17835
+ const selection = this.selection(repository);
17836
+ let login2 = selection.mode === "account" ? selection.login : null;
17837
+ let source = selection.mode === "account" ? "explicit" : null;
17838
+ try {
17839
+ const detection = await this.detect();
17840
+ if (detection.state !== "available" || !detection.candidate) throw new GhOperationError({
17841
+ code: detection.state === "not-found" ? "GH_CLI_NOT_FOUND" : "GH_PROCESS_FAILED",
17842
+ operation,
17843
+ message: detection.state === "not-found" ? "GitHub CLI is not installed on the backend host." : "GitHub CLI could not run. Check Settings → GitHub."
17844
+ });
17845
+ const executable = detection.candidate;
17846
+ if (selection.mode === "auto" && parseSshRemote(remote)) {
17847
+ const override = executable.env.GIT_SSH_COMMAND || executable.env.GIT_SSH || process.env.GIT_SSH_COMMAND || process.env.GIT_SSH;
17848
+ const configured = await this.git.run(repository.path, ["config", "--default", "", "--get", "core.sshCommand"], {
17849
+ operation: "github-ssh-configuration",
17850
+ readOnly: true,
17851
+ maxOutputBytes: 8 * 1024
17852
+ });
17853
+ if (override || configured.stdout.toString("utf8").trim()) throw new GhOperationError({
17854
+ code: "GH_ACCOUNT_UNRESOLVED",
17855
+ operation,
17856
+ message: "This repository uses a custom SSH command. Choose an explicit account in Settings → GitHub."
17857
+ });
17858
+ const key = `${repository.path}\0${remote}`;
17859
+ let cached2 = this.ssh.get(key);
17860
+ if (!cached2 || Date.now() - cached2.at > CACHE_MS) {
17861
+ cached2 = { at: Date.now(), promise: identifySshAccount(remote, this.runner) };
17862
+ this.ssh.set(key, cached2);
17863
+ }
17864
+ login2 = await cached2.promise;
17865
+ source = "ssh";
17866
+ if (!login2) throw new GhOperationError({
17867
+ code: "GH_ACCOUNT_UNRESOLVED",
17868
+ operation,
17869
+ message: "SSH could not verify a GitHub user account. Check again or choose an explicit account in Settings → GitHub. Deploy keys do not identify user accounts."
17870
+ });
17871
+ }
17872
+ if (!source) source = executable.env.GH_TOKEN || executable.env.GITHUB_TOKEN || process.env.GH_TOKEN || process.env.GITHUB_TOKEN ? "environment" : "global";
17873
+ const auth = await accountAuthentication(login2, executable, this.runner, operation);
17874
+ const verified = await this.identity(executable, auth, operation);
17875
+ if (login2 && login2.toLowerCase() !== verified.toLowerCase()) throw new GhOperationError({
17876
+ code: "GH_AUTH_REQUIRED",
17877
+ operation,
17878
+ message: `The saved credentials for @${login2} authenticate a different account. Authenticate @${login2} again and check Settings → GitHub.`
17879
+ });
17880
+ login2 = verified;
17881
+ const context = { selection, nameWithOwner, login: login2, source, state: "ready", checkedAt: (/* @__PURE__ */ new Date()).toISOString(), revision: revision2 };
17882
+ if (revision2 === this.revisionFor(repository)) {
17883
+ const previous = this.contexts.get(repository.id)?.value;
17884
+ if (previous?.state === "ready" && previous.revision === revision2 && (previous.login?.toLowerCase() !== login2.toLowerCase() || previous.source !== source)) {
17885
+ this.invalidate(repository);
17886
+ context.revision = this.revisionFor(repository);
17887
+ }
17888
+ this.contexts.set(repository.id, { remote, value: context });
17889
+ }
17890
+ return { auth, executable, context };
17891
+ } catch (error) {
17892
+ const message = error instanceof GhOperationError ? error.message : "Could not inspect GitHub authentication. Check Settings → GitHub again.";
17893
+ if (revision2 === this.revisionFor(repository)) this.contexts.set(repository.id, { remote, value: {
17894
+ selection,
17895
+ nameWithOwner,
17896
+ login: login2,
17897
+ source,
17898
+ state: "error",
17899
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
17900
+ message,
17901
+ revision: revision2
17902
+ } });
17903
+ if (error instanceof GhOperationError) throw error;
17904
+ throw new GhOperationError({ code: "GH_PROCESS_FAILED", operation, message, retryable: true });
17905
+ }
17906
+ }
17907
+ identity(executable, auth, operation) {
17908
+ const fingerprint = createHash("sha256").update(auth.env?.GH_TOKEN ?? "").digest("hex");
17909
+ let cached2 = this.identities.get(fingerprint);
17910
+ if (!cached2 || Date.now() - cached2.at > CACHE_MS) {
17911
+ const promise = (async () => {
17912
+ const result = await this.runner.run(executable.executable, ["api", "user", "--jq", ".login"], {
17913
+ cwd: os.homedir(),
17914
+ env: { ...executable.env, ...GH_ENV, ...auth.env },
17915
+ ...auth.removeEnv ? { removeEnv: auth.removeEnv } : {},
17916
+ timeoutMs: 15e3,
17917
+ maxOutputBytes: 8 * 1024
17918
+ });
17919
+ const login2 = result.stdout.trim();
17920
+ if (result.exitCode === 0 && GITHUB_LOGIN.test(login2)) return login2;
17921
+ const invalid2 = /401|bad credentials|requires authentication/i.test(result.stderr);
17922
+ throw new GhOperationError({
17923
+ code: invalid2 ? "GH_AUTH_REQUIRED" : "GH_PROCESS_FAILED",
17924
+ operation,
17925
+ message: invalid2 ? "GitHub rejected these credentials. They may be expired or revoked. Authenticate this account again and check Settings → GitHub." : "Could not verify this GitHub account. Check your connection and try again.",
17926
+ retryable: !invalid2
17927
+ });
17928
+ })();
17929
+ cached2 = { at: Date.now(), promise };
17930
+ this.identities.set(fingerprint, cached2);
17931
+ void promise.catch(() => {
17932
+ if (this.identities.get(fingerprint)?.promise === promise) this.identities.delete(fingerprint);
17933
+ });
17934
+ }
17935
+ return cached2.promise;
17936
+ }
17937
+ async check() {
17938
+ await this.status();
17939
+ this.refreshAuthentication();
17940
+ const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
17941
+ const detection = await this.detect(true);
17942
+ const result = {
17943
+ ...emptyStatus(),
17944
+ installationStatus: detection.state,
17945
+ checkedAt,
17946
+ ...detection.version ? { version: detection.version } : {}
17947
+ };
17948
+ if (detection.state === "available" && detection.candidate) {
17949
+ const executable = detection.candidate;
17950
+ try {
17951
+ const status = await this.runner.run(executable.executable, ["auth", "status", "--hostname", "github.com", "--json", "hosts"], {
17952
+ cwd: os.homedir(),
17953
+ env: { ...executable.env, ...GH_ENV },
17954
+ removeEnv: ["GH_TOKEN", ...GH_PRIVATE_ENV],
17955
+ timeoutMs: 6e4,
17956
+ maxOutputBytes: 256 * 1024
17957
+ });
17958
+ if (status.exitCode !== 0) {
17959
+ if (/unknown flag.*json/i.test(status.stderr)) result.installationStatus = "incompatible";
17960
+ throw new Error("Status failed");
17961
+ }
17962
+ Object.assign(result, parseGitHubAuthStatus(status.stdout));
17963
+ const envToken = executable.env.GH_TOKEN || executable.env.GITHUB_TOKEN || process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
17964
+ if (envToken) {
17965
+ result.environment.present = true;
17966
+ try {
17967
+ const auth = await accountAuthentication(null, executable, this.runner, "gh-auth-status");
17968
+ result.environment.login = await this.identity(executable, auth, "gh-auth-status");
17969
+ result.environment.state = "authenticated";
17970
+ } catch (error) {
17971
+ result.environment.state = error instanceof GhOperationError && error.detail.code === "GH_AUTH_REQUIRED" ? "invalid" : "unknown";
17972
+ }
17973
+ }
17974
+ } catch {
17975
+ result.message = result.installationStatus === "incompatible" ? "Update GitHub CLI to a version supporting auth status --json and multiple accounts." : "Could not check the saved GitHub accounts. Check your connection and CLI configuration, then try again.";
17976
+ result.accounts = this.inventory?.accounts.map((account) => ({ ...account, state: "unknown" })) ?? [];
17977
+ result.activeLogin = this.inventory?.activeLogin ?? null;
17978
+ }
17979
+ } else result.message = detection.state === "not-found" ? "Install GitHub CLI on the backend host." : "GitHub CLI was found but could not run. Check its installation and dependencies.";
17980
+ this.inventory = result;
17981
+ try {
17982
+ await this.store?.save(result);
17983
+ } finally {
17984
+ this.invalidate();
17985
+ }
17986
+ return result;
17987
+ }
17988
+ }
17989
+ const authState = _enum(["authenticated", "invalid", "unknown"]);
17990
+ const login = string().regex(/^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/);
17991
+ const schema = object({
17992
+ installationStatus: _enum(["unchecked", "available", "not-found", "not-executable", "incompatible", "inspection-failed"]),
17993
+ version: string().max(200).optional(),
17994
+ accounts: array(object({
17995
+ host: literal("github.com"),
17996
+ login,
17997
+ active: boolean(),
17998
+ state: authState,
17999
+ storage: _enum(["keyring", "file", "unknown"]),
18000
+ scopes: string().max(500).optional()
18001
+ })).max(100),
18002
+ activeLogin: login.nullable(),
18003
+ environment: object({ present: boolean(), login: login.nullable(), state: authState }),
18004
+ checkedAt: string().datetime().nullable(),
18005
+ message: string().max(500).optional()
18006
+ });
18007
+ class GitHubStatusStore {
18008
+ constructor(filePath) {
18009
+ this.filePath = filePath;
18010
+ }
18011
+ filePath;
18012
+ pending = Promise.resolve();
18013
+ async load() {
18014
+ await this.pending;
18015
+ try {
18016
+ if ((await stat(this.filePath)).size > 256 * 1024) return null;
18017
+ const parsed = schema.safeParse(JSON.parse(await readFile(this.filePath, "utf8")));
18018
+ return parsed.success ? parsed.data : null;
18019
+ } catch {
18020
+ return null;
18021
+ }
18022
+ }
18023
+ save(value) {
18024
+ const snapshot = JSON.stringify(schema.parse(value));
18025
+ const write = this.pending.then(() => writeFileAtomically(this.filePath, snapshot, { parseJson: true }));
18026
+ this.pending = write.catch(() => void 0);
18027
+ return write;
18028
+ }
18029
+ }
16695
18030
  const positive = number().int().positive().safe();
16696
18031
  const membershipSchema = object({
16697
18032
  stack: object({ number: positive, size: positive, baseRefName: string().min(1) }).nullable(),
@@ -16759,28 +18094,6 @@ function parsePullRequestStack(raw, prNumber, listing = false) {
16759
18094
  }))
16760
18095
  };
16761
18096
  }
16762
- const OWNER_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?$/;
16763
- const REPO_PATTERN = /^[A-Za-z0-9._-]+$/;
16764
- function parseGitHubRemote(url) {
16765
- const trimmed = url.trim();
16766
- if (!trimmed) return null;
16767
- let rest = null;
16768
- const https = /^https?:\/\/(?:[^@/\s]+@)?github\.com\/(.+)$/i.exec(trimmed);
16769
- const ssh = /^ssh:\/\/(?:[^@/\s]+@)?github\.com(?::\d+)?\/(.+)$/i.exec(trimmed);
16770
- const scp = /^(?:[^@\s]+)@github\.com:(.+)$/i.exec(trimmed);
16771
- const git = /^git:\/\/github\.com\/(.+)$/i.exec(trimmed);
16772
- if (https) rest = https[1] ?? null;
16773
- else if (ssh) rest = ssh[1] ?? null;
16774
- else if (scp) rest = scp[1] ?? null;
16775
- else if (git) rest = git[1] ?? null;
16776
- if (!rest) return null;
16777
- const segments = rest.replace(/\/+$/, "").split("/");
16778
- if (segments.length !== 2) return null;
16779
- const owner = segments[0] ?? "";
16780
- const repo = (segments[1] ?? "").replace(/\.git$/i, "");
16781
- if (!OWNER_PATTERN.test(owner) || !repo || repo === "." || repo === ".." || !REPO_PATTERN.test(repo)) return null;
16782
- return { nameWithOwner: `${owner}/${repo}` };
16783
- }
16784
18097
  const PR_SUMMARY_FIELDS = "number,title,state,isDraft,author,headRefName,baseRefName,updatedAt,url,reviewDecision,additions,deletions,statusCheckRollup";
16785
18098
  const PR_DETAIL_FIELDS = `${PR_SUMMARY_FIELDS},body,changedFiles,labels,commits`;
16786
18099
  const tolerantRecordSchema = looseObject({});
@@ -16795,6 +18108,15 @@ function parsePullRequestList(raw) {
16795
18108
  if (!list.success) throw invalidOutput("gh-pr-list");
16796
18109
  return list.data.map((item) => parseSummary(item, "gh-pr-list"));
16797
18110
  }
18111
+ function parseBranchPullRequestList(raw, nameWithOwner, branch) {
18112
+ const list = pullRequestArraySchema.safeParse(parseJson(raw, "gh-pr-for-branch"));
18113
+ if (!list.success) throw invalidOutput("gh-pr-for-branch");
18114
+ const headSchema = looseObject({ headRefName: string(), headRepository: looseObject({ nameWithOwner: string() }) });
18115
+ return list.data.filter((item) => {
18116
+ const head = headSchema.safeParse(item);
18117
+ return head.success && head.data.headRefName === branch && head.data.headRepository.nameWithOwner.toLowerCase() === nameWithOwner.toLowerCase();
18118
+ }).map((item) => parseSummary(item, "gh-pr-for-branch"));
18119
+ }
16798
18120
  function parsePullRequestDetails(raw) {
16799
18121
  const item = parseJson(raw, "gh-pr-view");
16800
18122
  const summary = parseSummary(item, "gh-pr-view");
@@ -16876,14 +18198,14 @@ function parseCommits(value) {
16876
18198
  if (typeof record2.oid !== "string" || !record2.oid) return [];
16877
18199
  const authors = Array.isArray(record2.authors) ? record2.authors : [];
16878
18200
  const authorRecord = authors.map((author) => tolerantRecordSchema.safeParse(author)).find((author) => author.success)?.data;
16879
- const login = typeof authorRecord?.login === "string" ? authorRecord.login : null;
16880
- const name = typeof authorRecord?.name === "string" && authorRecord.name ? authorRecord.name : login ?? "unknown";
18201
+ const login2 = typeof authorRecord?.login === "string" ? authorRecord.login : null;
18202
+ const name = typeof authorRecord?.name === "string" && authorRecord.name ? authorRecord.name : login2 ?? "unknown";
16881
18203
  return [{
16882
18204
  oid: record2.oid,
16883
18205
  messageHeadline: typeof record2.messageHeadline === "string" ? record2.messageHeadline : "",
16884
18206
  authoredAt: typeof record2.authoredDate === "string" ? record2.authoredDate : "",
16885
18207
  author: name,
16886
- authorAvatarUrl: login ? avatarUrl(login, login.endsWith("[bot]")) : null
18208
+ authorAvatarUrl: login2 ? avatarUrl(login2, login2.endsWith("[bot]")) : null
16887
18209
  }];
16888
18210
  });
16889
18211
  }
@@ -16902,11 +18224,11 @@ function parseChecksState(value) {
16902
18224
  }
16903
18225
  return pending ? "PENDING" : "PASSING";
16904
18226
  }
16905
- function avatarUrl(login, isBot) {
16906
- if (!login || login === "unknown") return null;
16907
- if (login === "app/dependabot" || login === "dependabot[bot]") return "https://avatars.githubusercontent.com/in/29110?s=40&v=4";
16908
- const botName = login.startsWith("app/") ? login.slice(4) : login.replace(/\[bot\]$/i, "");
16909
- const path2 = isBot || login.startsWith("app/") ? botName : login;
18227
+ function avatarUrl(login2, isBot) {
18228
+ if (!login2 || login2 === "unknown") return null;
18229
+ if (login2 === "app/dependabot" || login2 === "dependabot[bot]") return "https://avatars.githubusercontent.com/in/29110?s=40&v=4";
18230
+ const botName = login2.startsWith("app/") ? login2.slice(4) : login2.replace(/\[bot\]$/i, "");
18231
+ const path2 = isBot || login2.startsWith("app/") ? botName : login2;
16910
18232
  return `https://github.com/${encodeURIComponent(path2)}.png?size=40`;
16911
18233
  }
16912
18234
  function parseJson(raw, operation) {
@@ -16926,70 +18248,113 @@ function validTimestamp(value) {
16926
18248
  function invalidOutput(operation) {
16927
18249
  return new GhOperationError({ code: "GH_INVALID_OUTPUT", operation, message: "GitHub CLI returned an unexpected response." });
16928
18250
  }
16929
- const GH_ENV = { GH_PROMPT_DISABLED: "1", GH_NO_UPDATE_NOTIFIER: "1", GH_PAGER: "cat", NO_COLOR: "1" };
16930
18251
  const NETWORK_TIMEOUT_MS = 6e4;
16931
18252
  class GitHubService {
16932
- constructor(resolver, runner, git, repositories) {
16933
- this.resolver = resolver;
18253
+ constructor(resolver, runner, git, repositories, accounts) {
16934
18254
  this.runner = runner;
16935
18255
  this.git = git;
16936
18256
  this.repositories = repositories;
18257
+ this.accounts = accounts ?? new GitHubAccountsService(resolver, runner, git);
16937
18258
  }
16938
- resolver;
16939
18259
  runner;
16940
18260
  git;
16941
18261
  repositories;
16942
- statusCache = null;
18262
+ gitAccess;
18263
+ accounts;
16943
18264
  async status(forceRefresh = false) {
16944
- if (!forceRefresh && this.statusCache && Date.now() - this.statusCache.at < 3e4) return this.statusCache.value;
16945
- const value = await this.checkStatus(forceRefresh);
16946
- this.statusCache = { at: Date.now(), value };
16947
- return value;
18265
+ if (forceRefresh) this.accounts.refreshAuthentication();
18266
+ const detected = await this.accounts.detect(forceRefresh);
18267
+ return {
18268
+ installed: !!detected.candidate,
18269
+ availability: detected.state === "available" ? "ready" : "error",
18270
+ authStatus: "unknown",
18271
+ ...detected.version ? { version: detected.version } : {},
18272
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString()
18273
+ };
16948
18274
  }
16949
- async repositoryInfo(repositoryId) {
18275
+ async repositoryAccount(repositoryId, forceRefresh = false) {
18276
+ const repository = this.repositories.get(repositoryId);
18277
+ const info = await this.repositoryInfo(repositoryId);
18278
+ const remote = await this.remote(repositoryId);
18279
+ if (forceRefresh && info.nameWithOwner && !this.gitAccess) {
18280
+ this.accounts.refreshAuthentication();
18281
+ try {
18282
+ await this.accounts.authenticate(repository, remote, info.nameWithOwner, "gh-repository-account");
18283
+ } catch {
18284
+ }
18285
+ }
18286
+ const access2 = await this.gitAccess?.status(repositoryId, forceRefresh, { remote, nameWithOwner: info.nameWithOwner });
18287
+ return { ...this.accounts.context(repository, remote, info.nameWithOwner), ...access2 ? { access: access2 } : {} };
18288
+ }
18289
+ async setRepositoryAccount(repositoryId, selection, expectedRevision) {
18290
+ if (expectedRevision !== void 0 && expectedRevision !== this.accounts.revisionFor(this.repositories.get(repositoryId))) throw new Error("The repository account changed in another session. Reopen the account setup and review it.");
18291
+ await this.accounts.setSelection(this.repositories.get(repositoryId), selection);
18292
+ return this.repositoryAccount(repositoryId);
18293
+ }
18294
+ async remote(repositoryId) {
16950
18295
  const repository = this.repositories.get(repositoryId);
16951
18296
  try {
16952
- const output = await this.git.run(repository.path, ["config", "--get", "remote.origin.url"], { operation: "github-remote-url", readOnly: true, maxOutputBytes: 64 * 1024 });
16953
- const parsed = parseGitHubRemote(output.stdout.toString("utf8"));
18297
+ const output = await this.git.run(repository.path, ["remote", "get-url", "--", "origin"], { operation: "github-remote-url", readOnly: true, maxOutputBytes: 64 * 1024 });
18298
+ return output.stdout.toString("utf8").trim();
18299
+ } catch {
18300
+ return "";
18301
+ }
18302
+ }
18303
+ async repositoryInfo(repositoryId) {
18304
+ try {
18305
+ const url = await this.remote(repositoryId);
18306
+ let parsed = parseGitHubRemote(url);
18307
+ if (!parsed) {
18308
+ const ssh = parseSshRemote(url);
18309
+ if (ssh) {
18310
+ const config2 = await this.runner.run("ssh", ["-G", ...ssh.port ? ["-p", ssh.port] : [], "--", ssh.destination], {
18311
+ cwd: os.homedir(),
18312
+ timeoutMs: 5e3,
18313
+ maxOutputBytes: 64 * 1024
18314
+ });
18315
+ const hostname = config2.stdout.split(/\r?\n/).find((line) => /^hostname\s/i.test(line))?.trim().split(/\s+/)[1];
18316
+ if (config2.exitCode === 0 && hostname?.toLowerCase() === "github.com") parsed = { nameWithOwner: ssh.nameWithOwner };
18317
+ }
18318
+ }
16954
18319
  return parsed ? { isGitHub: true, nameWithOwner: parsed.nameWithOwner } : { isGitHub: false, nameWithOwner: null };
16955
18320
  } catch {
16956
18321
  return { isGitHub: false, nameWithOwner: null };
16957
18322
  }
16958
18323
  }
16959
18324
  async listPullRequests(repositoryId, states) {
16960
- const { repository, nameWithOwner } = await this.requireGitHub(repositoryId, "gh-pr-list");
18325
+ const { repository, nameWithOwner, auth, executable } = await this.requireGitHub(repositoryId, "gh-pr-list");
16961
18326
  const results = await Promise.all(states.map((state) => this.runGh(
16962
18327
  ["pr", "list", "-R", nameWithOwner, "--state", state.toLowerCase(), "--json", PR_SUMMARY_FIELDS, "--limit", "50"],
16963
- { cwd: repository.path, operation: "gh-pr-list", timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 4 * 1024 * 1024 }
18328
+ { auth, executable, cwd: repository.path, operation: "gh-pr-list", timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 4 * 1024 * 1024 }
16964
18329
  )));
16965
- return this.withStackMembership(repository.path, nameWithOwner, selectPullRequestsNewestFirst(results.flatMap((result) => parsePullRequestList(result.stdout)), states));
18330
+ return this.withStackMembership(repository.path, nameWithOwner, auth, executable, selectPullRequestsNewestFirst(results.flatMap((result) => parsePullRequestList(result.stdout)), states));
16966
18331
  }
16967
18332
  async findPullRequestForBranch(repositoryId, branchName) {
16968
18333
  const operation = "gh-pr-for-branch";
16969
- const { repository, nameWithOwner } = await this.requireGitHub(repositoryId, operation);
18334
+ const { repository, nameWithOwner, auth, executable } = await this.requireGitHub(repositoryId, operation);
16970
18335
  const branch = validateRef(branchName, operation);
16971
18336
  const openResult = await this.runGh(
16972
- ["pr", "list", "-R", nameWithOwner, "--head", branch, "--state", "open", "--json", PR_SUMMARY_FIELDS, "--limit", "10"],
16973
- { cwd: repository.path, operation, timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 1024 * 1024 }
18337
+ ["pr", "list", "-R", nameWithOwner, "--head", branch, "--state", "open", "--json", `${PR_SUMMARY_FIELDS},headRepository`, "--limit", "10"],
18338
+ { auth, executable, cwd: repository.path, operation, timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 1024 * 1024 }
16974
18339
  );
16975
- const openPull = sortPullRequestsNewestFirst(parsePullRequestList(openResult.stdout)).find((pull) => pull.state === "OPEN");
18340
+ const openPull = sortPullRequestsNewestFirst(parseBranchPullRequestList(openResult.stdout, nameWithOwner, branch)).find((pull) => pull.state === "OPEN");
16976
18341
  if (openPull) return openPull;
16977
18342
  const result = await this.runGh(
16978
- ["pr", "list", "-R", nameWithOwner, "--head", branch, "--state", "all", "--json", PR_SUMMARY_FIELDS, "--limit", "10"],
16979
- { cwd: repository.path, operation, timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 1024 * 1024 }
18343
+ ["pr", "list", "-R", nameWithOwner, "--head", branch, "--state", "all", "--json", `${PR_SUMMARY_FIELDS},headRepository`, "--limit", "10"],
18344
+ { auth, executable, cwd: repository.path, operation, timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 1024 * 1024 }
16980
18345
  );
16981
- return sortPullRequestsNewestFirst(parsePullRequestList(result.stdout))[0] ?? null;
18346
+ return sortPullRequestsNewestFirst(parseBranchPullRequestList(result.stdout, nameWithOwner, branch))[0] ?? null;
16982
18347
  }
16983
18348
  async getPullRequest(repositoryId, prNumber) {
16984
- const { repository, nameWithOwner } = await this.requireGitHub(repositoryId, "gh-pr-view");
18349
+ const { repository, nameWithOwner, auth, executable } = await this.requireGitHub(repositoryId, "gh-pr-view");
16985
18350
  const result = await this.runGh(
16986
18351
  ["pr", "view", String(prNumber), "-R", nameWithOwner, "--json", PR_DETAIL_FIELDS],
16987
- { cwd: repository.path, operation: "gh-pr-view", timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 4 * 1024 * 1024 }
18352
+ { auth, executable, cwd: repository.path, operation: "gh-pr-view", timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 4 * 1024 * 1024 }
16988
18353
  );
16989
18354
  const details = parsePullRequestDetails(result.stdout);
16990
- return (await this.withStackMembership(repository.path, nameWithOwner, [details]))[0];
18355
+ return (await this.withStackMembership(repository.path, nameWithOwner, auth, executable, [details]))[0];
16991
18356
  }
16992
- async withStackMembership(cwd, nameWithOwner, pulls) {
18357
+ async withStackMembership(cwd, nameWithOwner, auth, executable, pulls) {
16993
18358
  const [owner, name] = nameWithOwner.split("/");
16994
18359
  const enriched = [];
16995
18360
  for (let start = 0; start < pulls.length; start += 25) {
@@ -16997,6 +18362,8 @@ class GitHubService {
16997
18362
  try {
16998
18363
  const numbers = batch.map((pr) => pr.number);
16999
18364
  const result = await this.runGh(["api", "graphql", "-f", `owner=${owner}`, "-f", `name=${name}`, "-f", `query=${stackMembershipQuery(numbers)}`], {
18365
+ auth,
18366
+ executable,
17000
18367
  cwd,
17001
18368
  operation: "gh-pr-stack-membership",
17002
18369
  timeoutMs: 1e4,
@@ -17015,28 +18382,28 @@ class GitHubService {
17015
18382
  }
17016
18383
  async getPullRequestStack(repositoryId, prNumber) {
17017
18384
  const operation = "gh-pr-stack";
17018
- const { repository, nameWithOwner } = await this.requireGitHub(repositoryId, operation);
17019
- const options = { cwd: repository.path, operation, timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 4 * 1024 * 1024 };
17020
- const read = async (endpoint) => this.runGh(["api", endpoint], options);
18385
+ const { repository, nameWithOwner, auth, executable } = await this.requireGitHub(repositoryId, operation);
18386
+ const options = { auth, executable, cwd: repository.path, operation, timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 4 * 1024 * 1024 };
18387
+ const read2 = async (endpoint) => this.runGh(["api", endpoint], options);
17021
18388
  let listing;
17022
18389
  try {
17023
- listing = await read(`repos/${nameWithOwner}/stacks?pull_request=${prNumber}`);
18390
+ listing = await read2(`repos/${nameWithOwner}/stacks?pull_request=${prNumber}`);
17024
18391
  } catch (error) {
17025
18392
  if (error instanceof GhOperationError && error.detail.code === "GH_PROCESS_FAILED" && /HTTP 404/.test(error.message)) return null;
17026
18393
  throw error;
17027
18394
  }
17028
18395
  const stack = parsePullRequestStack(listing.stdout, prNumber, true);
17029
18396
  if (!stack) return null;
17030
- const details = await read(`repos/${nameWithOwner}/stacks/${stack.number}`);
18397
+ const details = await read2(`repos/${nameWithOwner}/stacks/${stack.number}`);
17031
18398
  const complete = parsePullRequestStack(details.stdout, prNumber);
17032
18399
  if (complete?.number !== stack.number) throw new GhOperationError({ code: "GH_INVALID_OUTPUT", operation, message: "The stack changed while loading. Refresh to try again." });
17033
18400
  return complete;
17034
18401
  }
17035
18402
  async getPullRequestDiff(repositoryId, prNumber) {
17036
- const { repository, nameWithOwner } = await this.requireGitHub(repositoryId, "gh-pr-diff");
18403
+ const { repository, nameWithOwner, auth, executable } = await this.requireGitHub(repositoryId, "gh-pr-diff");
17037
18404
  const result = await this.runGh(
17038
18405
  ["pr", "diff", String(prNumber), "-R", nameWithOwner],
17039
- { cwd: repository.path, operation: "gh-pr-diff", timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 32 * 1024 * 1024 }
18406
+ { auth, executable, cwd: repository.path, operation: "gh-pr-diff", timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 32 * 1024 * 1024 }
17040
18407
  );
17041
18408
  const patch = result.stdout;
17042
18409
  const binary = /Binary files .* differ|GIT binary patch/.test(patch);
@@ -17044,13 +18411,13 @@ class GitHubService {
17044
18411
  }
17045
18412
  async getPullRequestCommitDiff(repositoryId, oid) {
17046
18413
  const operation = "gh-pr-commit-diff";
17047
- const { repository, nameWithOwner } = await this.requireGitHub(repositoryId, operation);
18414
+ const { repository, nameWithOwner, auth, executable } = await this.requireGitHub(repositoryId, operation);
17048
18415
  if (!/^[0-9a-f]{40,64}$/i.test(oid)) {
17049
18416
  throw new GhOperationError({ code: "GH_PROCESS_FAILED", operation, message: "The commit is not valid." });
17050
18417
  }
17051
18418
  const result = await this.runGh(
17052
18419
  ["api", `repos/${nameWithOwner}/commits/${oid}`, "--header", "Accept: application/vnd.github.diff"],
17053
- { cwd: repository.path, operation, timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 32 * 1024 * 1024 }
18420
+ { auth, executable, cwd: repository.path, operation, timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 32 * 1024 * 1024 }
17054
18421
  );
17055
18422
  const patch = result.stdout;
17056
18423
  const binary = /Binary files .* differ|GIT binary patch/.test(patch);
@@ -17058,7 +18425,13 @@ class GitHubService {
17058
18425
  }
17059
18426
  async createPullRequest(input) {
17060
18427
  const operation = "gh-pr-create";
17061
- const { repository, nameWithOwner } = await this.requireGitHub(input.repositoryId, operation);
18428
+ const { repository, nameWithOwner, auth, executable, context } = await this.requireGitHub(input.repositoryId, operation);
18429
+ const verifyAccount = () => {
18430
+ if (input.expectedAccount && (input.expectedAccount.revision !== this.accounts.revisionFor(repository) || input.expectedAccount.revision !== context.revision || input.expectedAccount.login.toLowerCase() !== context.login?.toLowerCase())) {
18431
+ throw new GhOperationError({ code: "GH_ACCOUNT_UNRESOLVED", operation, message: "The GitHub account changed. Review the current identity before creating the pull request." });
18432
+ }
18433
+ };
18434
+ verifyAccount();
17062
18435
  const status = await this.repositories.status(input.repositoryId, false);
17063
18436
  if (status.detached || status.unborn || !status.branch) {
17064
18437
  throw new GhOperationError({ code: "GH_PROCESS_FAILED", operation, message: "Check out a branch before creating a pull request." });
@@ -17076,7 +18449,8 @@ class GitHubService {
17076
18449
  await writeFile(bodyPath, input.body, { encoding: "utf8", mode: 384 });
17077
18450
  const args = ["pr", "create", "-R", nameWithOwner, "--title", input.title, "--body-file", bodyPath, "--base", base, "--head", head];
17078
18451
  if (input.draft) args.push("--draft");
17079
- const result = await this.runGh(args, { cwd: repository.path, operation, timeoutMs: 12e4, maxOutputBytes: 1024 * 1024 });
18452
+ verifyAccount();
18453
+ const result = await this.runGh(args, { auth, executable, cwd: repository.path, operation, timeoutMs: 12e4, maxOutputBytes: 1024 * 1024 });
17080
18454
  const created = parseCreatedPullRequestUrl(result.stdout);
17081
18455
  if (!created) throw new GhOperationError({ code: "GH_INVALID_OUTPUT", operation, message: "GitHub CLI did not return the URL of the new pull request." });
17082
18456
  return created;
@@ -17084,51 +18458,32 @@ class GitHubService {
17084
18458
  await rm(temporary, { recursive: true, force: true });
17085
18459
  }
17086
18460
  }
17087
- async checkStatus(forceRefresh) {
17088
- const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
17089
- const detected = await selectCli(this.resolver, this.runner, "gh", { forceRefresh, runOptions: { cwd: os.tmpdir(), env: { ...GH_ENV } } });
17090
- const executable = detected.candidate;
17091
- if (!executable) {
17092
- return { installed: false, availability: "error", authStatus: "unknown", message: "Install GitHub CLI (cli.github.com) to work with pull requests.", checkedAt };
17093
- }
17094
- if (detected.state !== "available") return { installed: true, availability: "error", authStatus: "unknown", message: "GitHub CLI was found but could not run. Check its installation and dependencies.", checkedAt };
17095
- const versionLabel = detected.version ?? "";
17096
- const auth = await this.run(executable, ["auth", "status", "--hostname", "github.com", "--active"], { cwd: os.tmpdir(), operation: "gh-auth-status" });
17097
- if (auth.exitCode !== 0) {
17098
- return { installed: true, availability: "error", authStatus: "unauthenticated", version: versionLabel, message: "Run gh auth login.", checkedAt };
17099
- }
17100
- return { installed: true, availability: "ready", authStatus: "authenticated", version: versionLabel, checkedAt };
17101
- }
17102
18461
  async requireGitHub(repositoryId, operation) {
17103
18462
  const repository = this.repositories.get(repositoryId);
17104
18463
  const info = await this.repositoryInfo(repositoryId);
17105
- if (!info.isGitHub || !info.nameWithOwner) {
17106
- throw new GhOperationError({ code: "GH_NOT_GITHUB_REPO", operation, message: "The origin remote does not point to a GitHub repository." });
17107
- }
17108
- const status = await this.status();
17109
- if (!status.installed) throw new GhOperationError({ code: "GH_CLI_NOT_FOUND", operation, message: "GitHub CLI is not installed." });
17110
- if (status.authStatus === "unauthenticated") throw new GhOperationError({ code: "GH_AUTH_REQUIRED", operation, message: "Sign in with gh auth login to continue." });
17111
- return { repository, nameWithOwner: info.nameWithOwner };
18464
+ if (!info.isGitHub || !info.nameWithOwner) throw new GhOperationError({ code: "GH_NOT_GITHUB_REPO", operation, message: "The origin remote does not point to a GitHub repository." });
18465
+ const remote = await this.remote(repositoryId);
18466
+ const authenticated = await this.accounts.authenticate(repository, remote, info.nameWithOwner, operation);
18467
+ return { repository, nameWithOwner: info.nameWithOwner, ...authenticated };
17112
18468
  }
17113
18469
  async runGh(args, options) {
17114
- const detected = await selectCli(this.resolver, this.runner, "gh", { runOptions: { cwd: options.cwd, env: { ...GH_ENV } } });
17115
- const executable = detected.state === "available" ? detected.candidate : void 0;
17116
- if (!executable) throw new GhOperationError({ code: "GH_CLI_NOT_FOUND", operation: options.operation, message: "GitHub CLI is not installed." });
17117
- const result = await this.run(executable, args, options);
17118
- if (result.exitCode !== 0) throw classifyFailure(result, options.operation);
18470
+ const result = await this.run(options.executable, args, options);
18471
+ if (result.exitCode !== 0) throw classifyFailure({ ...result, stdout: redactCommandToken(result.stdout, options.auth), stderr: redactCommandToken(result.stderr, options.auth) }, options.operation);
17119
18472
  return result;
17120
18473
  }
17121
18474
  async run(executable, args, options) {
17122
18475
  try {
17123
18476
  return await this.runner.run(executable.executable, args, {
17124
18477
  cwd: options.cwd,
17125
- env: { ...executable.env, ...GH_ENV },
18478
+ env: { ...executable.env, ...GH_ENV, ...options.auth?.env },
18479
+ ...options.auth?.removeEnv ? { removeEnv: options.auth.removeEnv } : {},
17126
18480
  ...options.timeoutMs !== void 0 ? { timeoutMs: options.timeoutMs } : {},
17127
18481
  ...options.maxOutputBytes !== void 0 ? { maxOutputBytes: options.maxOutputBytes } : {},
17128
18482
  ...options.stdin !== void 0 ? { stdin: options.stdin } : {}
17129
18483
  });
17130
18484
  } catch (error) {
17131
- throw mapRunnerError(error, options.operation);
18485
+ const mapped = mapRunnerError(error, options.operation);
18486
+ throw new GhOperationError({ ...mapped.detail, message: redactCommandToken(mapped.message, options.auth ?? {}) });
17132
18487
  }
17133
18488
  }
17134
18489
  }
@@ -17152,6 +18507,8 @@ ${result.stdout}`.toLowerCase();
17152
18507
  if (/could not resolve|no such host|connection refused|connection reset|network|dial tcp|tls handshake/.test(raw)) {
17153
18508
  return new GhOperationError({ code: "GH_PROCESS_FAILED", operation, message: "Could not connect to GitHub. Check your network and try again.", exitCode: result.exitCode, retryable: true });
17154
18509
  }
18510
+ if (/403|resource not accessible|insufficient.*scope|saml|sso/.test(raw)) return new GhOperationError({ code: "GH_ACCESS_DENIED", operation, message: "This GitHub account cannot perform the operation. Check repository permissions, token permissions and organisation SSO policies.", exitCode: result.exitCode });
18511
+ if (/404|could not resolve to a repository/.test(raw) && operation !== "gh-pr-stack") return new GhOperationError({ code: "GH_ACCESS_DENIED", operation, message: "The repository was not found or is not accessible with this GitHub account.", exitCode: result.exitCode });
17155
18512
  const detail = firstLine(result.stderr) || firstLine(result.stdout);
17156
18513
  return new GhOperationError({ code: "GH_PROCESS_FAILED", operation, message: detail || "GitHub CLI could not complete the operation.", exitCode: result.exitCode });
17157
18514
  }
@@ -17167,6 +18524,50 @@ function mapRunnerError(error, operation) {
17167
18524
  function firstLine(value) {
17168
18525
  return (value.split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? "").slice(0, 300);
17169
18526
  }
18527
+ const statusSchema = object({
18528
+ id: _enum(["codex", "claude", "opencode", "grok"]),
18529
+ label: string(),
18530
+ availability: _enum(["ready", "warning", "error"]),
18531
+ installed: boolean(),
18532
+ authStatus: _enum(["authenticated", "unauthenticated", "unknown"]),
18533
+ installationStatus: _enum(["available", "not-found", "not-executable", "incompatible", "inspection-failed"]).optional(),
18534
+ executablePath: string().optional(),
18535
+ executableSource: _enum(["configured", "process-path", "user-path", "known-location"]).optional(),
18536
+ discoveryWarning: string().optional(),
18537
+ version: string().optional(),
18538
+ message: string().optional(),
18539
+ cliName: string().optional(),
18540
+ models: array(object({ id: string(), label: string(), description: string().optional() })),
18541
+ checkedAt: string().datetime()
18542
+ });
18543
+ const cacheSchema = object({
18544
+ version: literal(1),
18545
+ key: string(),
18546
+ statuses: array(statusSchema).min(1).max(4)
18547
+ }).refine(({ statuses }) => new Set(statuses.map((status) => status.id)).size === statuses.length);
18548
+ class AiStatusStore {
18549
+ constructor(filePath) {
18550
+ this.filePath = filePath;
18551
+ }
18552
+ filePath;
18553
+ pending = Promise.resolve();
18554
+ async load(key) {
18555
+ await this.pending;
18556
+ try {
18557
+ if ((await stat(this.filePath)).size > 4 * 1024 * 1024) return null;
18558
+ const parsed = cacheSchema.safeParse(JSON.parse(await readFile(this.filePath, "utf8")));
18559
+ return parsed.success && parsed.data.key === key ? parsed.data.statuses : null;
18560
+ } catch {
18561
+ return null;
18562
+ }
18563
+ }
18564
+ save(key, statuses) {
18565
+ const snapshot = cacheSchema.parse({ version: 1, key, statuses });
18566
+ const write = this.pending.then(() => writeFileAtomically(this.filePath, JSON.stringify(snapshot), { parseJson: true }));
18567
+ this.pending = write.catch(() => void 0);
18568
+ return write;
18569
+ }
18570
+ }
17170
18571
  const MAX_PROBLEM_LOG_ENTRIES = 500;
17171
18572
  const PROBLEM_LOG_COMPACTION_SLACK = 1.25;
17172
18573
  const LEVELS = ["error", "warn"];
@@ -17567,9 +18968,11 @@ function normalizeRemoteFetchIntervalSeconds(value) {
17567
18968
  Math.max(MIN_REMOTE_FETCH_INTERVAL_SECONDS, stepped)
17568
18969
  );
17569
18970
  }
17570
- const SETTINGS_SCHEMA_VERSION = 1;
18971
+ const SETTINGS_SCHEMA_VERSION = 2;
17571
18972
  const defaults = {
17572
18973
  version: SETTINGS_SCHEMA_VERSION,
18974
+ githubAccounts: {},
18975
+ githubDefaultLogin: null,
17573
18976
  recentRepositories: [],
17574
18977
  repositoryProjects: [],
17575
18978
  filesTreeStates: [],
@@ -17596,6 +18999,7 @@ const defaults = {
17596
18999
  };
17597
19000
  const settingsRecordSchema = looseObject({
17598
19001
  version: unknown().optional(),
19002
+ githubAccounts: unknown().optional(),
17599
19003
  recentRepositories: unknown().optional(),
17600
19004
  repositoryProjects: unknown().optional(),
17601
19005
  filesTreeStates: unknown().optional(),
@@ -17672,11 +19076,53 @@ class SettingsStore {
17672
19076
  get windowBounds() {
17673
19077
  return { ...this.data.windowBounds };
17674
19078
  }
19079
+ githubAccount(commonDir) {
19080
+ const selection = this.data.githubAccounts[githubRepositoryKey(commonDir)] ?? AUTOMATIC_GITHUB_ACCOUNT;
19081
+ if (selection.mode === "account" && selection.useGlobalDefault) {
19082
+ if (!this.data.githubDefaultLogin) throw new Error("Choose an OpenTig default account in Settings → GitHub.");
19083
+ return { ...selection, login: this.data.githubDefaultLogin };
19084
+ }
19085
+ return { ...selection };
19086
+ }
19087
+ get githubDefaultLogin() {
19088
+ return this.data.githubDefaultLogin;
19089
+ }
19090
+ async setGitHubDefaultLogin(login2) {
19091
+ const parsed = githubAccountSelectionSchema.parse({ mode: "account", host: "github.com", login: login2 });
19092
+ if (parsed.mode !== "account") throw new Error("Invalid default account.");
19093
+ const previous = this.data.githubDefaultLogin;
19094
+ this.data.githubDefaultLogin = parsed.login;
19095
+ try {
19096
+ await this.save();
19097
+ } catch (error) {
19098
+ if (this.data.githubDefaultLogin === parsed.login) this.data.githubDefaultLogin = previous;
19099
+ throw error;
19100
+ }
19101
+ }
19102
+ async setGitHubAccount(commonDir, selection) {
19103
+ const parsed = githubAccountSelectionSchema.parse(selection);
19104
+ if (parsed.mode === "account" && parsed.useGlobalDefault && parsed.login.toLowerCase() !== this.data.githubDefaultLogin?.toLowerCase()) {
19105
+ throw new Error("The OpenTig default account changed. Review the current account and try again.");
19106
+ }
19107
+ const key = githubRepositoryKey(commonDir);
19108
+ const previous = this.data.githubAccounts[key];
19109
+ if (parsed.mode === "auto") delete this.data.githubAccounts[key];
19110
+ else this.data.githubAccounts[key] = parsed;
19111
+ try {
19112
+ await this.save();
19113
+ } catch (error) {
19114
+ if (this.data.githubAccounts[key] === (parsed.mode === "auto" ? void 0 : parsed)) {
19115
+ if (previous) this.data.githubAccounts[key] = previous;
19116
+ else delete this.data.githubAccounts[key];
19117
+ }
19118
+ throw error;
19119
+ }
19120
+ }
17675
19121
  async touchRepository(repository) {
17676
19122
  const recent = { ...repository, lastOpenedAt: (/* @__PURE__ */ new Date()).toISOString() };
17677
- this.data.recentRepositories = [recent, ...this.data.recentRepositories.filter((item) => item.id !== repository.id)];
19123
+ const existing = this.data.recentRepositories.some((item) => item.id === repository.id);
19124
+ this.data.recentRepositories = existing ? this.data.recentRepositories.map((item) => item.id === repository.id ? recent : item) : [...this.data.recentRepositories, recent];
17678
19125
  this.data.activeRepositoryId = repository.id;
17679
- this.pruneRecentRepositories();
17680
19126
  await this.save();
17681
19127
  }
17682
19128
  /**
@@ -17687,6 +19133,12 @@ class SettingsStore {
17687
19133
  async relocateRepository(previousId, repository) {
17688
19134
  const previous = this.data.recentRepositories.find((item) => item.id === previousId);
17689
19135
  if (!previous) throw projectError("Unknown repository.");
19136
+ const previousAccountKey = githubRepositoryKey(previous.commonDir);
19137
+ const nextAccountKey = githubRepositoryKey(repository.commonDir);
19138
+ if (previousAccountKey !== nextAccountKey && this.data.githubAccounts[previousAccountKey]) {
19139
+ this.data.githubAccounts[nextAccountKey] ??= this.data.githubAccounts[previousAccountKey];
19140
+ delete this.data.githubAccounts[previousAccountKey];
19141
+ }
17690
19142
  const previousKey = normalizeRepositoryKey(previous.commonDir);
17691
19143
  const nextKey = normalizeRepositoryKey(repository.commonDir);
17692
19144
  const destinationAlreadyAssigned = this.data.repositoryProjects.some((project) => project.repositoryKeys.some((key) => key === nextKey && key !== previousKey));
@@ -17699,18 +19151,17 @@ class SettingsStore {
17699
19151
  });
17700
19152
  this.data.filesTreeStates = normalizeFilesTreeStates(this.data.filesTreeStates.map((state) => state.repositoryId === previousId ? { ...state, repositoryId: repository.id } : state));
17701
19153
  this.data.openFilesStates = normalizeOpenFilesStates(this.data.openFilesStates.map((state) => state.repositoryId === previousId ? { ...state, repositoryId: repository.id } : state));
17702
- this.data.recentRepositories = [
17703
- { ...repository, lastOpenedAt: (/* @__PURE__ */ new Date()).toISOString() },
17704
- ...this.data.recentRepositories.filter((item) => item.id !== previousId && item.id !== repository.id)
17705
- ];
19154
+ this.data.recentRepositories = this.data.recentRepositories.flatMap((item) => item.id === previousId ? [{ ...repository, lastOpenedAt: (/* @__PURE__ */ new Date()).toISOString() }] : item.id === repository.id ? [] : [item]);
17706
19155
  this.data.activeRepositoryId = repository.id;
17707
- this.pruneRecentRepositories();
17708
19156
  await this.save();
17709
19157
  }
17710
19158
  /** Forget the whole repository group, never its directories or Git data. */
17711
19159
  async forgetRepository(repositoryKey) {
17712
19160
  const key = normalizeRepositoryKey(repositoryKey);
17713
19161
  const removedIds = new Set(this.data.recentRepositories.filter((item) => normalizeRepositoryKey(item.commonDir) === key).map((item) => item.id));
19162
+ for (const item of this.data.recentRepositories) {
19163
+ if (removedIds.has(item.id)) delete this.data.githubAccounts[githubRepositoryKey(item.commonDir)];
19164
+ }
17714
19165
  this.data.recentRepositories = this.data.recentRepositories.filter((item) => !removedIds.has(item.id));
17715
19166
  this.data.repositoryProjects = this.data.repositoryProjects.map((project) => ({
17716
19167
  ...project,
@@ -17759,7 +19210,6 @@ class SettingsStore {
17759
19210
  async removeRepositoryProject(projectId) {
17760
19211
  this.getProject(projectId);
17761
19212
  this.data.repositoryProjects = this.data.repositoryProjects.filter((project) => project.id !== projectId);
17762
- this.pruneRecentRepositories();
17763
19213
  await this.save();
17764
19214
  return this.organization;
17765
19215
  }
@@ -17767,19 +19217,44 @@ class SettingsStore {
17767
19217
  const key = normalizeRepositoryKey(repositoryKey);
17768
19218
  if (!key || key.length > MAX_REPOSITORY_KEY_LENGTH || hasControlCharacters$1(key)) throw projectError("Invalid repository.");
17769
19219
  const target = projectId === null ? null : this.getProject(projectId);
17770
- if (target && !this.data.recentRepositories.some((repository) => normalizeRepositoryKey(repository.commonDir) === key)) {
19220
+ if (!this.data.recentRepositories.some((repository) => normalizeRepositoryKey(repository.commonDir) === key)) {
17771
19221
  throw projectError("Unknown repository.");
17772
19222
  }
19223
+ if (target?.repositoryKeys.includes(key)) return this.organization;
19224
+ if (target && target.repositoryKeys.length >= MAX_REPOSITORIES_PER_PROJECT) throw projectError("This project is full.");
17773
19225
  this.data.repositoryProjects = this.data.repositoryProjects.map((project) => ({
17774
19226
  ...project,
17775
19227
  repositoryKeys: project.repositoryKeys.filter((item) => item !== key)
17776
19228
  }));
17777
19229
  if (target) {
17778
19230
  const refreshed = this.getProject(target.id);
17779
- if (refreshed.repositoryKeys.length >= MAX_REPOSITORIES_PER_PROJECT) throw projectError("This project is full.");
17780
19231
  refreshed.repositoryKeys.push(key);
17781
19232
  }
17782
- this.pruneRecentRepositories();
19233
+ await this.save();
19234
+ return this.organization;
19235
+ }
19236
+ async moveRepositoryProject(projectId, toIndex) {
19237
+ this.data.repositoryProjects = moveToIndex(this.data.repositoryProjects, projectId, toIndex, (project) => project.id);
19238
+ await this.save();
19239
+ return this.organization;
19240
+ }
19241
+ /** Move within the assigned project, or within the unassigned repositories. */
19242
+ async moveRepository(repositoryKey, toIndex) {
19243
+ const key = normalizeRepositoryKey(repositoryKey);
19244
+ const keys = [...new Set(this.data.recentRepositories.map((item) => normalizeRepositoryKey(item.commonDir)))];
19245
+ const known = new Set(keys);
19246
+ const project = this.data.repositoryProjects.find((item) => item.repositoryKeys.includes(key));
19247
+ if (project) {
19248
+ const visible = project.repositoryKeys.filter((item) => known.has(item));
19249
+ project.repositoryKeys = [...moveToIndex(visible, key, toIndex, (item) => item), ...project.repositoryKeys.filter((item) => !known.has(item))];
19250
+ } else {
19251
+ const assigned = new Set(this.data.repositoryProjects.flatMap((item) => item.repositoryKeys));
19252
+ const unassigned = moveToIndex(keys.filter((item) => !assigned.has(item)), key, toIndex, (item) => item);
19253
+ let index = 0;
19254
+ const ordered = keys.map((item) => assigned.has(item) ? item : unassigned[index++]);
19255
+ const positions = new Map(ordered.map((item, position) => [item, position]));
19256
+ this.data.recentRepositories = [...this.data.recentRepositories].sort((a2, b) => positions.get(normalizeRepositoryKey(a2.commonDir)) - positions.get(normalizeRepositoryKey(b.commonDir)));
19257
+ }
17783
19258
  await this.save();
17784
19259
  return this.organization;
17785
19260
  }
@@ -17891,15 +19366,6 @@ class SettingsStore {
17891
19366
  throw projectError("A project with this name already exists.");
17892
19367
  }
17893
19368
  }
17894
- pruneRecentRepositories() {
17895
- const assigned = new Set(this.data.repositoryProjects.flatMap((project) => project.repositoryKeys));
17896
- let unassigned = 0;
17897
- this.data.recentRepositories = this.data.recentRepositories.filter((repository) => {
17898
- if (repository.id === this.data.activeRepositoryId || assigned.has(normalizeRepositoryKey(repository.commonDir))) return true;
17899
- unassigned += 1;
17900
- return unassigned <= UNASSIGNED_RECENT_LIMIT;
17901
- });
17902
- }
17903
19369
  }
17904
19370
  function validate(value, defaultDoubleControlShortcutEnabled) {
17905
19371
  const parsed = settingsRecordSchema.safeParse(value);
@@ -17909,8 +19375,16 @@ function validate(value, defaultDoubleControlShortcutEnabled) {
17909
19375
  return data;
17910
19376
  }
17911
19377
  const input = parsed.data;
17912
- const recentRepositories = Array.isArray(input.recentRepositories) ? input.recentRepositories.filter(isRecent).map(normalizeRecent).slice(0, 10) : [];
19378
+ const recentRepositories = Array.isArray(input.recentRepositories) ? input.recentRepositories.filter(isRecent).map(normalizeRecent) : [];
17913
19379
  const repositoryProjects = normalizeProjects(input.repositoryProjects);
19380
+ if (input.version !== SETTINGS_SCHEMA_VERSION) {
19381
+ const keys = [...new Set(recentRepositories.map((item) => normalizeRepositoryKey(item.commonDir)))];
19382
+ const known = new Set(keys);
19383
+ for (const project of repositoryProjects) {
19384
+ const assigned = new Set(project.repositoryKeys);
19385
+ project.repositoryKeys = [...keys.filter((key) => assigned.has(key)), ...project.repositoryKeys.filter((key) => !known.has(key))];
19386
+ }
19387
+ }
17914
19388
  const filesTreeStates = normalizeFilesTreeStates(input.filesTreeStates);
17915
19389
  const openFilesStates = normalizeOpenFilesStates(input.openFilesStates);
17916
19390
  const parsedPreferences = preferencesRecordSchema.safeParse(input.preferences);
@@ -17938,6 +19412,8 @@ function validate(value, defaultDoubleControlShortcutEnabled) {
17938
19412
  const windowBounds = bounds && typeof bounds.width === "number" && Number.isFinite(bounds.width) && typeof bounds.height === "number" && Number.isFinite(bounds.height) ? { width: Math.max(900, bounds.width), height: Math.max(600, bounds.height), ...typeof bounds.x === "number" && Number.isFinite(bounds.x) ? { x: bounds.x } : {}, ...typeof bounds.y === "number" && Number.isFinite(bounds.y) ? { y: bounds.y } : {} } : { ...defaults.windowBounds };
17939
19413
  return {
17940
19414
  version: SETTINGS_SCHEMA_VERSION,
19415
+ githubAccounts: normalizeGitHubAccounts(input.githubAccounts),
19416
+ githubDefaultLogin: typeof input.githubDefaultLogin === "string" && githubAccountSelectionSchema.safeParse({ mode: "account", host: "github.com", login: input.githubDefaultLogin }).success ? input.githubDefaultLogin : null,
17941
19417
  recentRepositories,
17942
19418
  repositoryProjects,
17943
19419
  filesTreeStates,
@@ -17990,6 +19466,15 @@ function normalizeWorktreePath(value) {
17990
19466
  function cloneProject(project) {
17991
19467
  return { ...project, repositoryKeys: [...project.repositoryKeys] };
17992
19468
  }
19469
+ function moveToIndex(items, key, toIndex, keyOf) {
19470
+ const fromIndex = items.findIndex((item2) => keyOf(item2) === key);
19471
+ if (fromIndex < 0) throw projectError("Unknown project or repository.");
19472
+ if (!Number.isSafeInteger(toIndex) || toIndex < 0 || toIndex >= items.length) throw projectError("Invalid ordering position.");
19473
+ const next = [...items];
19474
+ const [item] = next.splice(fromIndex, 1);
19475
+ next.splice(toIndex, 0, item);
19476
+ return next;
19477
+ }
17993
19478
  function cloneFilesTreeState(state) {
17994
19479
  return { ...state, expandedPaths: [...state.expandedPaths] };
17995
19480
  }
@@ -18059,6 +19544,16 @@ function executablePaths(value, strict = false, platform2 = process.platform, ho
18059
19544
  }
18060
19545
  return result;
18061
19546
  }
19547
+ function normalizeGitHubAccounts(value) {
19548
+ const result = /* @__PURE__ */ Object.create(null);
19549
+ if (!value || typeof value !== "object" || Array.isArray(value)) return result;
19550
+ for (const [key, selection] of Object.entries(value).slice(0, 1e3)) {
19551
+ if (!key || key.length > MAX_REPOSITORY_KEY_LENGTH || hasControlCharacters$1(key) || !path.isAbsolute(key)) continue;
19552
+ const parsed = githubAccountSelectionSchema.safeParse(selection);
19553
+ if (parsed.success && parsed.data.mode === "account") result[githubRepositoryKey(key)] = parsed.data;
19554
+ }
19555
+ return result;
19556
+ }
18062
19557
  class SystemTrash {
18063
19558
  available;
18064
19559
  operation;
@@ -18129,6 +19624,15 @@ class OpenTigRuntime {
18129
19624
  this.lastNotifiedAt = Date.now();
18130
19625
  this.onEvent({ type: "repository.changed", repositoryId, scope });
18131
19626
  }
19627
+ publishGitHubAccountsChange(commonDir, defaultOnly = false) {
19628
+ if (this.closePromise) return;
19629
+ const repositories = this.services.repositories.recents();
19630
+ const repositoryIds = defaultOnly ? repositories.filter((repo) => {
19631
+ const selection = this.settings.githubAccount(repo.commonDir);
19632
+ return selection.mode === "account" && selection.useGlobalDefault;
19633
+ }).map((repo) => repo.id) : commonDir ? repositories.filter((repo) => githubRepositoryKey(repo.commonDir) === githubRepositoryKey(commonDir)).map((repo) => repo.id) : void 0;
19634
+ this.onEvent({ type: "github.accounts-changed", ...repositoryIds ? { repositoryIds } : {}, ...defaultOnly ? { inventoryChanged: true } : {} });
19635
+ }
18132
19636
  publishActiveRepositoryChange(repository) {
18133
19637
  if (!this.closePromise) this.onEvent({ type: "repository.active-changed", repository });
18134
19638
  }
@@ -18187,10 +19691,25 @@ async function createOpenTigRuntime(options) {
18187
19691
  ];
18188
19692
  const aiLog = new AiLogStore(options.aiLogPath);
18189
19693
  await aiLog.load();
18190
- const ai = new CommitMessageService(operations, providers, aiLog, () => cliResolver.invalidate());
19694
+ const ai = new CommitMessageService(operations, providers, aiLog, () => cliResolver.invalidate(), {
19695
+ store: new AiStatusStore(path.join(path.dirname(options.settingsPath), "ai-statuses.json")),
19696
+ key: () => aiExecutablePathsKey(settings.preferences.aiExecutablePaths)
19697
+ });
18191
19698
  const prDrafts = new PullRequestDraftService(operations, providers, aiLog);
18192
- const github = new GitHubService(cliResolver, cliRunner, git, repositories);
18193
19699
  let runtime = null;
19700
+ const githubAccounts = new GitHubAccountsService(
19701
+ cliResolver,
19702
+ cliRunner,
19703
+ git,
19704
+ settings,
19705
+ new GitHubStatusStore(path.join(path.dirname(options.settingsPath), "github-status.json")),
19706
+ (repository, defaultOnly) => runtime?.publishGitHubAccountsChange(repository?.commonDir, defaultOnly)
19707
+ );
19708
+ const gitAccess = new RepositoryGitAccess(git, repositories, githubAccounts, cliRunner);
19709
+ gitAccess.install();
19710
+ operations.access = gitAccess;
19711
+ const github = new GitHubService(cliResolver, cliRunner, git, repositories, githubAccounts);
19712
+ github.gitAccess = gitAccess;
18194
19713
  const events = {
18195
19714
  repositoryChanged: (repositoryId, scope) => {
18196
19715
  runtime?.publishRepositoryChange(repositoryId, scope);
@@ -18357,6 +19876,10 @@ const IPC = {
18357
19876
  projectRename: "projects:rename",
18358
19877
  projectRemove: "projects:remove",
18359
19878
  projectAssign: "projects:assign",
19879
+ projectMove: "projects:move",
19880
+ projectMoveRepository: "projects:move-repository",
19881
+ repositoryPreparePath: "repository:prepare-path",
19882
+ repositoryCompleteSetup: "repository:complete-setup",
18360
19883
  repositoryOpenPath: "repository:open-path",
18361
19884
  repositoryBrowseDirectories: "repository:browse-directories",
18362
19885
  repositoryOpenRecent: "repository:open-recent",
@@ -18399,18 +19922,25 @@ const IPC = {
18399
19922
  commitUndoLatest: "commit:undo-latest",
18400
19923
  commitsList: "commits:list",
18401
19924
  commitsFiles: "commits:files",
19925
+ commitAuthorship: "commits:authorship",
19926
+ commitSetAuthorship: "commits:set-authorship",
18402
19927
  branchesList: "refs:branches",
18403
19928
  branchSwitch: "refs:switch",
18404
19929
  worktreesList: "refs:worktrees",
18405
19930
  worktreeSelect: "refs:select-worktree",
18406
19931
  refsPull: "refs:pull",
18407
19932
  refsPush: "refs:push",
19933
+ refsPushContext: "refs:push-context",
18408
19934
  refsFetch: "refs:fetch",
18409
19935
  localRefsSnapshot: "refs:local-snapshot",
18410
19936
  branchDetails: "refs:branch-details",
18411
19937
  worktreeDetails: "refs:worktree-details",
18412
19938
  branchDelete: "refs:delete-branch",
18413
19939
  worktreeRemove: "refs:remove-worktree",
19940
+ remoteBranchDetails: "refs:remote-branch-details",
19941
+ trackingBranchCreate: "refs:create-tracking-branch",
19942
+ remoteBranchDelete: "refs:delete-remote-branch",
19943
+ branchesFetch: "refs:fetch-branches",
18414
19944
  aiStatuses: "ai:statuses",
18415
19945
  aiGenerateCommitMessage: "ai:generate-commit-message",
18416
19946
  aiCancelGeneration: "ai:cancel-generation",
@@ -18419,6 +19949,10 @@ const IPC = {
18419
19949
  diagnosticsList: "diagnostics:list",
18420
19950
  diagnosticsClear: "diagnostics:clear",
18421
19951
  diagnosticsRecord: "diagnostics:record",
19952
+ githubAccountsStatus: "github:accounts-status",
19953
+ githubSetDefaultAccount: "github:set-default-account",
19954
+ githubRepositoryAccount: "github:repository-account",
19955
+ githubSetRepositoryAccount: "github:set-repository-account",
18422
19956
  githubStatus: "github:status",
18423
19957
  githubRepositoryInfo: "github:repository-info",
18424
19958
  githubPrForBranch: "github:pr-for-branch",
@@ -18445,6 +19979,10 @@ const OPEN_TIG_SERVER_COMMANDS = {
18445
19979
  "projects.rename": command(IPC.projectRename, "project-rename", true),
18446
19980
  "projects.remove": command(IPC.projectRemove, "project-remove", true),
18447
19981
  "projects.assign": command(IPC.projectAssign, "project-assign", true),
19982
+ "projects.moveProject": command(IPC.projectMove, "project-move", true),
19983
+ "projects.moveRepository": command(IPC.projectMoveRepository, "project-move-repository", true),
19984
+ "repository.preparePath": command(IPC.repositoryPreparePath, "prepare-path", false),
19985
+ "repository.completeSetup": command(IPC.repositoryCompleteSetup, "complete-setup", true),
18448
19986
  "repository.openPath": command(IPC.repositoryOpenPath, "open-path", true),
18449
19987
  "repository.browseDirectories": command(IPC.repositoryBrowseDirectories, "browse-directories", false),
18450
19988
  "repository.openRecent": command(IPC.repositoryOpenRecent, "open-recent", true),
@@ -18484,6 +20022,8 @@ const OPEN_TIG_SERVER_COMMANDS = {
18484
20022
  "index.updateConflict": command(IPC.indexUpdateConflict, "update-conflict", true, { maxRequestBytes: CONTENT_MAX_REQUEST_BYTES }),
18485
20023
  "index.resolveConflict": command(IPC.indexResolveConflict, "resolve-conflict", true, { maxRequestBytes: CONTENT_MAX_REQUEST_BYTES }),
18486
20024
  "commits.create": command(IPC.commitCreate, "commit", true),
20025
+ "commits.authorship": command(IPC.commitAuthorship, "commit-authorship-read", false),
20026
+ "commits.setAuthorship": command(IPC.commitSetAuthorship, "commit-authorship-set", true),
18487
20027
  "commits.undoLatest": command(IPC.commitUndoLatest, "undo-latest-commit", true),
18488
20028
  "commits.list": command(IPC.commitsList, "history", false),
18489
20029
  "commits.files": command(IPC.commitsFiles, "commit-files", false),
@@ -18492,10 +20032,15 @@ const OPEN_TIG_SERVER_COMMANDS = {
18492
20032
  "refs.listWorktrees": command(IPC.worktreesList, "worktrees", false),
18493
20033
  "refs.selectWorktree": command(IPC.worktreeSelect, "select-worktree", true),
18494
20034
  "refs.pull": command(IPC.refsPull, "pull", true),
20035
+ "refs.pushContext": command(IPC.refsPushContext, "push-context", false),
18495
20036
  "refs.push": command(IPC.refsPush, "push", true),
18496
20037
  "refs.fetch": command(IPC.refsFetch, "fetch", true),
18497
20038
  "refs.localRefsSnapshot": command(IPC.localRefsSnapshot, "local-refs-snapshot", false),
18498
20039
  "refs.branchDetails": command(IPC.branchDetails, "branch-details", false),
20040
+ "refs.remoteBranchDetails": command(IPC.remoteBranchDetails, "remote-branch-details", false, { timeoutMs: 12e4 }),
20041
+ "refs.createTrackingBranch": command(IPC.trackingBranchCreate, "create-tracking-branch", true),
20042
+ "refs.deleteRemoteBranch": command(IPC.remoteBranchDelete, "delete-remote-branch", true, { timeoutMs: 18e4 }),
20043
+ "refs.fetchBranches": command(IPC.branchesFetch, "fetch-branches", true, { timeoutMs: 18e4 }),
18499
20044
  "refs.worktreeDetails": command(IPC.worktreeDetails, "worktree-details", false),
18500
20045
  "refs.deleteBranch": command(IPC.branchDelete, "delete-branch", true),
18501
20046
  "refs.removeWorktree": command(IPC.worktreeRemove, "remove-worktree", true),
@@ -18504,6 +20049,10 @@ const OPEN_TIG_SERVER_COMMANDS = {
18504
20049
  "ai.log": command(IPC.aiLog, "ai-log", false),
18505
20050
  "ai.clearLog": command(IPC.aiClearLog, "ai-clear-log", true),
18506
20051
  "ai.cancelGeneration": command(IPC.aiCancelGeneration, "ai-cancel-generation", true),
20052
+ "github.accountsStatus": command(IPC.githubAccountsStatus, "gh-accounts-status", false, { timeoutMs: 9e4 }),
20053
+ "github.repositoryAccount": command(IPC.githubRepositoryAccount, "gh-repository-account", false, { timeoutMs: 6e4 }),
20054
+ "github.setDefaultAccount": command(IPC.githubSetDefaultAccount, "gh-set-default-account", true),
20055
+ "github.setRepositoryAccount": command(IPC.githubSetRepositoryAccount, "gh-set-repository-account", true),
18507
20056
  "github.status": command(IPC.githubStatus, "gh-status", false),
18508
20057
  "github.repositoryInfo": command(IPC.githubRepositoryInfo, "gh-repository-info", false),
18509
20058
  "github.findPullRequestForBranch": command(IPC.githubPrForBranch, "gh-pr-for-branch", false),
@@ -18730,10 +20279,10 @@ async function inspectRepository(path2, git) {
18730
20279
  }
18731
20280
  const requestRecordSchema = looseObject({});
18732
20281
  function boundedStringSchema(maxLength, options = {}) {
18733
- let schema = string().max(maxLength).refine((value) => !value.includes("\0"));
18734
- if (options.empty !== true) schema = schema.min(1);
18735
- if (options.controls === false) return schema;
18736
- return schema.refine((value) => ![...value].some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127));
20282
+ let schema2 = string().max(maxLength).refine((value) => !value.includes("\0"));
20283
+ if (options.empty !== true) schema2 = schema2.min(1);
20284
+ if (options.controls === false) return schema2;
20285
+ return schema2.refine((value) => ![...value].some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127));
18737
20286
  }
18738
20287
  const booleanWithDefaultSchema = (fallback) => boolean().optional().default(fallback);
18739
20288
  function parseRecord(value) {
@@ -18884,6 +20433,22 @@ function deleteBranchArg(value, operation) {
18884
20433
  force: booleanArg(request.force, operation)
18885
20434
  };
18886
20435
  }
20436
+ function createTrackingBranchArg(value) {
20437
+ const operation = "create-tracking-branch";
20438
+ const request = requestObject(value, operation);
20439
+ return {
20440
+ ...branchDetailsArg(request, operation),
20441
+ expectedOid: oidArg(request.expectedOid, operation),
20442
+ localName: refsString(request.localName, operation, MAX_BRANCH_REF_LENGTH)
20443
+ };
20444
+ }
20445
+ function deleteRemoteBranchArg(value) {
20446
+ const operation = "delete-remote-branch";
20447
+ const request = requestObject(value, operation);
20448
+ const destinationId = refsString(request.destinationId, operation, 64);
20449
+ if (!/^[0-9a-f]{64}$/.test(destinationId)) throw new GitOperationError({ code: "INVALID_ARGUMENT", operation, message: "Invalid remote destination." });
20450
+ return { ...branchDetailsArg(request, operation), expectedOid: oidArg(request.expectedOid, operation), destinationId };
20451
+ }
18887
20452
  function removeWorktreeArg(value, operation) {
18888
20453
  const request = requestObject(value, operation);
18889
20454
  return {
@@ -18981,7 +20546,16 @@ function createPullRequestArg(value) {
18981
20546
  if (typeof input.base !== "string" || !input.base || input.base.length > 300 || input.base.includes("\0")) throw invalidGh(operation);
18982
20547
  if (typeof input.draft !== "boolean") throw invalidGh(operation);
18983
20548
  if (typeof input.repositoryId !== "string" || !input.repositoryId || input.repositoryId.length > 64) throw invalidGh(operation);
18984
- return { repositoryId: input.repositoryId, title: input.title.trim(), body: input.body, base: input.base, draft: input.draft };
20549
+ const expected = input.expectedAccount;
20550
+ if (expected !== void 0 && (!expected || typeof expected.login !== "string" || !/^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/.test(expected.login) || !Number.isSafeInteger(expected.revision) || expected.revision < 0)) throw invalidGh(operation);
20551
+ return {
20552
+ repositoryId: input.repositoryId,
20553
+ title: input.title.trim(),
20554
+ body: input.body,
20555
+ base: input.base,
20556
+ draft: input.draft,
20557
+ ...expected ? { expectedAccount: { login: expected.login, revision: expected.revision } } : {}
20558
+ };
18985
20559
  }
18986
20560
  function generatePullRequestDraftArg(value) {
18987
20561
  const operation = "ai-pr-draft";
@@ -19023,6 +20597,10 @@ function invalidAi(operation) {
19023
20597
  function invalidProject(operation) {
19024
20598
  return new GitOperationError({ code: "INVALID_ARGUMENT", operation, message: "Invalid project data." });
19025
20599
  }
20600
+ function orderingIndexArg(value, operation) {
20601
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) throw invalidProject(operation);
20602
+ return value;
20603
+ }
19026
20604
  function registerServerCommands(registry2, services, host) {
19027
20605
  const serverDefinitions = new Map(
19028
20606
  Object.values(OPEN_TIG_SERVER_COMMANDS).map((definition) => [definition.command, definition])
@@ -19069,8 +20647,9 @@ function registerServerCommands(registry2, services, host) {
19069
20647
  };
19070
20648
  });
19071
20649
  handle(IPC.preferences, "preferences", async (partial2) => {
20650
+ const previousPaths = aiExecutablePathsKey(services.settings.preferences.aiExecutablePaths);
19072
20651
  const preferences = await services.settings.setPreferences(partial2 ?? {});
19073
- if (partial2 && typeof partial2 === "object" && "aiExecutablePaths" in partial2) services.ai.invalidateStatuses();
20652
+ if (previousPaths !== aiExecutablePathsKey(preferences.aiExecutablePaths)) services.ai.invalidateStatuses();
19074
20653
  return preferences;
19075
20654
  });
19076
20655
  handle(IPC.filesTreeStateUpdate, "files-tree-state", (repositoryId, expandedPaths) => {
@@ -19085,6 +20664,22 @@ function registerServerCommands(registry2, services, host) {
19085
20664
  handle(IPC.projectRename, "project-rename", (projectId, name) => services.settings.renameRepositoryProject(projectIdArg(projectId, "project-rename"), projectNameArg(name, "project-rename")));
19086
20665
  handle(IPC.projectRemove, "project-remove", (projectId) => services.settings.removeRepositoryProject(projectIdArg(projectId, "project-remove")));
19087
20666
  handle(IPC.projectAssign, "project-assign", (repositoryKey, projectId) => services.settings.assignRepositoryProject(repositoryKeyArg(repositoryKey, "project-assign"), nullableProjectIdArg(projectId, "project-assign")));
20667
+ handle(IPC.projectMove, "project-move", (projectId, toIndex) => services.settings.moveRepositoryProject(projectIdArg(projectId, "project-move"), orderingIndexArg(toIndex, "project-move")));
20668
+ handle(IPC.projectMoveRepository, "project-move-repository", (repositoryKey, toIndex) => services.settings.moveRepository(repositoryKeyArg(repositoryKey, "project-move-repository"), orderingIndexArg(toIndex, "project-move-repository")));
20669
+ handle(IPC.repositoryPreparePath, "prepare-path", (path2) => services.repositories.preparePath(stringArg(path2, "prepare-path", 32768)));
20670
+ handleWithContext(IPC.repositoryCompleteSetup, "complete-setup", async (context, id, revision2) => {
20671
+ const repositoryId = stringArg(id, "complete-setup", 64);
20672
+ if (typeof revision2 !== "number" || !Number.isSafeInteger(revision2) || revision2 < 0) throw new GitOperationError({ code: "INVALID_ARGUMENT", operation: "complete-setup", message: "Invalid account revision." });
20673
+ const repository = await services.repositories.completeSetup(repositoryId, () => {
20674
+ if (revision2 !== services.github.accounts.revisionFor(services.repositories.get(repositoryId))) {
20675
+ throw new Error("The repository account changed. Reload setup and review it again.");
20676
+ }
20677
+ });
20678
+ fileClipboardState(context).pendingCut = null;
20679
+ services.watcher.start(repository);
20680
+ services.events.activeRepositoryChanged(repository);
20681
+ return repository;
20682
+ });
19088
20683
  handleWithContext(IPC.repositoryOpenPath, "open-path", (context, selectedPath) => openRepositoryPath(
19089
20684
  stringArg(selectedPath, "open-path", 32768),
19090
20685
  context
@@ -19329,6 +20924,17 @@ function registerServerCommands(registry2, services, host) {
19329
20924
  textArg(content, "update-conflict", 8 * 1024 * 1024)
19330
20925
  ));
19331
20926
  handle(IPC.commitCreate, "commit", (id, message) => services.operations.createCommit(stringArg(id, "commit", 64), stringArg(message, "commit", 1e5)));
20927
+ handle(IPC.commitAuthorship, "commit-authorship-read", (id) => services.operations.getCommitAuthorship(stringArg(id, "commit-authorship-read", 64)));
20928
+ handle(IPC.commitSetAuthorship, "commit-authorship-set", async (id, input) => {
20929
+ const repositoryId = stringArg(id, "commit-authorship-set", 64);
20930
+ const parsed = commitAuthorshipInputSchema.safeParse(input);
20931
+ if (!parsed.success) throw new GitOperationError({ code: "INVALID_ARGUMENT", operation: "commit-authorship-set", message: "Enter a valid name, email and reviewed identity." });
20932
+ const result = await services.operations.setCommitAuthorship(repositoryId, parsed.data);
20933
+ const repository = services.repositories.get(repositoryId);
20934
+ const ids = /* @__PURE__ */ new Set([repositoryId, ...services.repositories.recents().filter((item) => item.commonDir === repository.commonDir).map((item) => item.id)]);
20935
+ for (const siblingId of ids) services.events.repositoryChanged(siblingId, "unknown");
20936
+ return result;
20937
+ });
19332
20938
  handle(IPC.commitUndoLatest, "undo-latest-commit", (id, expectedOid) => services.operations.undoLatestCommit(
19333
20939
  stringArg(id, "undo-latest-commit", 64),
19334
20940
  oidArg(expectedOid, "undo-latest-commit")
@@ -19353,6 +20959,13 @@ function registerServerCommands(registry2, services, host) {
19353
20959
  const { repositoryId, fullName } = branchDetailsArg(request, "branch-details");
19354
20960
  return services.operations.branchDetails(repositoryId, fullName);
19355
20961
  });
20962
+ handle(IPC.remoteBranchDetails, "remote-branch-details", (request) => {
20963
+ const { repositoryId, fullName } = branchDetailsArg(request, "remote-branch-details");
20964
+ return services.operations.remoteBranchDetails(repositoryId, fullName);
20965
+ });
20966
+ handle(IPC.trackingBranchCreate, "create-tracking-branch", (request) => services.operations.createTrackingBranch(createTrackingBranchArg(request)));
20967
+ handle(IPC.remoteBranchDelete, "delete-remote-branch", (request) => services.operations.deleteRemoteBranch(deleteRemoteBranchArg(request)));
20968
+ handle(IPC.branchesFetch, "fetch-branches", (id) => services.operations.fetchBranches(stringArg(id, "fetch-branches", 64)));
19356
20969
  handle(IPC.worktreeDetails, "worktree-details", (request) => {
19357
20970
  const { repositoryId, path: path2 } = worktreeDetailsArg(request, "worktree-details");
19358
20971
  return services.operations.worktreeDetails(repositoryId, path2);
@@ -19366,7 +20979,8 @@ function registerServerCommands(registry2, services, host) {
19366
20979
  return services.operations.removeWorktree(repositoryId, path2, expectedOid, force, deleteBranch);
19367
20980
  });
19368
20981
  handle(IPC.refsPull, "pull", (id) => services.operations.pull(stringArg(id, "pull", 64)));
19369
- handle(IPC.refsPush, "push", (id, publish) => services.operations.push(stringArg(id, "push", 64), publishBranchArg(publish)));
20982
+ handle(IPC.refsPushContext, "push-context", (id, remote) => services.operations.publicationContext(stringArg(id, "push-context", 64), remote == null ? void 0 : stringArg(remote, "push-context", 512)));
20983
+ handle(IPC.refsPush, "push", (id, publish, expected) => services.operations.push(stringArg(id, "push", 64), publishBranchArg(publish), expected == null ? void 0 : stringArg(expected, "push", 64)));
19370
20984
  handle(IPC.refsFetch, "fetch", (id) => services.operations.fetch(stringArg(id, "fetch", 64)));
19371
20985
  handle(IPC.aiStatuses, "ai-statuses", (forceRefresh) => services.ai.statuses(booleanArg(forceRefresh, "ai-statuses")));
19372
20986
  handleWithContext(IPC.aiGenerateCommitMessage, "ai-generate-commit-message", (context, input) => services.ai.generate(generateCommitMessageArg(input), context.signal));
@@ -19390,6 +21004,15 @@ function registerServerCommands(registry2, services, host) {
19390
21004
  handle(IPC.aiCancelGeneration, "ai-cancel-generation", (requestId2) => {
19391
21005
  services.ai.cancel(aiString(requestId2, "ai-cancel-generation", 100, true));
19392
21006
  });
21007
+ handle(IPC.githubAccountsStatus, "gh-accounts-status", (forceRefresh) => services.github.accounts.status(booleanArg(forceRefresh, "gh-accounts-status")));
21008
+ handle(IPC.githubSetDefaultAccount, "gh-set-default-account", (login2) => services.github.accounts.setDefaultAccount(stringArg(login2, "gh-set-default-account", 39)));
21009
+ handle(IPC.githubRepositoryAccount, "gh-repository-account", (id, forceRefresh) => services.github.repositoryAccount(stringArg(id, "gh-repository-account", 64), booleanArg(forceRefresh, "gh-repository-account")));
21010
+ handle(IPC.githubSetRepositoryAccount, "gh-set-repository-account", (id, selection, expectedRevision) => {
21011
+ const parsed = githubAccountSelectionSchema.safeParse(selection);
21012
+ if (expectedRevision !== void 0 && (typeof expectedRevision !== "number" || !Number.isSafeInteger(expectedRevision) || expectedRevision < 0)) throw new GitOperationError({ code: "INVALID_ARGUMENT", operation: "gh-set-repository-account", message: "Invalid account revision." });
21013
+ if (!parsed.success) throw new GitOperationError({ code: "INVALID_ARGUMENT", operation: "gh-set-repository-account", message: "Invalid GitHub account selection." });
21014
+ return services.github.setRepositoryAccount(stringArg(id, "gh-set-repository-account", 64), parsed.data, typeof expectedRevision === "number" ? expectedRevision : void 0);
21015
+ });
19393
21016
  handle(IPC.githubStatus, "gh-status", (forceRefresh) => services.github.status(booleanArg(forceRefresh, "gh-status")));
19394
21017
  handle(IPC.githubRepositoryInfo, "gh-repository-info", (id) => services.github.repositoryInfo(stringArg(id, "gh-repository-info", 64)));
19395
21018
  handle(IPC.githubPrForBranch, "gh-pr-for-branch", (id, branchName) => services.github.findPullRequestForBranch(
@@ -19439,7 +21062,7 @@ function clipboardImageArg(value) {
19439
21062
  if (value instanceof Uint8Array) return value;
19440
21063
  throw new GitOperationError({ code: "INVALID_ARGUMENT", operation: "paste-entries", message: "Invalid clipboard image." });
19441
21064
  }
19442
- const protocolVersion = 1;
21065
+ const protocolVersion = 4;
19443
21066
  const protocolMetadata = {
19444
21067
  protocolVersion
19445
21068
  };
@@ -19455,6 +21078,85 @@ function preferredServerPort(profile) {
19455
21078
  }
19456
21079
  const AUTH_DATA_VERSION = 4;
19457
21080
  const BROWSER_SESSION_MAX_AGE_SECONDS = 30 * 24 * 60 * 60;
21081
+ async function hasActiveBrowserSessions(directory, now = Date.now) {
21082
+ const { sessions } = await loadSessions(path.join(directory, "sessions.json"));
21083
+ return sessions.some((session) => session.kind !== "desktop" && session.expiresAt !== null && now() < Date.parse(session.expiresAt));
21084
+ }
21085
+ async function loadSessions(filePath) {
21086
+ try {
21087
+ const parsed = JSON.parse(await readFile(filePath, "utf8"));
21088
+ if (isLegacyAuthData(parsed)) {
21089
+ await chmod(filePath, 384);
21090
+ return {
21091
+ sessions: parsed.sessions.map((session) => ({
21092
+ ...session,
21093
+ kind: "legacy",
21094
+ expiresAt: sessionExpiry("legacy", session.createdAt),
21095
+ clientName: "Legacy browser session",
21096
+ deviceType: "unknown",
21097
+ os: null,
21098
+ browser: null,
21099
+ remoteAddress: null,
21100
+ viaProxy: false,
21101
+ lastConnectedAt: null
21102
+ })),
21103
+ migrated: true
21104
+ };
21105
+ }
21106
+ if (isVersionTwoAuthData(parsed)) {
21107
+ await chmod(filePath, 384);
21108
+ return {
21109
+ sessions: parsed.sessions.map((session) => ({
21110
+ ...session,
21111
+ expiresAt: sessionExpiry(session.kind, session.createdAt),
21112
+ clientName: session.kind === "legacy" ? "Legacy browser session" : session.clientName,
21113
+ deviceType: session.kind === "desktop" ? "desktop" : "unknown",
21114
+ os: null,
21115
+ browser: session.kind === "browser" ? session.clientName : null,
21116
+ viaProxy: false,
21117
+ lastConnectedAt: null
21118
+ })),
21119
+ migrated: true
21120
+ };
21121
+ }
21122
+ if (isVersionThreeAuthData(parsed)) {
21123
+ await chmod(filePath, 384);
21124
+ return {
21125
+ sessions: parsed.sessions.map((session) => ({ ...session, expiresAt: sessionExpiry(session.kind, session.createdAt) })),
21126
+ migrated: true
21127
+ };
21128
+ }
21129
+ if (!isAuthData(parsed)) throw corrupt$1("session data");
21130
+ await chmod(filePath, 384);
21131
+ return { sessions: parsed.sessions.map((session) => ({ ...session })), migrated: false };
21132
+ } catch (error) {
21133
+ if (error.code === "ENOENT") return { sessions: [], migrated: false };
21134
+ if (error instanceof SyntaxError) throw corrupt$1("session data");
21135
+ throw error;
21136
+ }
21137
+ }
21138
+ function isAuthData(value) {
21139
+ const data = value;
21140
+ return Boolean(data && data.version === AUTH_DATA_VERSION && isVersionThreeAuthData({ ...data, version: 3 }) && data.sessions?.every((session) => session.kind === "desktop" ? session.expiresAt === null : typeof session.expiresAt === "string" && Number.isFinite(Date.parse(session.expiresAt)) && Date.parse(session.expiresAt) > Date.parse(session.createdAt)));
21141
+ }
21142
+ function isVersionThreeAuthData(value) {
21143
+ const data = value;
21144
+ return Boolean(data && data.version === 3 && Array.isArray(data.sessions) && data.sessions.every((session) => session && typeof session.id === "string" && /^[A-Za-z0-9_-]{24}$/.test(session.id) && typeof session.digest === "string" && /^[A-Za-z0-9_-]{43}$/.test(session.digest) && (session.kind === "desktop" || session.kind === "browser" || session.kind === "legacy") && typeof session.clientName === "string" && session.clientName.length > 0 && session.clientName.length <= 160 && (session.deviceType === "desktop" || session.deviceType === "mobile" || session.deviceType === "tablet" || session.deviceType === "bot" || session.deviceType === "unknown") && (session.os === null || typeof session.os === "string" && session.os.length <= 80) && (session.browser === null || typeof session.browser === "string" && session.browser.length <= 80) && (session.remoteAddress === null || typeof session.remoteAddress === "string" && session.remoteAddress.length <= 128) && typeof session.viaProxy === "boolean" && typeof session.createdAt === "string" && !Number.isNaN(Date.parse(session.createdAt)) && (session.lastConnectedAt === null || typeof session.lastConnectedAt === "string" && !Number.isNaN(Date.parse(session.lastConnectedAt)))));
21145
+ }
21146
+ function isVersionTwoAuthData(value) {
21147
+ const data = value;
21148
+ return Boolean(data && data.version === 2 && Array.isArray(data.sessions) && data.sessions.every((session) => session && typeof session.id === "string" && /^[A-Za-z0-9_-]{24}$/.test(session.id) && typeof session.digest === "string" && /^[A-Za-z0-9_-]{43}$/.test(session.digest) && (session.kind === "desktop" || session.kind === "browser" || session.kind === "legacy") && typeof session.clientName === "string" && session.clientName.length > 0 && session.clientName.length <= 160 && (session.remoteAddress === null || typeof session.remoteAddress === "string" && session.remoteAddress.length <= 128) && typeof session.createdAt === "string" && !Number.isNaN(Date.parse(session.createdAt))));
21149
+ }
21150
+ function isLegacyAuthData(value) {
21151
+ const data = value;
21152
+ return Boolean(data && data.version === 1 && Array.isArray(data.sessions) && data.sessions.every((session) => session && typeof session.id === "string" && /^[A-Za-z0-9_-]{24}$/.test(session.id) && typeof session.digest === "string" && /^[A-Za-z0-9_-]{43}$/.test(session.digest) && typeof session.createdAt === "string" && !Number.isNaN(Date.parse(session.createdAt))));
21153
+ }
21154
+ function corrupt$1(name) {
21155
+ return new Error(`OpenTig authentication ${name} is corrupt.`);
21156
+ }
21157
+ function sessionExpiry(kind, createdAt) {
21158
+ return kind === "desktop" ? null : new Date(Date.parse(createdAt) + BROWSER_SESSION_MAX_AGE_SECONDS * 1e3).toISOString();
21159
+ }
19458
21160
  const SECRET_BYTES = 32;
19459
21161
  const SECRET_FILE = "server-secret";
19460
21162
  const SESSIONS_FILE = "sessions.json";
@@ -19470,6 +21172,10 @@ class PersistentAuthStore {
19470
21172
  now;
19471
21173
  sessions;
19472
21174
  mutationQueue = Promise.resolve();
21175
+ /** Inspect an existing installation without issuing, revoking, or writing sessions. */
21176
+ static async hasActiveBrowserSessions(directory, now = Date.now) {
21177
+ return hasActiveBrowserSessions(directory, now);
21178
+ }
19473
21179
  static async open(directory, now = Date.now) {
19474
21180
  const resolved = path.resolve(directory);
19475
21181
  await mkdir(resolved, { recursive: true, mode: 448 });
@@ -19527,9 +21233,9 @@ class PersistentAuthStore {
19527
21233
  });
19528
21234
  }
19529
21235
  renewBrowserSession(token) {
19530
- const digest = this.digestCredential(token);
21236
+ const digest2 = this.digestCredential(token);
19531
21237
  return this.mutate((sessions) => {
19532
- const index = sessions.findIndex((session2) => session2.kind !== "desktop" && constantTimeEqual(digest, session2.digest) && this.isActive(session2));
21238
+ const index = sessions.findIndex((session2) => session2.kind !== "desktop" && constantTimeEqual(digest2, session2.digest) && this.isActive(session2));
19533
21239
  if (index < 0) return { next: sessions, result: false };
19534
21240
  const session = sessions[index];
19535
21241
  const expiresAt = sessionExpiry(session.kind, new Date(this.now()).toISOString());
@@ -19659,75 +21365,6 @@ async function loadOrCreateSecret(filePath) {
19659
21365
  throw error;
19660
21366
  }
19661
21367
  }
19662
- async function loadSessions(filePath) {
19663
- try {
19664
- const parsed = JSON.parse(await readFile(filePath, "utf8"));
19665
- if (isLegacyAuthData(parsed)) {
19666
- await chmod(filePath, 384);
19667
- return {
19668
- sessions: parsed.sessions.map((session) => ({
19669
- ...session,
19670
- kind: "legacy",
19671
- expiresAt: sessionExpiry("legacy", session.createdAt),
19672
- clientName: "Legacy browser session",
19673
- deviceType: "unknown",
19674
- os: null,
19675
- browser: null,
19676
- remoteAddress: null,
19677
- viaProxy: false,
19678
- lastConnectedAt: null
19679
- })),
19680
- migrated: true
19681
- };
19682
- }
19683
- if (isVersionTwoAuthData(parsed)) {
19684
- await chmod(filePath, 384);
19685
- return {
19686
- sessions: parsed.sessions.map((session) => ({
19687
- ...session,
19688
- expiresAt: sessionExpiry(session.kind, session.createdAt),
19689
- clientName: session.kind === "legacy" ? "Legacy browser session" : session.clientName,
19690
- deviceType: session.kind === "desktop" ? "desktop" : "unknown",
19691
- os: null,
19692
- browser: session.kind === "browser" ? session.clientName : null,
19693
- viaProxy: false,
19694
- lastConnectedAt: null
19695
- })),
19696
- migrated: true
19697
- };
19698
- }
19699
- if (isVersionThreeAuthData(parsed)) {
19700
- await chmod(filePath, 384);
19701
- return {
19702
- sessions: parsed.sessions.map((session) => ({ ...session, expiresAt: sessionExpiry(session.kind, session.createdAt) })),
19703
- migrated: true
19704
- };
19705
- }
19706
- if (!isAuthData(parsed)) throw corrupt("session data");
19707
- await chmod(filePath, 384);
19708
- return { sessions: parsed.sessions.map((session) => ({ ...session })), migrated: false };
19709
- } catch (error) {
19710
- if (error.code === "ENOENT") return { sessions: [], migrated: false };
19711
- if (error instanceof SyntaxError) throw corrupt("session data");
19712
- throw error;
19713
- }
19714
- }
19715
- function isAuthData(value) {
19716
- const data = value;
19717
- return Boolean(data && data.version === AUTH_DATA_VERSION && isVersionThreeAuthData({ ...data, version: 3 }) && data.sessions?.every((session) => session.kind === "desktop" ? session.expiresAt === null : typeof session.expiresAt === "string" && Number.isFinite(Date.parse(session.expiresAt)) && Date.parse(session.expiresAt) > Date.parse(session.createdAt)));
19718
- }
19719
- function isVersionThreeAuthData(value) {
19720
- const data = value;
19721
- return Boolean(data && data.version === 3 && Array.isArray(data.sessions) && data.sessions.every((session) => session && typeof session.id === "string" && /^[A-Za-z0-9_-]{24}$/.test(session.id) && typeof session.digest === "string" && /^[A-Za-z0-9_-]{43}$/.test(session.digest) && (session.kind === "desktop" || session.kind === "browser" || session.kind === "legacy") && typeof session.clientName === "string" && session.clientName.length > 0 && session.clientName.length <= 160 && (session.deviceType === "desktop" || session.deviceType === "mobile" || session.deviceType === "tablet" || session.deviceType === "bot" || session.deviceType === "unknown") && (session.os === null || typeof session.os === "string" && session.os.length <= 80) && (session.browser === null || typeof session.browser === "string" && session.browser.length <= 80) && (session.remoteAddress === null || typeof session.remoteAddress === "string" && session.remoteAddress.length <= 128) && typeof session.viaProxy === "boolean" && typeof session.createdAt === "string" && !Number.isNaN(Date.parse(session.createdAt)) && (session.lastConnectedAt === null || typeof session.lastConnectedAt === "string" && !Number.isNaN(Date.parse(session.lastConnectedAt)))));
19722
- }
19723
- function isVersionTwoAuthData(value) {
19724
- const data = value;
19725
- return Boolean(data && data.version === 2 && Array.isArray(data.sessions) && data.sessions.every((session) => session && typeof session.id === "string" && /^[A-Za-z0-9_-]{24}$/.test(session.id) && typeof session.digest === "string" && /^[A-Za-z0-9_-]{43}$/.test(session.digest) && (session.kind === "desktop" || session.kind === "browser" || session.kind === "legacy") && typeof session.clientName === "string" && session.clientName.length > 0 && session.clientName.length <= 160 && (session.remoteAddress === null || typeof session.remoteAddress === "string" && session.remoteAddress.length <= 128) && typeof session.createdAt === "string" && !Number.isNaN(Date.parse(session.createdAt))));
19726
- }
19727
- function isLegacyAuthData(value) {
19728
- const data = value;
19729
- return Boolean(data && data.version === 1 && Array.isArray(data.sessions) && data.sessions.every((session) => session && typeof session.id === "string" && /^[A-Za-z0-9_-]{24}$/.test(session.id) && typeof session.digest === "string" && /^[A-Za-z0-9_-]{43}$/.test(session.digest) && typeof session.createdAt === "string" && !Number.isNaN(Date.parse(session.createdAt))));
19730
- }
19731
21368
  function constantTimeEqual(left, right) {
19732
21369
  const leftBytes = Buffer.from(left);
19733
21370
  const rightBytes = Buffer.from(right);
@@ -19745,17 +21382,15 @@ async function exists(filePath) {
19745
21382
  function corrupt(name) {
19746
21383
  return new Error(`OpenTig authentication ${name} is corrupt.`);
19747
21384
  }
19748
- function sessionExpiry(kind, createdAt) {
19749
- return kind === "desktop" ? null : new Date(Date.parse(createdAt) + BROWSER_SESSION_MAX_AGE_SECONDS * 1e3).toISOString();
19750
- }
19751
21385
  const DEFAULT_PAIRING_TTL_MS = 5 * 60 * 1e3;
19752
21386
  class OpenTigSessionAuth {
19753
- constructor(source, store, secureCookies, now, profile) {
21387
+ constructor(source, store, secureCookies, now, profile, browserAccessEnabled) {
19754
21388
  this.source = source;
19755
21389
  this.store = store;
19756
21390
  this.secureCookies = secureCookies;
19757
21391
  this.now = now;
19758
21392
  this.profile = profile;
21393
+ this.browserAccessEnabled = browserAccessEnabled;
19759
21394
  }
19760
21395
  source;
19761
21396
  store;
@@ -19763,15 +21398,30 @@ class OpenTigSessionAuth {
19763
21398
  now;
19764
21399
  profile;
19765
21400
  pairing = null;
21401
+ browserAccessEnabled;
21402
+ browserGeneration = 0;
21403
+ browserQueue = Promise.resolve();
19766
21404
  static async open(options) {
19767
21405
  const store = await PersistentAuthStore.open(options.dataDirectory, options.now);
19768
- return new OpenTigSessionAuth(options.source, store, options.secureCookies ?? false, options.now ?? Date.now, options.profile ?? "production");
21406
+ return new OpenTigSessionAuth(options.source, store, options.secureCookies ?? false, options.now ?? Date.now, options.profile ?? "production", options.browserAccessEnabled ?? true);
19769
21407
  }
19770
21408
  descriptor() {
19771
21409
  this.dropExpiredPairing();
19772
- return { authenticationRequired: true, pairingAvailable: this.pairing !== null };
21410
+ return { authenticationRequired: true, pairingAvailable: this.browserAccessEnabled && this.pairing !== null, browserAccessEnabled: this.browserAccessEnabled };
21411
+ }
21412
+ setBrowserAccessEnabled(enabled) {
21413
+ if (!enabled) {
21414
+ this.browserAccessEnabled = false;
21415
+ this.browserGeneration++;
21416
+ this.pairing = null;
21417
+ }
21418
+ const generation = this.browserGeneration;
21419
+ return this.queueBrowserMutation(async () => {
21420
+ this.browserAccessEnabled = enabled && generation === this.browserGeneration;
21421
+ });
19773
21422
  }
19774
21423
  createPairingToken(ttlMs = DEFAULT_PAIRING_TTL_MS) {
21424
+ if (!this.browserAccessEnabled) throw new Error("Web access is disabled.");
19775
21425
  if (!Number.isSafeInteger(ttlMs) || ttlMs < 1e3 || ttlMs > 15 * 60 * 1e3) throw new Error("Invalid pairing token lifetime.");
19776
21426
  const token = randomBytes(32).toString("base64url");
19777
21427
  const expiresAt = this.now() + ttlMs;
@@ -19793,30 +21443,43 @@ class OpenTigSessionAuth {
19793
21443
  async exchangePairingToken(token, metadata, secure = false) {
19794
21444
  this.dropExpiredPairing();
19795
21445
  const pairing = this.pairing;
19796
- if (!pairing || !isCredential(token) || !this.store.matchesDigest(token, pairing.digest)) return null;
21446
+ if (!this.browserAccessEnabled || !pairing || !isCredential(token) || !this.store.matchesDigest(token, pairing.digest)) return null;
19797
21447
  this.pairing = null;
19798
- try {
19799
- return await this.issueCookie({
19800
- kind: "browser",
19801
- clientName: metadata?.clientName ?? "Browser",
19802
- deviceType: metadata?.deviceType ?? "unknown",
19803
- os: metadata?.os ?? null,
19804
- browser: metadata?.browser ?? null,
19805
- remoteAddress: metadata?.remoteAddress ?? null,
19806
- viaProxy: metadata?.viaProxy ?? false
19807
- }, secure);
19808
- } catch (error) {
19809
- if (this.now() < pairing.expiresAt && !this.pairing) this.pairing = pairing;
19810
- throw error;
19811
- }
21448
+ const generation = this.browserGeneration;
21449
+ return this.queueBrowserMutation(async () => {
21450
+ if (!this.browserAccessEnabled || generation !== this.browserGeneration) return null;
21451
+ try {
21452
+ const cookie = await this.issueCookie({
21453
+ kind: "browser",
21454
+ clientName: metadata?.clientName ?? "Browser",
21455
+ deviceType: metadata?.deviceType ?? "unknown",
21456
+ os: metadata?.os ?? null,
21457
+ browser: metadata?.browser ?? null,
21458
+ remoteAddress: metadata?.remoteAddress ?? null,
21459
+ viaProxy: metadata?.viaProxy ?? false
21460
+ }, secure);
21461
+ if (!this.browserAccessEnabled || generation !== this.browserGeneration) {
21462
+ await this.revoke({ cookie });
21463
+ return null;
21464
+ }
21465
+ return cookie;
21466
+ } catch (error) {
21467
+ if (this.browserAccessEnabled && generation === this.browserGeneration && this.now() < pairing.expiresAt && !this.pairing) this.pairing = pairing;
21468
+ throw error;
21469
+ }
21470
+ });
19812
21471
  }
19813
21472
  authenticate(headers) {
19814
21473
  const token = readCookie(headers.cookie, sessionCookieName(this.profile));
19815
- return token ? this.store.authenticate(token) : null;
21474
+ const sessionId = token ? this.store.authenticate(token) : null;
21475
+ return sessionId && this.hasSession(sessionId) ? sessionId : null;
19816
21476
  }
19817
21477
  async renewBrowserCookie(headers, secure = false) {
21478
+ if (!this.browserAccessEnabled) return null;
21479
+ const generation = this.browserGeneration;
19818
21480
  const token = readCookie(headers.cookie, sessionCookieName(this.profile));
19819
21481
  if (!token || !await this.store.renewBrowserSession(token)) return null;
21482
+ if (!this.browserAccessEnabled || generation !== this.browserGeneration) return null;
19820
21483
  return this.sessionCookie(token, true, secure);
19821
21484
  }
19822
21485
  async revoke(headers) {
@@ -19861,11 +21524,18 @@ class OpenTigSessionAuth {
19861
21524
  };
19862
21525
  }
19863
21526
  hasSession(sessionId) {
19864
- return this.store.hasSession(sessionId);
21527
+ return this.store.hasSession(sessionId) && (this.browserAccessEnabled || this.sessions().some((session) => session.id === sessionId && session.kind === "desktop"));
19865
21528
  }
19866
21529
  close() {
21530
+ this.browserAccessEnabled = false;
21531
+ this.browserGeneration++;
19867
21532
  this.pairing = null;
19868
- return this.store.close();
21533
+ return this.browserQueue.then(() => this.store.close());
21534
+ }
21535
+ queueBrowserMutation(operation) {
21536
+ const result = this.browserQueue.then(operation);
21537
+ this.browserQueue = result.then(() => void 0, () => void 0);
21538
+ return result;
19869
21539
  }
19870
21540
  expiredCookie(secure = false) {
19871
21541
  return `${sessionCookieName(this.profile)}=; Path=/; HttpOnly; SameSite=Strict; Max-Age=0${this.secureCookies || secure ? "; Secure" : ""}`;
@@ -20128,6 +21798,19 @@ async function handleRequest(context, request, response) {
20128
21798
  }
20129
21799
  return sendJson(response, 404, { error: "Not found." });
20130
21800
  }
21801
+ if (method === "GET" && rawPath === "/api/auth/web-access") {
21802
+ if (!context.auth.authenticate(request.headers)) return sendJson(response, 401, { error: "Authentication required." });
21803
+ const listener = context.listenerAddress();
21804
+ const status = {
21805
+ webAccessEnabled: context.auth.descriptor().browserAccessEnabled,
21806
+ pairingAvailable: context.auth.descriptor().pairingAvailable,
21807
+ listeningOnLan: !isLoopbackAddress(listener.host),
21808
+ listenerHost: listener.host,
21809
+ actualPort: listener.port,
21810
+ ready: context.isReady()
21811
+ };
21812
+ return sendJson(response, 200, status);
21813
+ }
20131
21814
  if (method === "GET" && rawPath === "/api/auth/sessions") {
20132
21815
  const currentSessionId = context.auth.authenticate(request.headers);
20133
21816
  if (!currentSessionId) return sendJson(response, 401, { error: "Authentication required." });
@@ -20147,12 +21830,22 @@ async function handleRequest(context, request, response) {
20147
21830
  }
20148
21831
  if (method === "POST" && rawPath.startsWith("/api/")) {
20149
21832
  if (!isAllowedOrigin(request)) return sendJson(response, 403, { error: "Forbidden origin." });
21833
+ if (rawPath === "/api/auth/pairing-link") {
21834
+ if (!context.auth.authenticate(request.headers)) return sendJson(response, 401, { error: "Authentication required." });
21835
+ if (!context.auth.descriptor().browserAccessEnabled) return sendJson(response, 403, { error: "Web access is disabled.", code: "WEB_ACCESS_DISABLED" });
21836
+ if (!context.isReady()) return sendJson(response, 503, { error: "Server is not ready." });
21837
+ const url = new URL("/pair", request.headers.origin);
21838
+ const pairing = context.auth.createPairingToken();
21839
+ url.hash = new URLSearchParams({ token: pairing.token }).toString();
21840
+ return sendJson(response, 200, { url: url.href, expiresAt: pairing.expiresAt });
21841
+ }
20150
21842
  if (rawPath === "/api/auth/renew") {
20151
21843
  const cookie = await context.auth.renewBrowserCookie(request.headers, requestOriginIsSecure(request));
20152
21844
  if (!cookie) return sendJson(response, 401, { error: "Authentication required." });
20153
21845
  return sendJson(response, 204, null, { "Set-Cookie": cookie });
20154
21846
  }
20155
21847
  if (rawPath === "/api/auth/pair" || rawPath === "/api/auth/desktop") {
21848
+ if (rawPath === "/api/auth/pair" && !context.auth.descriptor().browserAccessEnabled) return sendJson(response, 403, { error: "Web access is disabled.", code: "WEB_ACCESS_DISABLED" });
20156
21849
  const body = await readJsonObject(request, response);
20157
21850
  if (!body) return;
20158
21851
  const secure = requestOriginIsSecure(request);
@@ -20164,6 +21857,7 @@ async function handleRequest(context, request, response) {
20164
21857
  } else {
20165
21858
  cookie = await context.auth.exchangeDesktopSecret(body.secret);
20166
21859
  }
21860
+ if (!cookie && rawPath === "/api/auth/pair" && !context.auth.descriptor().browserAccessEnabled) return sendJson(response, 403, { error: "Web access is disabled.", code: "WEB_ACCESS_DISABLED" });
20167
21861
  if (!cookie) return sendJson(response, 401, { error: "Authentication failed." });
20168
21862
  return sendJson(response, 204, null, { "Set-Cookie": cookie });
20169
21863
  }
@@ -20324,6 +22018,7 @@ async function handleLocalAdminPair(context, request, response) {
20324
22018
  }
20325
22019
  const origin = normalizePublicOrigin(body.publicOrigin);
20326
22020
  if (!origin) return sendJson(response, 400, { error: "Invalid public origin." });
22021
+ if (!context.auth.descriptor().browserAccessEnabled) return sendJson(response, 403, { error: "Web access is disabled.", code: "WEB_ACCESS_DISABLED" });
20327
22022
  const pairing = admin.createPairingToken();
20328
22023
  const url = new URL("/pair", origin);
20329
22024
  url.hash = new URLSearchParams({ token: pairing.token }).toString();
@@ -20456,7 +22151,10 @@ class OpenTigWebSocketTransport {
20456
22151
  revokeSessions(sessionIds) {
20457
22152
  const revoked = new Set(sessionIds);
20458
22153
  for (const [socket, state] of this.clients) {
20459
- if (revoked.has(state.sessionId)) socket.close(1008, "Session revoked");
22154
+ if (revoked.has(state.sessionId)) {
22155
+ for (const controller of state.requests.values()) controller.abort();
22156
+ socket.close(1008, "Session revoked");
22157
+ }
20460
22158
  }
20461
22159
  }
20462
22160
  close() {
@@ -20499,6 +22197,7 @@ class OpenTigWebSocketTransport {
20499
22197
  socket.on("error", () => this.options.logger("warn", "WebSocket connection failed."));
20500
22198
  }
20501
22199
  async message(socket, state, data, isBinary) {
22200
+ if (!this.options.auth.hasSession(state.sessionId)) return socket.close(1008, "Session expired or revoked");
20502
22201
  if (isBinary) return socket.close(1003, "Text messages only");
20503
22202
  const bytes = rawBytes(data);
20504
22203
  if (bytes.byteLength > FILE_WRITE_FRAME_LIMIT) return socket.close(1009, "Message too large");
@@ -20653,6 +22352,10 @@ class OpenTigServer {
20653
22352
  identity: options.identity,
20654
22353
  mode: options.mode ?? "desktop",
20655
22354
  isReady: () => this.ready,
22355
+ listenerAddress: () => {
22356
+ const address = this.httpServer.address();
22357
+ return { host: address.address, port: address.port };
22358
+ },
20656
22359
  sessionConnectionCount: (sessionId) => this.webSockets.connectionCount(sessionId),
20657
22360
  onSessionsRevoked: (sessionIds) => this.disconnectSessions(sessionIds),
20658
22361
  logger: this.logger,
@@ -20696,7 +22399,13 @@ class OpenTigServer {
20696
22399
  return this.options.auth.createPairingToken();
20697
22400
  }
20698
22401
  getStatus() {
20699
- return { connectedSessionCount: this.webSockets.connectedSessionCount };
22402
+ return { connectedSessionCount: this.webSockets.connectedSessionCount, browserAccessEnabled: this.options.auth.descriptor().browserAccessEnabled };
22403
+ }
22404
+ async setBrowserAccessEnabled(enabled) {
22405
+ if (!this.ready) throw new Error("Server is not ready.");
22406
+ const pending = this.options.auth.setBrowserAccessEnabled(enabled);
22407
+ if (!enabled) this.disconnectSessions(this.options.auth.sessions().filter((session) => session.kind !== "desktop").map((session) => session.id));
22408
+ await pending;
20700
22409
  }
20701
22410
  async revokeAllSessions() {
20702
22411
  if (!this.ready) throw new Error("Server is not ready.");
@@ -20754,6 +22463,7 @@ async function runOpenTigServer(config2) {
20754
22463
  let transport = null;
20755
22464
  const auth = await OpenTigSessionAuth.open({
20756
22465
  source: config2.auth,
22466
+ browserAccessEnabled: config2.browserAccessEnabled ?? config2.mode === "web-access",
20757
22467
  ...config2.profile ? { profile: config2.profile } : {},
20758
22468
  dataDirectory: config2.serverDataPath ?? path.join(path.dirname(config2.settingsPath), "server"),
20759
22469
  ...config2.secureCookies === void 0 ? {} : { secureCookies: config2.secureCookies }
@@ -20807,6 +22517,7 @@ async function runOpenTigServer(config2) {
20807
22517
  createPairingLink: () => pairingLink(address.origin, transport.createPairingToken()),
20808
22518
  getStatus: () => transport.getStatus(),
20809
22519
  revokeAllSessions: () => transport.revokeAllSessions(),
22520
+ setBrowserAccessEnabled: (enabled) => transport.setBrowserAccessEnabled(enabled),
20810
22521
  close: () => transport.stop()
20811
22522
  };
20812
22523
  } catch (error) {