@opentig/cli 0.1.7 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/THIRD_PARTY_NOTICES.md +3 -1
  2. package/dist/bin.mjs +2 -5
  3. package/dist/chunks/{cli-DCPhUVBb.mjs → cli-Bi6ET5UT.mjs} +15 -2
  4. package/dist/client/assets/App-BXZLGT86.js +74 -0
  5. package/dist/client/assets/CreatePullRequestDialog-DFonMYEx.js +2 -0
  6. package/dist/client/assets/DiffWorkspace-RDG_Y70N.js +26 -0
  7. package/dist/client/assets/EditableFileViewer-CsbifqBO.js +1 -0
  8. package/dist/client/assets/File-CHjvYpb2.js +1 -0
  9. package/dist/client/assets/FilesView-Dl8QFbFA.js +1 -0
  10. package/dist/client/assets/GitHubAvatar-CnBaloV0.js +1 -0
  11. package/dist/client/assets/HistoryView-BgCZU2JO.js +1 -0
  12. package/dist/client/assets/{HtmlFileViewer-C3Qylr9B.js → HtmlFileViewer-h8ULU8Gy.js} +1 -1
  13. package/dist/client/assets/{IconEdit-BHqlqze_.js → IconEdit-Cg8S2wqW.js} +1 -1
  14. package/dist/client/assets/IconFolderSymlink-BZSLX1ez.js +1 -0
  15. package/dist/client/assets/{ImageFileViewer-BJy0ptmR.js → ImageFileViewer-Cjw58ckv.js} +1 -1
  16. package/dist/client/assets/LocalRefsDialog-DNgrd7Qp.js +1 -0
  17. package/dist/client/assets/MarkdownFileViewer-kLiduag6.js +2 -0
  18. package/dist/client/assets/{PairingPage-DOPAXy3a.js → PairingPage-C97sv-8s.js} +1 -1
  19. package/dist/client/assets/PierreDiffViewer-eAcugmoF.js +1 -0
  20. package/dist/client/assets/{PierreEditBoundary-mxtF7x3F.js → PierreEditBoundary-DlGd1pqO.js} +2 -2
  21. package/dist/client/assets/{PierreWorkerPool-B_NFnwU-.js → PierreWorkerPool-Col4k7qz.js} +18 -18
  22. package/dist/client/assets/{PullRequestDiff-Bcu8Gmrf.js → PullRequestDiff-DhKTXyIF.js} +1 -1
  23. package/dist/client/assets/PullRequestViewer-BgI8HFMe.js +2 -0
  24. package/dist/client/assets/PullRequestsView-PYWLy8We.js +1 -0
  25. package/dist/client/assets/QuickOpenDialog-BFDDZVCL.js +1 -0
  26. package/dist/client/assets/RepositoryProjectsDialog-CvB8KcFp.js +1 -0
  27. package/dist/client/assets/SearchView-BwF_MsxK.js +1 -0
  28. package/dist/client/assets/SettingsDialog-DIcLmXSE.js +9 -0
  29. package/dist/client/assets/Viewer-o1hDU33O.js +3 -0
  30. package/dist/client/assets/{abnfDiagram-N423BO3Z-BxGW6tGd.js → abnfDiagram-N423BO3Z-BvrnK_R-.js} +1 -1
  31. package/dist/client/assets/{arc-CphVLv_F.js → arc-DLa3_7ua.js} +1 -1
  32. package/dist/client/assets/{architectureDiagram-T3A2C74G-D9IDzZ6u.js → architectureDiagram-T3A2C74G-BexwhnyX.js} +1 -1
  33. package/dist/client/assets/base-ui-CJ8ZIx3d.js +1 -0
  34. package/dist/client/assets/{blockDiagram-VBNYF7ZC-CzCatff9.js → blockDiagram-VBNYF7ZC-CoOttUUo.js} +1 -1
  35. package/dist/client/assets/{c4Diagram-5PPSVZJV-jvC7ACDE.js → c4Diagram-5PPSVZJV-Bgp6ppUh.js} +1 -1
  36. package/dist/client/assets/channel-1k1AYpyV.js +1 -0
  37. package/dist/client/assets/{checkbox-DDpujnre.js → checkbox-BRH0ZNIC.js} +1 -1
  38. package/dist/client/assets/{chunk-2GRJ4B5K-CNRn3w2y.js → chunk-2GRJ4B5K-C_kzp38w.js} +1 -1
  39. package/dist/client/assets/{chunk-2Q5K7J3B-CnTjXDS8.js → chunk-2Q5K7J3B-CysYnRkJ.js} +1 -1
  40. package/dist/client/assets/{chunk-5RXB4S5H-8QZZSU3J.js → chunk-5RXB4S5H-oMS6x_ag.js} +1 -1
  41. package/dist/client/assets/{chunk-5VM5RSS4-B2MFN1L0.js → chunk-5VM5RSS4-C5-dW5wf.js} +1 -1
  42. package/dist/client/assets/{chunk-6Q2QTUOP-BRGuFKSr.js → chunk-6Q2QTUOP-DxahInnA.js} +1 -1
  43. package/dist/client/assets/{chunk-GF5L2VYU-CWTKyD7U.js → chunk-GF5L2VYU-D7V-3eRg.js} +1 -1
  44. package/dist/client/assets/{chunk-JWPE2WC7-rGurufZY.js → chunk-JWPE2WC7-BrLmjbVP.js} +1 -1
  45. package/dist/client/assets/{chunk-KBJHAD2P-DOwIHyo0.js → chunk-KBJHAD2P-CyOvijwM.js} +1 -1
  46. package/dist/client/assets/{chunk-RYQCIY6F-qrVd0AV3.js → chunk-RYQCIY6F-BvEjVjQs.js} +1 -1
  47. package/dist/client/assets/{chunk-XXDRQBXY-BGvaybJK.js → chunk-XXDRQBXY-C_IY9A64.js} +1 -1
  48. package/dist/client/assets/classDiagram-JCYQIIEL-ChyNFpay.js +1 -0
  49. package/dist/client/assets/classDiagram-v2-OCEON4UE-ChyNFpay.js +1 -0
  50. package/dist/client/assets/{vb-D17OF-Vu.js → cmd-BW5THilD.js} +1 -1
  51. package/dist/client/assets/{cose-bilkent-JH36ORCC-CHgOt-vq.js → cose-bilkent-JH36ORCC-Cu7qZi7h.js} +1 -1
  52. package/dist/client/assets/{cynefin-VYW2F7L2-DX7Kb3Ft.js → cynefin-VYW2F7L2-B4M1t9lL.js} +1 -1
  53. package/dist/client/assets/{cynefinDiagram-MW4NZA55-B4MgYgbV.js → cynefinDiagram-MW4NZA55-CAosOLmT.js} +1 -1
  54. package/dist/client/assets/{dagre-VZM6K2ZE-xG9l95NU.js → dagre-VZM6K2ZE-q354V538.js} +1 -1
  55. package/dist/client/assets/{diagram-7IWD3JNH-BQKb8ivh.js → diagram-7IWD3JNH-C_CzkBPt.js} +1 -1
  56. package/dist/client/assets/{diagram-B4RE2ZJO-CyIMrtDr.js → diagram-B4RE2ZJO-VvvYHE9c.js} +1 -1
  57. package/dist/client/assets/{diagram-LBJQPF4R-WuCMu4WW.js → diagram-LBJQPF4R-D8vRQwb5.js} +1 -1
  58. package/dist/client/assets/{diagram-Q27KOJAE--Oi1nRrR.js → diagram-Q27KOJAE-Bul4aDKg.js} +1 -1
  59. package/dist/client/assets/{diagram-UB23O5K3-CP1DXsa_.js → diagram-UB23O5K3-DNadEE58.js} +1 -1
  60. package/dist/client/assets/{ebnfDiagram-BXEA7PRR-C-7jJg9C.js → ebnfDiagram-BXEA7PRR-Bf-68MMZ.js} +1 -1
  61. package/dist/client/assets/{erDiagram-JOGREHBK-FSyqgO_9.js → erDiagram-JOGREHBK-CJ-jHFsf.js} +1 -1
  62. package/dist/client/assets/{flowDiagram-UKHOOZJN-y2e2HZHu.js → flowDiagram-UKHOOZJN-DMOdinBU.js} +1 -1
  63. package/dist/client/assets/{ganttDiagram-PKOTCBZU-kZ3m3wwW.js → ganttDiagram-PKOTCBZU-DzezS3Zy.js} +1 -1
  64. package/dist/client/assets/{gitGraphDiagram-DS77QQ5N-3Z9e4WPi.js → gitGraphDiagram-DS77QQ5N-Dftpvns8.js} +1 -1
  65. package/dist/client/assets/index-BfA4tAc1.css +1 -0
  66. package/dist/client/assets/{index-DDbUjVD9.js → index-BzmFkR37.js} +1 -1
  67. package/dist/client/assets/{index-ChKJF4C_.js → index-DAUK3rnz.js} +4 -4
  68. package/dist/client/assets/{infoDiagram-6WML65LV-TfCo0Rhw.js → infoDiagram-6WML65LV-DmgUBq17.js} +1 -1
  69. package/dist/client/assets/{ishikawaDiagram-WSZJBQD7-DaXI89rw.js → ishikawaDiagram-WSZJBQD7-CL0PQhpQ.js} +1 -1
  70. package/dist/client/assets/{journeyDiagram-NVQOT4AX-C4jS6BVn.js → journeyDiagram-NVQOT4AX-Ck4pQx_A.js} +1 -1
  71. package/dist/client/assets/{kanban-definition-27J2QSJJ-bwOtQiC8.js → kanban-definition-27J2QSJJ-DuRZxBql.js} +1 -1
  72. package/dist/client/assets/{linear-DfZM9J6Z.js → linear-C2JDCaz4.js} +1 -1
  73. package/dist/client/assets/{mermaid.core-B-iLnCqi.js → mermaid.core-9dt2ugQA.js} +4 -4
  74. package/dist/client/assets/{mindmap-definition-FAOFIHXS-DAdKdypB.js → mindmap-definition-FAOFIHXS-vy0fdDF0.js} +1 -1
  75. package/dist/client/assets/{pegDiagram-VL7TDLO6-Bl95_ELH.js → pegDiagram-VL7TDLO6-DjVJy-Gm.js} +1 -1
  76. package/dist/client/assets/{pieDiagram-7S7Q4E2Y-DXf4GTLI.js → pieDiagram-7S7Q4E2Y-ATbd_8vw.js} +1 -1
  77. package/dist/client/assets/{platform-BMNQ8V5r.js → platform-Bqt97_Jr.js} +2 -2
  78. package/dist/client/assets/{quadrantDiagram-CIZ2JOQS-CDiwouFY.js → quadrantDiagram-CIZ2JOQS-C8o6ofcl.js} +1 -1
  79. package/dist/client/assets/{railroadDiagram-AXF67PYL-CZAR7rja.js → railroadDiagram-AXF67PYL-Qvij9Ubo.js} +1 -1
  80. package/dist/client/assets/render-markdown-BtCybfvQ.js +280 -0
  81. package/dist/client/assets/repository-grammars-ixqpb0uW.js +1 -0
  82. package/dist/client/assets/{requirementDiagram-LRYGKXZP-BklWtcnl.js → requirementDiagram-LRYGKXZP-yLfrba2h.js} +1 -1
  83. package/dist/client/assets/{sankeyDiagram-W5VNT64P-CpD7cM5j.js → sankeyDiagram-W5VNT64P-BTXRtTyG.js} +1 -1
  84. package/dist/client/assets/{sequenceDiagram-SI44F4Z6-BnHPNl9l.js → sequenceDiagram-SI44F4Z6-BN1CCZIX.js} +1 -1
  85. package/dist/client/assets/{sizeCapture-X5ZJPWSS-D1eo0r-2.js → sizeCapture-X5ZJPWSS-BfXjBBYo.js} +1 -1
  86. package/dist/client/assets/{stateDiagram-OKZ733FA-ClTacl-d.js → stateDiagram-OKZ733FA-DMAWiBsP.js} +1 -1
  87. package/dist/client/assets/stateDiagram-v2-UEYNNEHI-D_ofZeaa.js +1 -0
  88. package/dist/client/assets/{swimlanes-SLNWSIFB-_PLbkM5u.js → swimlanes-SLNWSIFB-BD20m5cw.js} +2 -2
  89. package/dist/client/assets/swimlanesDiagram-ULZ7WXOC-CF9m5JSB.js +8 -0
  90. package/dist/client/assets/{tanstack-CtfgE41X.js → tanstack-B0Sf7wNU.js} +1 -1
  91. package/dist/client/assets/{timeline-definition-Z64GVDOM-C9U6VikV.js → timeline-definition-Z64GVDOM-gtc0-AHB.js} +1 -1
  92. package/dist/client/assets/{vennDiagram-T6HMQDX7-Desj7KFU.js → vennDiagram-T6HMQDX7-Cdr4AXTp.js} +1 -1
  93. package/dist/client/assets/viewer-tabs-D-T0xQaq.js +1 -0
  94. package/dist/client/assets/{wardleyDiagram-T6FBY63Y-rlMEd5tV.js → wardleyDiagram-T6FBY63Y-BcU4vxO8.js} +1 -1
  95. package/dist/client/assets/{xychartDiagram-ELKLHX3M-CzFgA0NM.js → xychartDiagram-ELKLHX3M-B3Zuadj8.js} +1 -1
  96. package/dist/client/index.html +4 -4
  97. package/dist/dev.mjs +1 -4
  98. package/dist/manifest.json +1 -1
  99. package/dist/server.mjs +779 -2436
  100. package/dist/utility.mjs +1 -4
  101. package/package.json +2 -2
  102. package/dist/client/assets/App-CGiS2z40.js +0 -74
  103. package/dist/client/assets/CreatePullRequestDialog-dNbTTo9i.js +0 -2
  104. package/dist/client/assets/DiffWorkspace-XS025gbZ.js +0 -26
  105. package/dist/client/assets/EditableFileViewer-Cve4V9RS.js +0 -1
  106. package/dist/client/assets/File-BJuBDans.js +0 -1
  107. package/dist/client/assets/FilesView-BUAz46Qa.js +0 -1
  108. package/dist/client/assets/GitHubAvatar-Depnd_JA.js +0 -1
  109. package/dist/client/assets/HistoryView-Dd4Kr4KN.js +0 -1
  110. package/dist/client/assets/IconFolderPlus-BJIo1hfe.js +0 -1
  111. package/dist/client/assets/LocalRefsDialog-C1MWMkv1.js +0 -1
  112. package/dist/client/assets/MarkdownFileViewer-pagMxv2y.js +0 -2
  113. package/dist/client/assets/PierreDiffViewer-FmgbHdls.js +0 -1
  114. package/dist/client/assets/PullRequestViewer-oeDoq-3g.js +0 -2
  115. package/dist/client/assets/PullRequestsView-DpTtagTX.js +0 -1
  116. package/dist/client/assets/QuickOpenDialog-rjeV0l_J.js +0 -1
  117. package/dist/client/assets/RepositoryProjectsDialog-QYXEt7Lg.js +0 -1
  118. package/dist/client/assets/SearchView-C-c6oCil.js +0 -1
  119. package/dist/client/assets/SettingsDialog-DHmWkKYV.js +0 -9
  120. package/dist/client/assets/Viewer-B6qUr83C.js +0 -3
  121. package/dist/client/assets/base-ui-DT3JsyND.js +0 -1
  122. package/dist/client/assets/channel-BGbncBYv.js +0 -1
  123. package/dist/client/assets/classDiagram-JCYQIIEL-BoYriqPM.js +0 -1
  124. package/dist/client/assets/classDiagram-v2-OCEON4UE-BoYriqPM.js +0 -1
  125. package/dist/client/assets/gh-utils-DxPHIkD8.js +0 -1
  126. package/dist/client/assets/index-WwGOu_aM.css +0 -1
  127. package/dist/client/assets/render-markdown-BiKX2Zdv.js +0 -280
  128. package/dist/client/assets/stateDiagram-v2-UEYNNEHI-DAo7Tfh9.js +0 -1
  129. package/dist/client/assets/swimlanesDiagram-ULZ7WXOC-DHeOpgR_.js +0 -8
  130. package/dist/client/assets/viewer-tabs-BwMhis6Q.js +0 -1
package/dist/server.mjs CHANGED
@@ -1,24 +1,21 @@
1
1
  import { fileURLToPath, pathToFileURL } from "node:url";
2
2
  import path, { extname, delimiter, resolve, join, isAbsolute, dirname } from "node:path";
3
3
  import { randomUUID, randomBytes, createHash, createHmac, timingSafeEqual } from "node:crypto";
4
- import { access, mkdir, open, rename, chmod, rm, appendFile, readFile, mkdtemp, writeFile, lstat, readdir, realpath, stat, cp } from "node:fs/promises";
4
+ import { stat, access, realpath, mkdir, open, rename, chmod, rm, appendFile, readFile, mkdtemp, writeFile, lstat, readdir, cp } from "node:fs/promises";
5
+ import os, { constants, homedir, networkInterfaces } from "node:os";
5
6
  import { ChildProcess, execFile, spawnSync, spawn } from "node:child_process";
6
7
  import { StringDecoder } from "node:string_decoder";
7
8
  import { debuglog, stripVTControlCharacters, inspect, promisify, callbackify, aborted as aborted$1 } from "node:util";
8
9
  import process$3, { platform, hrtime, execPath, execArgv } from "node:process";
9
10
  import tty from "node:tty";
10
11
  import { setTimeout as setTimeout$1, scheduler, setImmediate } from "node:timers/promises";
11
- import os, { constants, homedir, networkInterfaces } from "node:os";
12
12
  import { once, addAbortListener, EventEmitter, on, setMaxListeners } from "node:events";
13
13
  import { serialize } from "node:v8";
14
- import fs, { openSync, readSync, closeSync, statSync, readFileSync, appendFileSync, writeFileSync, createWriteStream, createReadStream, watch } from "node:fs";
14
+ import fs, { openSync, readSync, closeSync, statSync, readFileSync, appendFileSync, writeFileSync, createWriteStream, createReadStream, constants as constants$1, watch } from "node:fs";
15
15
  import { Buffer as Buffer$1 } from "node:buffer";
16
16
  import { Transform, getDefaultHighWaterMark, Duplex, Writable, Readable, PassThrough } from "node:stream";
17
17
  import { finished } from "node:stream/promises";
18
18
  import path$1 from "node:path/win32";
19
- import require$$0$2 from "child_process";
20
- import require$$0$1 from "path";
21
- import require$$0 from "fs";
22
19
  import { createServer, isIP } from "node:net";
23
20
  import { createServer as createServer$1 } from "node:http";
24
21
  import WebSocket, { WebSocketServer } from "ws";
@@ -769,7 +766,7 @@ const handleCommand = (filePath, rawArguments, rawOptions) => {
769
766
  verboseInfo
770
767
  };
771
768
  };
772
- function pathKey$2(options = {}) {
769
+ function pathKey$1(options = {}) {
773
770
  const {
774
771
  env = process.env,
775
772
  platform: platform2 = process.platform
@@ -798,7 +795,7 @@ function traversePathUp(startPath) {
798
795
  }
799
796
  const npmRunPath = ({
800
797
  cwd = process$3.cwd(),
801
- path: pathOption = process$3.env[pathKey$2()],
798
+ path: pathOption = process$3.env[pathKey$1()],
802
799
  preferLocal = true,
803
800
  execPath: execPath2 = process$3.execPath,
804
801
  addExecPath = true
@@ -830,7 +827,7 @@ const applyExecPath = (result, pathParts, execPath2, cwdPath) => {
830
827
  };
831
828
  const npmRunPathEnv = ({ env = process$3.env, ...options } = {}) => {
832
829
  env = { ...env };
833
- const pathName = pathKey$2({ env });
830
+ const pathName = pathKey$1({ env });
834
831
  options.path = env[pathName];
835
832
  env[pathName] = npmRunPath(options);
836
833
  return env;
@@ -5262,7 +5259,7 @@ if (process.platform === "linux") {
5262
5259
  }
5263
5260
  const processOk = (process2) => !!process2 && typeof process2 === "object" && typeof process2.removeListener === "function" && typeof process2.emit === "function" && typeof process2.reallyExit === "function" && typeof process2.listeners === "function" && typeof process2.kill === "function" && typeof process2.pid === "number" && typeof process2.on === "function";
5264
5261
  const kExitEmitter = /* @__PURE__ */ Symbol.for("signal-exit emitter");
5265
- const global$1 = globalThis;
5262
+ const global = globalThis;
5266
5263
  const ObjectDefineProperty = Object.defineProperty.bind(Object);
5267
5264
  class Emitter {
5268
5265
  emitted = {
@@ -5276,10 +5273,10 @@ class Emitter {
5276
5273
  count = 0;
5277
5274
  id = Math.random();
5278
5275
  constructor() {
5279
- if (global$1[kExitEmitter]) {
5280
- return global$1[kExitEmitter];
5276
+ if (global[kExitEmitter]) {
5277
+ return global[kExitEmitter];
5281
5278
  }
5282
- ObjectDefineProperty(global$1, kExitEmitter, {
5279
+ ObjectDefineProperty(global, kExitEmitter, {
5283
5280
  value: this,
5284
5281
  writable: false,
5285
5282
  enumerable: false,
@@ -6875,6 +6872,153 @@ createExeca(() => ({ isSync: true }));
6875
6872
  createExeca(mapNode);
6876
6873
  createExeca(mapScriptAsync, {}, deepScriptOptions, setScriptSync);
6877
6874
  getIpcExport();
6875
+ const LOCATION_KEYS = ["PATH", "CODEX_INSTALL_DIR", "GROK_BIN_DIR", "BUN_INSTALL", "PNPM_HOME", "VOLTA_HOME", "NPM_CONFIG_PREFIX", "npm_config_prefix", "NVM_BIN", "FNM_MULTISHELL_PATH", "ASDF_DATA_DIR", "MISE_DATA_DIR", "XDG_DATA_HOME", "APPDATA", "LOCALAPPDATA"];
6876
+ function currentCliHost() {
6877
+ let shell;
6878
+ try {
6879
+ shell = os.userInfo().shell ?? void 0;
6880
+ } catch {
6881
+ }
6882
+ return { platform: process.platform, home: os.homedir(), env: cleanEnvironment(process.env), shell: process.env.SHELL || shell };
6883
+ }
6884
+ function cleanEnvironment(input, platform2 = process.platform) {
6885
+ const result = {};
6886
+ for (const [key, value] of Object.entries(input)) {
6887
+ if (value !== void 0) result[platform2 === "win32" && key.toUpperCase() === "PATH" ? "PATH" : key] = value;
6888
+ }
6889
+ return result;
6890
+ }
6891
+ function pathDirectories(value, platform2) {
6892
+ const p = platform2 === "win32" ? path.win32 : path.posix;
6893
+ const seen = /* @__PURE__ */ new Set();
6894
+ return (value ?? "").split(platform2 === "win32" ? ";" : ":").map((item) => item.replace(/^"(.*)"$/, "$1")).filter((item) => {
6895
+ if (!p.isAbsolute(item)) return false;
6896
+ const key = platform2 === "win32" ? p.normalize(item).toLowerCase() : p.normalize(item);
6897
+ if (seen.has(key)) return false;
6898
+ seen.add(key);
6899
+ return true;
6900
+ });
6901
+ }
6902
+ class CliEnvironment {
6903
+ constructor(host = currentCliHost(), read = readUserEnvironment) {
6904
+ this.host = host;
6905
+ this.read = read;
6906
+ }
6907
+ host;
6908
+ read;
6909
+ cached;
6910
+ pending;
6911
+ generation = 0;
6912
+ invalidate() {
6913
+ this.generation++;
6914
+ this.cached = void 0;
6915
+ this.pending = void 0;
6916
+ }
6917
+ async get(force = false) {
6918
+ const env = cleanEnvironment(this.host.env, this.host.platform);
6919
+ const inheritedPath = pathDirectories(env.PATH, this.host.platform);
6920
+ if (this.pending) return this.pending;
6921
+ if (!force && this.cached && Date.now() - this.cached.at < 3e4) return this.cached.value;
6922
+ const generation = this.generation;
6923
+ const pending = (async () => {
6924
+ let locations = {};
6925
+ let warning;
6926
+ try {
6927
+ locations = await this.read(this.host);
6928
+ } catch {
6929
+ warning = "Could not refresh the user environment. Using the inherited environment and known installation locations. Check your shell configuration or set an executable path.";
6930
+ }
6931
+ const refreshedPath = pathDirectories(locations.PATH, this.host.platform);
6932
+ const value = { env: { ...env, ...locations }, inheritedPath, refreshedPath, ...warning ? { warning } : {} };
6933
+ if (generation === this.generation) this.cached = { at: Date.now(), value };
6934
+ return value;
6935
+ })();
6936
+ this.pending = pending;
6937
+ try {
6938
+ return await pending;
6939
+ } finally {
6940
+ if (this.pending === pending) this.pending = void 0;
6941
+ }
6942
+ }
6943
+ }
6944
+ async function readUserEnvironment(host) {
6945
+ const options = {
6946
+ cwd: host.home,
6947
+ env: host.env,
6948
+ extendEnv: false,
6949
+ input: "",
6950
+ timeout: 5e3,
6951
+ maxBuffer: 128 * 1024,
6952
+ cleanup: true,
6953
+ killDescendants: true,
6954
+ windowsHide: true,
6955
+ reject: false,
6956
+ stripFinalNewline: false
6957
+ };
6958
+ if (host.platform === "win32") {
6959
+ const root = host.env.SystemRoot || host.env.SYSTEMROOT;
6960
+ if (!root || !path.win32.isAbsolute(root)) throw new Error("SystemRoot unavailable");
6961
+ const shell2 = path.win32.join(root, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
6962
+ const script = '$ErrorActionPreference="Stop"; $r=@{}; foreach($scope in @("Machine","User")){ $r[$scope]=[Environment]::GetEnvironmentVariables($scope) }; ConvertTo-Json -InputObject $r -Compress';
6963
+ 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");
6965
+ return windowsLocations(JSON.parse(result.stdout), host.env);
6966
+ }
6967
+ const shell = host.shell;
6968
+ if (!shell || !path.posix.isAbsolute(shell)) throw new Error("User shell unavailable");
6969
+ const kind = path.posix.basename(shell);
6970
+ if (!["bash", "zsh", "fish"].includes(kind)) throw new Error("Unsupported user shell");
6971
+ const started = Date.now();
6972
+ const modes = kind === "bash" ? ["-lic", "-ic"] : ["-lic"];
6973
+ let merged = {};
6974
+ let succeeded = false;
6975
+ for (const mode of modes) {
6976
+ const timeout = 5e3 - (Date.now() - started);
6977
+ if (timeout <= 0) break;
6978
+ const script = LOCATION_KEYS.map((key) => kind === "fish" ? `printf '\\0OPENTIG_ENV_${key}\\0%s\\0' (string join ':' -- $${key})` : `printf '\\0OPENTIG_ENV_${key}\\0%s\\0' "$${key}"`).join("; ");
6979
+ const result = await execa(shell, [mode, script], { ...options, timeout });
6980
+ if (result.exitCode !== 0 || result.timedOut || result.isMaxBuffer) continue;
6981
+ const locations = parseShellLocations(result.stdout);
6982
+ if (!locations.PATH) continue;
6983
+ succeeded = true;
6984
+ merged = { ...merged, ...locations, PATH: [locations.PATH, merged.PATH].filter(Boolean).join(":") };
6985
+ }
6986
+ if (!succeeded) throw new Error("Shell environment query failed");
6987
+ return merged;
6988
+ }
6989
+ function parseShellLocations(output) {
6990
+ const locations = {};
6991
+ for (const key of LOCATION_KEYS) {
6992
+ const marker = `\0OPENTIG_ENV_${key}\0`;
6993
+ const start = output.lastIndexOf(marker);
6994
+ if (start < 0) continue;
6995
+ const end = output.indexOf("\0", start + marker.length);
6996
+ if (end < 0) continue;
6997
+ const value = output.slice(start + marker.length, end);
6998
+ if (value && !/[\r\n]/.test(value)) locations[key] = value;
6999
+ }
7000
+ return locations;
7001
+ }
7002
+ function windowsLocations(raw, inherited) {
7003
+ const data = raw;
7004
+ if (!data || typeof data !== "object" || !data.Machine || !data.User) throw new Error("Invalid environment response");
7005
+ const upper = (input) => Object.fromEntries(Object.entries(input).filter(([, v]) => typeof v === "string").map(([k, v]) => [k.toUpperCase(), v]));
7006
+ const machine = upper(data.Machine), user = upper(data.User);
7007
+ const variables = { ...upper(inherited), ...machine, ...user };
7008
+ const expand = (input) => {
7009
+ let value = input;
7010
+ for (let i2 = 0; i2 < 10; i2++) {
7011
+ const next = value.replace(/%([^%]+)%/g, (match, key) => variables[key.toUpperCase()] ?? match);
7012
+ if (next === value) break;
7013
+ value = next;
7014
+ }
7015
+ return value;
7016
+ };
7017
+ const result = {};
7018
+ for (const key of LOCATION_KEYS) if (key !== "PATH" && variables[key.toUpperCase()]) result[key] = expand(variables[key.toUpperCase()]);
7019
+ result.PATH = [machine.PATH, user.PATH].filter((value) => !!value).map(expand).join(";");
7020
+ return result;
7021
+ }
6878
7022
  class GitOperationError extends Error {
6879
7023
  detail;
6880
7024
  constructor(detail) {
@@ -6942,17 +7086,18 @@ class CliProcessRunner {
6942
7086
  async run(command2, args, options = {}) {
6943
7087
  const timeoutMs = options.timeoutMs ?? 15e3;
6944
7088
  const maxOutputBytes = options.maxOutputBytes ?? 2 * 1024 * 1024;
6945
- const environment = Object.fromEntries(
7089
+ const environment = cleanEnvironment(Object.fromEntries(
6946
7090
  Object.entries({ ...process.env, ...options.env }).filter((entry) => entry[1] !== void 0)
6947
- );
7091
+ ));
6948
7092
  for (const key of options.removeEnv ?? []) delete environment[key];
6949
7093
  if (this.closePromise || options.signal?.aborted) throw cancelled();
6950
7094
  const lifecycle = new AbortController();
6951
7095
  this.active.add(lifecycle);
6952
7096
  const signal = options.signal ? AbortSignal.any([options.signal, lifecycle.signal]) : lifecycle.signal;
6953
- const resolvedCommand = resolveProcessCommand(command2, options.cwd ?? process.cwd(), environment);
7097
+ let resolvedCommand;
6954
7098
  let result;
6955
7099
  try {
7100
+ resolvedCommand = resolveProcessCommand(command2, options.cwd ?? process.cwd(), environment);
6956
7101
  result = await execa(resolvedCommand.file, args, {
6957
7102
  ...options.cwd ? { cwd: options.cwd } : {},
6958
7103
  cancelSignal: signal,
@@ -6967,9 +7112,9 @@ class CliProcessRunner {
6967
7112
  input: options.stdin ?? "",
6968
7113
  stripFinalNewline: false,
6969
7114
  reject: false
6970
- }).catch(() => {
7115
+ }).catch((error) => {
6971
7116
  if (signal.aborted) throw cancelled();
6972
- throw processFailed();
7117
+ throw processFailed(error);
6973
7118
  });
6974
7119
  } finally {
6975
7120
  this.active.delete(lifecycle);
@@ -6986,7 +7131,7 @@ class CliProcessRunner {
6986
7131
  throw new AiOperationError({ code: "AI_CONTEXT_TOO_LARGE", operation: "ai-process", message: "The AI tool produced too much output." });
6987
7132
  }
6988
7133
  if (result.exitCode === void 0 || !resolvedCommand.found) {
6989
- throw processFailed();
7134
+ throw processFailed(result);
6990
7135
  }
6991
7136
  return { exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr };
6992
7137
  }
@@ -7011,45 +7156,143 @@ class CliProcessRunner {
7011
7156
  function cancelled() {
7012
7157
  return new AiOperationError({ code: "AI_CANCELLED", operation: "ai-process", message: "Generation canceled." });
7013
7158
  }
7014
- function processFailed() {
7015
- return new AiOperationError({ code: "AI_PROCESS_FAILED", operation: "ai-process", message: "Could not start the AI tool." });
7159
+ class CliLaunchError extends AiOperationError {
7160
+ constructor(reason) {
7161
+ super({ code: "AI_PROCESS_FAILED", operation: "ai-process", message: "Could not start the AI tool." });
7162
+ this.reason = reason;
7163
+ }
7164
+ reason;
7165
+ }
7166
+ function processFailed(error) {
7167
+ const code = error && typeof error === "object" && "code" in error ? error.code : void 0;
7168
+ return new CliLaunchError(code === "ENOENT" ? "missing" : code === "EACCES" || code === "EPERM" ? "permission" : code === "ENOEXEC" ? "format" : "unknown");
7016
7169
  }
7017
- const ALLOWED = /* @__PURE__ */ new Set(["codex", "claude", "opencode", "gh"]);
7018
7170
  const ALIASES = {
7019
7171
  codex: ["codex"],
7020
7172
  claude: ["claude"],
7021
- // OpenCode 2 installs as opencode2 and does not replace OpenCode 1's opencode binary.
7022
7173
  opencode: ["opencode", "opencode2"],
7174
+ grok: ["grok"],
7023
7175
  gh: ["gh"]
7024
7176
  };
7025
7177
  class CliResolver {
7178
+ constructor(environment = new CliEnvironment(), preferences = () => ({ aiExecutablePaths: {} })) {
7179
+ this.environment = environment;
7180
+ this.preferences = preferences;
7181
+ }
7182
+ environment;
7183
+ preferences;
7026
7184
  cache = /* @__PURE__ */ new Map();
7027
- async resolve(name, forceRefresh = false) {
7028
- if (!ALLOWED.has(name)) return null;
7029
- if (!forceRefresh && this.cache.has(name)) return this.cache.get(name) ?? null;
7030
- let resolved = null;
7031
- for (const alias of ALIASES[name] ?? [name]) {
7032
- resolved = await findOnPath(alias);
7033
- if (resolved) break;
7034
- }
7035
- this.cache.set(name, resolved);
7036
- return resolved;
7185
+ settingsKey = "";
7186
+ generation = 0;
7187
+ invalidate() {
7188
+ this.generation++;
7189
+ this.cache.clear();
7190
+ this.environment.invalidate();
7037
7191
  }
7038
- }
7039
- async function findOnPath(name) {
7040
- const directories = (process.env.PATH ?? "").split(path.delimiter).filter(Boolean);
7041
- const extensions = process.platform === "win32" ? [".exe", ".cmd", ".bat"] : [""];
7042
- for (const extension of extensions) {
7043
- for (const directory of directories) {
7044
- const candidate = path.resolve(directory.replace(/^"|"$/g, ""), `${name}${extension}`);
7045
- try {
7046
- await access(candidate);
7047
- return candidate;
7048
- } catch {
7192
+ async resolve(name, forceRefresh = false) {
7193
+ return (await this.resolveAll(name, forceRefresh))[0] ?? null;
7194
+ }
7195
+ async resolveAll(name, forceRefresh = false) {
7196
+ return (await this.discover(name, forceRefresh)).filter((item) => !item.problem).map((item) => item.executable);
7197
+ }
7198
+ async discover(name, forceRefresh = false) {
7199
+ if (!Object.hasOwn(ALIASES, name)) return [];
7200
+ const preferences = this.preferences();
7201
+ const settingsKey = JSON.stringify(preferences.aiExecutablePaths);
7202
+ if (settingsKey !== this.settingsKey) {
7203
+ this.settingsKey = settingsKey;
7204
+ this.invalidate();
7205
+ }
7206
+ const key = name;
7207
+ if (forceRefresh) {
7208
+ this.generation++;
7209
+ this.cache.delete(key);
7210
+ }
7211
+ const generation = this.generation;
7212
+ const cached2 = this.cache.get(key);
7213
+ if (!forceRefresh && cached2 && Date.now() - cached2.at < 3e4) {
7214
+ if ((await Promise.all(cached2.candidates.map((item) => this.problem(item.executable)))).every((problem, index) => problem === cached2.candidates[index]?.problem)) {
7215
+ return structuredClone(cached2.candidates);
7216
+ }
7217
+ }
7218
+ const snapshot = await this.environment.get(forceRefresh);
7219
+ const host = this.environment.host;
7220
+ const p = host.platform === "win32" ? path.win32 : path.posix;
7221
+ const known = knownCliDirectories(name, host.home, host.platform, snapshot.env);
7222
+ const allKnown = [...new Set(Object.keys(ALIASES).flatMap((id) => knownCliDirectories(id, host.home, host.platform, snapshot.env)))];
7223
+ const runtimePath = host.platform === "win32" ? [...snapshot.inheritedPath, ...snapshot.refreshedPath] : [...snapshot.refreshedPath, ...snapshot.inheritedPath];
7224
+ const env = { ...snapshot.env, PATH: pathDirectories([...runtimePath, ...allKnown].join(p.delimiter), host.platform).join(p.delimiter) };
7225
+ if (host.env.XDG_DATA_HOME === void 0) delete env.XDG_DATA_HOME;
7226
+ else env.XDG_DATA_HOME = host.env.XDG_DATA_HOME;
7227
+ const configured = name === "gh" ? void 0 : preferences.aiExecutablePaths[name];
7228
+ const candidates = [];
7229
+ const seen = /* @__PURE__ */ new Set();
7230
+ const add = async (executable, alias, source) => {
7231
+ const problem = await this.problem(executable);
7232
+ if (problem === "not-found" && source !== "configured") return;
7233
+ const canonical = await realpath(executable).catch(() => executable);
7234
+ const identity2 = host.platform === "win32" ? canonical.toLowerCase() : canonical;
7235
+ if (seen.has(identity2)) return;
7236
+ seen.add(identity2);
7237
+ candidates.push({ executable, alias, source, env, ...problem ? { problem } : {}, ...snapshot.warning ? { warning: snapshot.warning } : {} });
7238
+ };
7239
+ if (configured) {
7240
+ await add(configured, p.basename(configured), "configured");
7241
+ } else {
7242
+ for (const [directories, source] of [[snapshot.inheritedPath, "process-path"], [snapshot.refreshedPath, "user-path"], [known, "known-location"]]) {
7243
+ for (const directory of directories) {
7244
+ for (const alias of ALIASES[name]) {
7245
+ for (const extension of executableExtensions(host.platform, snapshot.env)) await add(p.join(directory, `${alias}${extension}`), alias, source);
7246
+ }
7247
+ }
7049
7248
  }
7050
7249
  }
7250
+ if (generation === this.generation) this.cache.set(key, { at: Date.now(), candidates });
7251
+ return structuredClone(candidates);
7051
7252
  }
7052
- return null;
7253
+ async warning() {
7254
+ return (await this.environment.get()).warning;
7255
+ }
7256
+ async problem(executable) {
7257
+ try {
7258
+ if (!(await stat(executable)).isFile()) return "not-executable";
7259
+ if (this.environment.host.platform === "win32") {
7260
+ if (![".exe", ".com", ".cmd", ".bat"].includes(path.win32.extname(executable).toLowerCase())) return "not-executable";
7261
+ } else await access(executable, constants$1.X_OK);
7262
+ return void 0;
7263
+ } catch (error) {
7264
+ return error.code === "EACCES" ? "not-executable" : "not-found";
7265
+ }
7266
+ }
7267
+ }
7268
+ function executableExtensions(platform2, env) {
7269
+ if (platform2 !== "win32") return [""];
7270
+ const value = Object.entries(env).find(([key]) => key.toUpperCase() === "PATHEXT")?.[1] ?? ".COM;.EXE;.BAT;.CMD";
7271
+ return [...new Set(value.split(";").map((item) => item.toLowerCase()).filter((item) => [".exe", ".com", ".cmd", ".bat"].includes(item)))];
7272
+ }
7273
+ function knownCliDirectories(name, home, platform2, env) {
7274
+ const p = platform2 === "win32" ? path.win32 : path.posix;
7275
+ const dirs = [p.join(home, ".local", "bin")];
7276
+ if (name === "opencode") dirs.push(p.join(home, ".opencode", "bin"));
7277
+ if (name === "grok") dirs.push(env.GROK_BIN_DIR, p.join(home, ".grok", "bin"));
7278
+ if (name === "codex") dirs.push(env.CODEX_INSTALL_DIR);
7279
+ dirs.push(env.PNPM_HOME, env.NVM_BIN, p.join(env.BUN_INSTALL || p.join(home, ".bun"), "bin"));
7280
+ if (env.FNM_MULTISHELL_PATH) dirs.push(platform2 === "win32" ? env.FNM_MULTISHELL_PATH : p.join(env.FNM_MULTISHELL_PATH, "bin"));
7281
+ const prefix = env.npm_config_prefix || env.NPM_CONFIG_PREFIX;
7282
+ if (prefix) dirs.push(platform2 === "win32" ? prefix : p.join(prefix, "bin"));
7283
+ if (platform2 === "win32") {
7284
+ if (env.APPDATA) dirs.push(p.join(env.APPDATA, "npm"));
7285
+ if (env.LOCALAPPDATA) {
7286
+ dirs.push(p.join(env.LOCALAPPDATA, "Volta", "bin"), p.join(env.LOCALAPPDATA, "pnpm"), p.join(env.LOCALAPPDATA, "Microsoft", "WinGet", "Links"));
7287
+ if (name === "codex") dirs.push(p.join(env.LOCALAPPDATA, "Programs", "OpenAI", "Codex", "bin"));
7288
+ }
7289
+ if (env.VOLTA_HOME) dirs.push(p.join(env.VOLTA_HOME, "bin"));
7290
+ } else {
7291
+ dirs.push(p.join(env.VOLTA_HOME || p.join(home, ".volta"), "bin"), p.join(home, ".npm-global", "bin"), p.join(home, ".yarn", "bin"));
7292
+ dirs.push(p.join(env.XDG_DATA_HOME || p.join(home, ".local", "share"), "pnpm"));
7293
+ dirs.push("/usr/local/bin", "/usr/bin", "/bin", platform2 === "darwin" ? "/opt/homebrew/bin" : "/home/linuxbrew/.linuxbrew/bin");
7294
+ }
7295
+ return [...new Set(dirs.filter((item) => !!item && p.isAbsolute(item)))];
7053
7296
  }
7054
7297
  var _a$1;
7055
7298
  function $constructor(name, initializer2, params) {
@@ -7286,7 +7529,7 @@ const NUMBER_FORMAT_RANGES = {
7286
7529
  float32: [-34028234663852886e22, 34028234663852886e22],
7287
7530
  float64: [-Number.MAX_VALUE, Number.MAX_VALUE]
7288
7531
  };
7289
- function pick$1(schema, mask) {
7532
+ function pick(schema, mask) {
7290
7533
  const currDef = schema._zod.def;
7291
7534
  const checks = currDef.checks;
7292
7535
  const hasChecks = checks && checks.length > 0;
@@ -11529,7 +11772,7 @@ const ZodObject = /* @__PURE__ */ $constructor("ZodObject", (inst, def) => {
11529
11772
  return merge(this, other);
11530
11773
  },
11531
11774
  pick(mask) {
11532
- return pick$1(this, mask);
11775
+ return pick(this, mask);
11533
11776
  },
11534
11777
  omit(mask) {
11535
11778
  return omit(this, mask);
@@ -11854,7 +12097,7 @@ const EMPTY_AI_USAGE = {
11854
12097
  };
11855
12098
  const OPERATIONS = ["commit-message", "pull-request-draft"];
11856
12099
  const STATUSES = ["success", "failed", "cancelled"];
11857
- const HARNESSES = ["codex", "claude", "opencode"];
12100
+ const HARNESSES = ["codex", "claude", "opencode", "grok"];
11858
12101
  const MAX_TEXT$1 = 200;
11859
12102
  const MAX_ERROR_TEXT = 400;
11860
12103
  const aiUsageRecordSchema = looseObject({
@@ -12134,7 +12377,7 @@ function parseGeneratedParts(value) {
12134
12377
  const parsed = generatedPartsSchema.safeParse(value);
12135
12378
  if (!parsed.success || `${parsed.data.subject}
12136
12379
 
12137
- ${parsed.data.body}`.length > 1e4) throw invalidOutput$3();
12380
+ ${parsed.data.body}`.length > 1e4) throw invalidOutput$4();
12138
12381
  return parsed.data;
12139
12382
  }
12140
12383
  function parseCommitSplitProposal(value, stagedPaths) {
@@ -12194,14 +12437,14 @@ function parseJsonPayload(raw) {
12194
12437
  if (nested instanceof AiOperationError) throw nested;
12195
12438
  }
12196
12439
  }
12197
- throw invalidOutput$3();
12440
+ throw invalidOutput$4();
12198
12441
  }
12199
12442
  }
12200
12443
  function asRecord(value) {
12201
- if (!value || typeof value !== "object" || Array.isArray(value)) throw invalidOutput$3();
12444
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw invalidOutput$4();
12202
12445
  return value;
12203
12446
  }
12204
- function invalidOutput$3() {
12447
+ function invalidOutput$4() {
12205
12448
  return new AiOperationError({ code: "AI_INVALID_OUTPUT", operation: "ai-parse-output", message: "The AI returned a message with an invalid format.", retryable: true });
12206
12449
  }
12207
12450
  function hasControlCharacters$6(value, allowWhitespace) {
@@ -12215,22 +12458,58 @@ function cleanText(value, maxLength) {
12215
12458
  const text2 = value.trim();
12216
12459
  return !text2 || text2.length > maxLength || hasControlCharacters$6(text2, true) ? null : text2;
12217
12460
  }
12461
+ const DEFAULT_MODEL$1 = { id: "default", label: "Default (CLI)" };
12218
12462
  class CommitMessageService {
12219
- constructor(operations, providers, log) {
12463
+ constructor(operations, providers, log, invalidateDiscovery) {
12220
12464
  this.operations = operations;
12221
12465
  this.log = log;
12466
+ this.invalidateDiscovery = invalidateDiscovery;
12222
12467
  for (const provider of providers) this.providers.set(provider.id, provider);
12223
12468
  }
12224
12469
  operations;
12225
12470
  log;
12471
+ invalidateDiscovery;
12226
12472
  providers = /* @__PURE__ */ new Map();
12227
12473
  active = /* @__PURE__ */ new Map();
12228
12474
  statusCache = null;
12475
+ statusGeneration = 0;
12476
+ pendingStatus;
12477
+ invalidateStatuses() {
12478
+ this.statusGeneration++;
12479
+ this.statusCache = null;
12480
+ this.pendingStatus = void 0;
12481
+ this.invalidateDiscovery?.();
12482
+ }
12229
12483
  async statuses(forceRefresh = false) {
12230
12484
  if (!forceRefresh && this.statusCache && Date.now() - this.statusCache.at < 3e4) return this.statusCache.value;
12231
- const value = await Promise.all([...this.providers.values()].map((provider) => provider.status(forceRefresh)));
12232
- this.statusCache = { at: Date.now(), value };
12233
- return value;
12485
+ if (this.pendingStatus && (!forceRefresh || this.pendingStatus.force)) return this.pendingStatus.promise;
12486
+ 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
+ };
12502
+ }
12503
+ })).then((value) => {
12504
+ if (generation === this.statusGeneration) this.statusCache = { at: Date.now(), value };
12505
+ return value;
12506
+ });
12507
+ this.pendingStatus = { force: forceRefresh, promise };
12508
+ try {
12509
+ return await promise;
12510
+ } finally {
12511
+ if (this.pendingStatus?.promise === promise) this.pendingStatus = void 0;
12512
+ }
12234
12513
  }
12235
12514
  async generate(input, externalSignal) {
12236
12515
  if (this.active.has(input.requestId) || [...this.active.values()].some((entry) => entry.repositoryId === input.repositoryId) || this.active.size >= 3) {
@@ -12293,7 +12572,7 @@ ${parts.body}` : parts.subject,
12293
12572
  splitBlockedReason: context.splitBlockedReason
12294
12573
  };
12295
12574
  } catch (error) {
12296
- const failure = failureLogFields(error);
12575
+ const failure2 = failureLogFields(error);
12297
12576
  recordSafely(this.log, {
12298
12577
  operation: "commit-message",
12299
12578
  harness: input.harness,
@@ -12301,10 +12580,10 @@ ${parts.body}` : parts.subject,
12301
12580
  repositoryId: input.repositoryId,
12302
12581
  // A cancellation is a decision, not a failure; merging the two would
12303
12582
  // poison any reliability figure taken from this log.
12304
- status: failure.status,
12583
+ status: failure2.status,
12305
12584
  durationMs: Date.now() - startedAt,
12306
- errorCode: failure.errorCode,
12307
- errorMessage: failure.errorMessage,
12585
+ errorCode: failure2.errorCode,
12586
+ errorMessage: failure2.errorMessage,
12308
12587
  usage,
12309
12588
  stagedFileCount,
12310
12589
  contextTruncated,
@@ -12375,10 +12654,10 @@ ${context.patch}`;
12375
12654
  }
12376
12655
  function parsePullRequestDraft(value) {
12377
12656
  const parsed = pullRequestDraftSchema.safeParse(value);
12378
- if (!parsed.success) throw invalidOutput$2();
12657
+ if (!parsed.success) throw invalidOutput$3();
12379
12658
  return parsed.data;
12380
12659
  }
12381
- function invalidOutput$2() {
12660
+ function invalidOutput$3() {
12382
12661
  return new AiOperationError({ code: "AI_INVALID_OUTPUT", operation: "ai-parse-pr-draft", message: "The AI returned a pull request draft with an invalid format.", retryable: true });
12383
12662
  }
12384
12663
  function hasControlCharacters$5(value, allowWhitespace) {
@@ -12428,8 +12707,8 @@ class PullRequestDraftService {
12428
12707
  this.record(input, "success", null, null, usage, contextTruncated, Date.now() - startedAt);
12429
12708
  return { ...parts, harness: input.harness, model: input.model, contextWasTruncated: context.truncated };
12430
12709
  } catch (error) {
12431
- const failure = failureLogFields(error);
12432
- this.record(input, failure.status, failure.errorCode, failure.errorMessage, usage, contextTruncated, Date.now() - startedAt);
12710
+ const failure2 = failureLogFields(error);
12711
+ this.record(input, failure2.status, failure2.errorCode, failure2.errorMessage, usage, contextTruncated, Date.now() - startedAt);
12433
12712
  throw error;
12434
12713
  } finally {
12435
12714
  this.active.delete(input.requestId);
@@ -12467,9 +12746,66 @@ function throwIfCancelled(signal, harness) {
12467
12746
  if (!signal?.aborted) return;
12468
12747
  throw new AiOperationError({ code: "AI_CANCELLED", operation: "ai-pr-draft", harness, message: "Generation canceled." });
12469
12748
  }
12749
+ function runCandidate(runner, candidate, args, options = {}) {
12750
+ return runner.run(candidate.executable, args, { ...options, env: { ...candidate.env, ...options.env } });
12751
+ }
12752
+ async function selectCli(resolver, runner, name, options = {}) {
12753
+ let first;
12754
+ const candidates = await resolver.discover(name, options.forceRefresh);
12755
+ for (const candidate of candidates) {
12756
+ if (options.runOptions?.signal?.aborted) throw new AiOperationError({ code: "AI_CANCELLED", operation: "cli-discovery", message: "Generation canceled." });
12757
+ if (candidate.problem) {
12758
+ first ??= { candidate, state: candidate.problem };
12759
+ continue;
12760
+ }
12761
+ try {
12762
+ const result = await runCandidate(runner, candidate, ["--version"], options.runOptions);
12763
+ const raw = result.stdout.trim() || result.stderr.trim();
12764
+ const version2 = (raw.split(/\r?\n/)[0] ?? "").slice(0, 200);
12765
+ if (result.exitCode !== 0 || !version2) {
12766
+ first ??= { candidate, state: "not-executable" };
12767
+ continue;
12768
+ }
12769
+ if (options.compatible && !options.compatible(raw)) {
12770
+ first ??= { candidate, version: version2, state: "incompatible" };
12771
+ continue;
12772
+ }
12773
+ return { candidate, version: version2, state: "available", warning: candidate.warning };
12774
+ } catch (error) {
12775
+ if (options.runOptions?.signal?.aborted) throw error;
12776
+ first ??= { candidate, state: "not-executable" };
12777
+ }
12778
+ }
12779
+ const warning = await resolver.warning();
12780
+ return first ? { ...first, warning } : { state: warning ? "inspection-failed" : "not-found", warning };
12781
+ }
12782
+ function detectionFields(detected) {
12783
+ return { installationStatus: detected.state, ...detected.candidate ? { executablePath: detected.candidate.executable, executableSource: detected.candidate.source } : {}, ...detected.warning ? { discoveryWarning: detected.warning } : {} };
12784
+ }
12785
+ function detectionFailure(detected, base, unsupported) {
12786
+ if (detected.state === "available") return null;
12787
+ const message = detected.state === "not-found" ? detected.candidate?.source === "configured" ? "The configured executable was not found. Correct the path or reset it to Automatic." : `Install ${base.label} and check again, or set its executable path.` : detected.state === "incompatible" ? unsupported ?? "The installed CLI version is not supported." : detected.state === "inspection-failed" ? "Could not inspect the CLI installation. Check again or set its executable path." : "The CLI was found but could not run. Check executable permissions, runtime dependencies, and system compatibility.";
12788
+ return { ...base, ...detectionFields(detected), installed: !!detected.candidate && detected.state !== "not-found", availability: "error", authStatus: "unknown", ...detected.version ? { version: detected.version } : {}, message };
12789
+ }
12790
+ function requireCandidate(detected, name) {
12791
+ if (detected.state !== "available" || !detected.candidate) throw new AiOperationError({ code: detected.state === "not-found" ? "AI_CLI_NOT_FOUND" : "AI_PROCESS_FAILED", operation: "cli-discovery", message: `${name} could not be started. Check its status in Settings → AI assistance.` });
12792
+ return detected.candidate;
12793
+ }
12470
12794
  const AI_PROVIDER_TIMEOUT_MS = 9 * 6e4;
12471
12795
  const AI_SERVER_TIMEOUT_MS = AI_PROVIDER_TIMEOUT_MS + 3e4;
12472
- const DEFAULT_MODEL = { id: "default", label: "Default (CLI)" };
12796
+ function grokUsage(envelope) {
12797
+ if (!envelope || typeof envelope !== "object") return { ...EMPTY_AI_USAGE };
12798
+ const record2 = envelope;
12799
+ const usage = record2.usage && typeof record2.usage === "object" ? record2.usage : {};
12800
+ return {
12801
+ inputTokens: count(usage.input_tokens),
12802
+ outputTokens: count(usage.output_tokens),
12803
+ reasoningTokens: count(usage.reasoning_tokens),
12804
+ cacheReadTokens: count(usage.cache_read_input_tokens),
12805
+ cacheWriteTokens: count(usage.cache_creation_input_tokens),
12806
+ costUsd: count(record2.total_cost_usd, true)
12807
+ };
12808
+ }
12473
12809
  function claudeUsage(envelope) {
12474
12810
  if (!envelope || typeof envelope !== "object") return { ...EMPTY_AI_USAGE };
12475
12811
  const record2 = envelope;
@@ -12507,20 +12843,6 @@ function codexUsage(stdout) {
12507
12843
  costUsd: null
12508
12844
  };
12509
12845
  }
12510
- function openCodeUsage(info) {
12511
- if (!info || typeof info !== "object") return { ...EMPTY_AI_USAGE };
12512
- const record2 = info;
12513
- const tokens = record2.tokens ?? {};
12514
- const cache = tokens.cache ?? {};
12515
- return {
12516
- inputTokens: count(tokens.input),
12517
- outputTokens: count(tokens.output),
12518
- reasoningTokens: count(tokens.reasoning),
12519
- cacheReadTokens: count(cache.read),
12520
- cacheWriteTokens: count(cache.write),
12521
- costUsd: count(record2.cost, true)
12522
- };
12523
- }
12524
12846
  function count(value, fractional = false) {
12525
12847
  if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return null;
12526
12848
  return fractional ? value : Math.round(value);
@@ -12569,7 +12891,7 @@ ${diagnostic ?? ""}`.toLowerCase();
12569
12891
  });
12570
12892
  }
12571
12893
  function label(harness) {
12572
- return harness === "codex" ? "Codex" : harness === "claude" ? "Claude Code" : "OpenCode";
12894
+ return harness === "codex" ? "Codex" : harness === "claude" ? "Claude Code" : harness === "grok" ? "Grok Build" : "OpenCode";
12573
12895
  }
12574
12896
  function stripAnsi(value) {
12575
12897
  return value.replace(new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g"), "");
@@ -12663,7 +12985,7 @@ function flattenWhitespace(value) {
12663
12985
  }
12664
12986
  return flattened.replace(/ {2,}/g, " ").trim();
12665
12987
  }
12666
- const MODELS = [DEFAULT_MODEL, { id: "sonnet", label: "Sonnet" }, { id: "opus", label: "Opus" }, { id: "haiku", label: "Haiku" }];
12988
+ const MODELS = [DEFAULT_MODEL$1, { id: "sonnet", label: "Sonnet" }, { id: "opus", label: "Opus" }, { id: "haiku", label: "Haiku" }];
12667
12989
  class ClaudeProvider {
12668
12990
  constructor(resolver, runner) {
12669
12991
  this.resolver = resolver;
@@ -12674,10 +12996,11 @@ class ClaudeProvider {
12674
12996
  id = "claude";
12675
12997
  async status(forceRefresh = false) {
12676
12998
  const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
12677
- const executable = await this.resolver.resolve("claude", forceRefresh);
12678
- if (!executable) return { id: this.id, label: "Claude Code", availability: "error", installed: false, authStatus: "unknown", message: "Install Claude Code to use this harness.", models: MODELS, checkedAt };
12679
- const version2 = await this.runner.run(executable, ["--version"]);
12680
- const auth = await this.runner.run(executable, ["auth", "status", "--json"]);
12999
+ const detected = await selectCli(this.resolver, this.runner, "claude", { forceRefresh });
13000
+ const failure2 = detectionFailure(detected, { id: this.id, label: "Claude Code", models: MODELS, checkedAt });
13001
+ if (failure2) return failure2;
13002
+ const executable = requireCandidate(detected, this.id);
13003
+ const auth = await runCandidate(this.runner, executable, ["auth", "status", "--json"]);
12681
13004
  let loggedIn;
12682
13005
  try {
12683
13006
  loggedIn = auth.exitCode === 0 && JSON.parse(auth.stdout).loggedIn === true;
@@ -12685,23 +13008,23 @@ class ClaudeProvider {
12685
13008
  loggedIn = false;
12686
13009
  }
12687
13010
  return {
13011
+ ...detectionFields(detected),
12688
13012
  id: this.id,
12689
13013
  label: "Claude Code",
12690
13014
  availability: loggedIn ? "ready" : "error",
12691
13015
  installed: true,
12692
13016
  authStatus: loggedIn ? "authenticated" : "unauthenticated",
12693
- version: version2.stdout.trim() || version2.stderr.trim(),
13017
+ version: detected.version ?? "",
12694
13018
  ...!loggedIn ? { message: "Run claude auth login." } : {},
12695
13019
  models: MODELS,
12696
13020
  checkedAt
12697
13021
  };
12698
13022
  }
12699
13023
  async generate(input) {
12700
- const executable = await this.resolver.resolve("claude");
12701
- if (!executable) throw new AiOperationError({ code: "AI_CLI_NOT_FOUND", operation: "claude-generate", harness: this.id, message: "Claude Code is not installed." });
13024
+ const executable = requireCandidate(await selectCli(this.resolver, this.runner, "claude", { runOptions: { signal: input.signal } }), this.id);
12702
13025
  const args = ["-p", "--output-format", "json", "--json-schema", JSON.stringify(input.schema), "--tools", "", "--no-session-persistence", "--safe-mode"];
12703
13026
  if (input.model !== "default") args.push("--model", input.model);
12704
- const result = await this.runner.run(executable, args, { cwd: input.repositoryPath, stdin: input.prompt, timeoutMs: AI_PROVIDER_TIMEOUT_MS, signal: input.signal, removeEnv: ["ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN"] });
13027
+ const result = await runCandidate(this.runner, executable, args, { cwd: input.repositoryPath, stdin: input.prompt, timeoutMs: AI_PROVIDER_TIMEOUT_MS, signal: input.signal, removeEnv: ["ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN"] });
12705
13028
  requireSuccess(result, this.id, "claude-generate");
12706
13029
  try {
12707
13030
  const envelope = JSON.parse(result.stdout);
@@ -12724,31 +13047,32 @@ class CodexProvider {
12724
13047
  id = "codex";
12725
13048
  async status(forceRefresh = false) {
12726
13049
  const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
12727
- const executable = await this.resolver.resolve("codex", forceRefresh);
12728
- if (!executable) return { id: this.id, label: "Codex", availability: "error", installed: false, authStatus: "unknown", message: "Install Codex CLI to use this harness.", models: [DEFAULT_MODEL], checkedAt };
12729
- const version2 = await this.runner.run(executable, ["--version"]);
12730
- const login = await this.runner.run(executable, ["login", "status"]);
13050
+ const detected = await selectCli(this.resolver, this.runner, "codex", { forceRefresh });
13051
+ const failure2 = detectionFailure(detected, { id: this.id, label: "Codex", models: [DEFAULT_MODEL$1], checkedAt });
13052
+ if (failure2) return failure2;
13053
+ const executable = requireCandidate(detected, this.id);
13054
+ const login = await runCandidate(this.runner, executable, ["login", "status"]);
12731
13055
  if (login.exitCode !== 0 || !/logged in/i.test(`${login.stdout}
12732
13056
  ${login.stderr}`)) {
12733
- return { id: this.id, label: "Codex", availability: "error", installed: true, authStatus: "unauthenticated", version: version2.stdout.trim() || version2.stderr.trim(), message: "Run codex login.", models: [DEFAULT_MODEL], checkedAt };
13057
+ 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 };
12734
13058
  }
12735
- const catalog = await this.runner.run(executable, ["debug", "models", "--bundled"]);
12736
- const models = catalog.exitCode === 0 ? parseCodexModels(catalog.stdout) : [DEFAULT_MODEL];
13059
+ const catalog = await runCandidate(this.runner, executable, ["debug", "models", "--bundled"]);
13060
+ const models = catalog.exitCode === 0 ? parseCodexModels(catalog.stdout) : [DEFAULT_MODEL$1];
12737
13061
  return {
13062
+ ...detectionFields(detected),
12738
13063
  id: this.id,
12739
13064
  label: "Codex",
12740
13065
  availability: models.length > 1 ? "ready" : "warning",
12741
13066
  installed: true,
12742
13067
  authStatus: "authenticated",
12743
- version: version2.stdout.trim() || version2.stderr.trim(),
13068
+ version: detected.version ?? "",
12744
13069
  ...models.length > 1 ? {} : { message: "Available; the model catalog could not be verified." },
12745
13070
  models,
12746
13071
  checkedAt
12747
13072
  };
12748
13073
  }
12749
13074
  async generate(input) {
12750
- const executable = await this.resolver.resolve("codex");
12751
- if (!executable) throw new AiOperationError({ code: "AI_CLI_NOT_FOUND", operation: "codex-generate", harness: this.id, message: "Codex is not installed." });
13075
+ const executable = requireCandidate(await selectCli(this.resolver, this.runner, "codex", { runOptions: { signal: input.signal } }), this.id);
12752
13076
  const temporary = await mkdtemp(path.join(os.tmpdir(), "opentig-codex-"));
12753
13077
  const schemaPath = path.join(temporary, "schema.json");
12754
13078
  const outputPath = path.join(temporary, "output.json");
@@ -12757,7 +13081,7 @@ ${login.stderr}`)) {
12757
13081
  const args = ["exec", "--json", "--ephemeral", "--skip-git-repo-check", "-s", "read-only"];
12758
13082
  if (input.model !== "default") args.push("--model", input.model);
12759
13083
  args.push("--config", 'model_reasoning_effort="low"', "--output-schema", schemaPath, "--output-last-message", outputPath, "-");
12760
- const result = await this.runner.run(executable, args, { cwd: input.repositoryPath, stdin: input.prompt, timeoutMs: AI_PROVIDER_TIMEOUT_MS, signal: input.signal, removeEnv: ["OPENAI_API_KEY"] });
13084
+ const result = await runCandidate(this.runner, executable, args, { cwd: input.repositoryPath, stdin: input.prompt, timeoutMs: AI_PROVIDER_TIMEOUT_MS, signal: input.signal, removeEnv: ["OPENAI_API_KEY"] });
12761
13085
  requireSuccess(result, this.id, "codex-generate");
12762
13086
  return { output: parseJsonPayload(await readFile(outputPath, "utf8")), usage: codexUsage(result.stdout) };
12763
13087
  } finally {
@@ -12769,2254 +13093,219 @@ function parseCodexModels(raw) {
12769
13093
  try {
12770
13094
  const parsed = JSON.parse(raw);
12771
13095
  const options = (parsed.models ?? []).filter((item) => typeof item.slug === "string" && item.visibility !== "hidden").map((item) => ({ id: item.slug, label: typeof item.display_name === "string" ? item.display_name : item.slug }));
12772
- return [DEFAULT_MODEL, ...options];
13096
+ return [DEFAULT_MODEL$1, ...options];
12773
13097
  } catch {
12774
- return [DEFAULT_MODEL];
12775
- }
12776
- }
12777
- const createSseClient = ({ onSseError, onSseEvent, responseTransformer, responseValidator, sseDefaultRetryDelay, sseMaxRetryAttempts, sseMaxRetryDelay, sseSleepFn, url, ...options }) => {
12778
- let lastEventId;
12779
- const sleep = sseSleepFn ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms)));
12780
- const createStream = async function* () {
12781
- let retryDelay = sseDefaultRetryDelay ?? 3e3;
12782
- let attempt = 0;
12783
- const signal = options.signal ?? new AbortController().signal;
12784
- while (true) {
12785
- if (signal.aborted)
12786
- break;
12787
- attempt++;
12788
- const headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers);
12789
- if (lastEventId !== void 0) {
12790
- headers.set("Last-Event-ID", lastEventId);
12791
- }
12792
- try {
12793
- const response = await fetch(url, { ...options, headers, signal });
12794
- if (!response.ok)
12795
- throw new Error(`SSE failed: ${response.status} ${response.statusText}`);
12796
- if (!response.body)
12797
- throw new Error("No body in SSE response");
12798
- const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
12799
- let buffer = "";
12800
- const abortHandler = () => {
12801
- try {
12802
- void reader.cancel();
12803
- } catch {
12804
- }
12805
- };
12806
- signal.addEventListener("abort", abortHandler);
12807
- try {
12808
- while (true) {
12809
- const { done, value } = await reader.read();
12810
- if (done)
12811
- break;
12812
- buffer += value;
12813
- const chunks = buffer.split("\n\n");
12814
- buffer = chunks.pop() ?? "";
12815
- for (const chunk of chunks) {
12816
- const lines = chunk.split("\n");
12817
- const dataLines = [];
12818
- let eventName;
12819
- for (const line of lines) {
12820
- if (line.startsWith("data:")) {
12821
- dataLines.push(line.replace(/^data:\s*/, ""));
12822
- } else if (line.startsWith("event:")) {
12823
- eventName = line.replace(/^event:\s*/, "");
12824
- } else if (line.startsWith("id:")) {
12825
- lastEventId = line.replace(/^id:\s*/, "");
12826
- } else if (line.startsWith("retry:")) {
12827
- const parsed = Number.parseInt(line.replace(/^retry:\s*/, ""), 10);
12828
- if (!Number.isNaN(parsed)) {
12829
- retryDelay = parsed;
12830
- }
12831
- }
12832
- }
12833
- let data;
12834
- let parsedJson = false;
12835
- if (dataLines.length) {
12836
- const rawData = dataLines.join("\n");
12837
- try {
12838
- data = JSON.parse(rawData);
12839
- parsedJson = true;
12840
- } catch {
12841
- data = rawData;
12842
- }
12843
- }
12844
- if (parsedJson) {
12845
- if (responseValidator) {
12846
- await responseValidator(data);
12847
- }
12848
- if (responseTransformer) {
12849
- data = await responseTransformer(data);
12850
- }
12851
- }
12852
- onSseEvent?.({
12853
- data,
12854
- event: eventName,
12855
- id: lastEventId,
12856
- retry: retryDelay
12857
- });
12858
- if (dataLines.length) {
12859
- yield data;
12860
- }
12861
- }
12862
- }
12863
- } finally {
12864
- signal.removeEventListener("abort", abortHandler);
12865
- reader.releaseLock();
12866
- }
12867
- break;
12868
- } catch (error) {
12869
- onSseError?.(error);
12870
- if (sseMaxRetryAttempts !== void 0 && attempt >= sseMaxRetryAttempts) {
12871
- break;
12872
- }
12873
- const backoff = Math.min(retryDelay * 2 ** (attempt - 1), sseMaxRetryDelay ?? 3e4);
12874
- await sleep(backoff);
12875
- }
12876
- }
12877
- };
12878
- const stream = createStream();
12879
- return { stream };
12880
- };
12881
- const getAuthToken = async (auth, callback) => {
12882
- const token = typeof callback === "function" ? await callback(auth) : callback;
12883
- if (!token) {
12884
- return;
12885
- }
12886
- if (auth.scheme === "bearer") {
12887
- return `Bearer ${token}`;
12888
- }
12889
- if (auth.scheme === "basic") {
12890
- return `Basic ${btoa(token)}`;
12891
- }
12892
- return token;
12893
- };
12894
- const jsonBodySerializer = {
12895
- bodySerializer: (body) => JSON.stringify(body, (_key, value) => typeof value === "bigint" ? value.toString() : value)
12896
- };
12897
- const separatorArrayExplode = (style) => {
12898
- switch (style) {
12899
- case "label":
12900
- return ".";
12901
- case "matrix":
12902
- return ";";
12903
- case "simple":
12904
- return ",";
12905
- default:
12906
- return "&";
12907
- }
12908
- };
12909
- const separatorArrayNoExplode = (style) => {
12910
- switch (style) {
12911
- case "form":
12912
- return ",";
12913
- case "pipeDelimited":
12914
- return "|";
12915
- case "spaceDelimited":
12916
- return "%20";
12917
- default:
12918
- return ",";
12919
- }
12920
- };
12921
- const separatorObjectExplode = (style) => {
12922
- switch (style) {
12923
- case "label":
12924
- return ".";
12925
- case "matrix":
12926
- return ";";
12927
- case "simple":
12928
- return ",";
12929
- default:
12930
- return "&";
12931
- }
12932
- };
12933
- const serializeArrayParam = ({ allowReserved, explode, name, style, value }) => {
12934
- if (!explode) {
12935
- const joinedValues2 = (allowReserved ? value : value.map((v) => encodeURIComponent(v))).join(separatorArrayNoExplode(style));
12936
- switch (style) {
12937
- case "label":
12938
- return `.${joinedValues2}`;
12939
- case "matrix":
12940
- return `;${name}=${joinedValues2}`;
12941
- case "simple":
12942
- return joinedValues2;
12943
- default:
12944
- return `${name}=${joinedValues2}`;
12945
- }
12946
- }
12947
- const separator = separatorArrayExplode(style);
12948
- const joinedValues = value.map((v) => {
12949
- if (style === "label" || style === "simple") {
12950
- return allowReserved ? v : encodeURIComponent(v);
12951
- }
12952
- return serializePrimitiveParam({
12953
- allowReserved,
12954
- name,
12955
- value: v
12956
- });
12957
- }).join(separator);
12958
- return style === "label" || style === "matrix" ? separator + joinedValues : joinedValues;
12959
- };
12960
- const serializePrimitiveParam = ({ allowReserved, name, value }) => {
12961
- if (value === void 0 || value === null) {
12962
- return "";
12963
- }
12964
- if (typeof value === "object") {
12965
- throw new Error("Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.");
12966
- }
12967
- return `${name}=${allowReserved ? value : encodeURIComponent(value)}`;
12968
- };
12969
- const serializeObjectParam = ({ allowReserved, explode, name, style, value, valueOnly }) => {
12970
- if (value instanceof Date) {
12971
- return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`;
13098
+ return [DEFAULT_MODEL$1];
12972
13099
  }
12973
- if (style !== "deepObject" && !explode) {
12974
- let values = [];
12975
- Object.entries(value).forEach(([key, v]) => {
12976
- values = [...values, key, allowReserved ? v : encodeURIComponent(v)];
12977
- });
12978
- const joinedValues2 = values.join(",");
12979
- switch (style) {
12980
- case "form":
12981
- return `${name}=${joinedValues2}`;
12982
- case "label":
12983
- return `.${joinedValues2}`;
12984
- case "matrix":
12985
- return `;${name}=${joinedValues2}`;
12986
- default:
12987
- return joinedValues2;
12988
- }
12989
- }
12990
- const separator = separatorObjectExplode(style);
12991
- const joinedValues = Object.entries(value).map(([key, v]) => serializePrimitiveParam({
12992
- allowReserved,
12993
- name: style === "deepObject" ? `${name}[${key}]` : key,
12994
- value: v
12995
- })).join(separator);
12996
- return style === "label" || style === "matrix" ? separator + joinedValues : joinedValues;
12997
- };
12998
- const PATH_PARAM_RE = /\{[^{}]+\}/g;
12999
- const defaultPathSerializer = ({ path: path2, url: _url2 }) => {
13000
- let url = _url2;
13001
- const matches = _url2.match(PATH_PARAM_RE);
13002
- if (matches) {
13003
- for (const match of matches) {
13004
- let explode = false;
13005
- let name = match.substring(1, match.length - 1);
13006
- let style = "simple";
13007
- if (name.endsWith("*")) {
13008
- explode = true;
13009
- name = name.substring(0, name.length - 1);
13010
- }
13011
- if (name.startsWith(".")) {
13012
- name = name.substring(1);
13013
- style = "label";
13014
- } else if (name.startsWith(";")) {
13015
- name = name.substring(1);
13016
- style = "matrix";
13017
- }
13018
- const value = path2[name];
13019
- if (value === void 0 || value === null) {
13020
- continue;
13021
- }
13022
- if (Array.isArray(value)) {
13023
- url = url.replace(match, serializeArrayParam({ explode, name, style, value }));
13024
- continue;
13025
- }
13026
- if (typeof value === "object") {
13027
- url = url.replace(match, serializeObjectParam({
13028
- explode,
13029
- name,
13030
- style,
13031
- value,
13032
- valueOnly: true
13033
- }));
13034
- continue;
13035
- }
13036
- if (style === "matrix") {
13037
- url = url.replace(match, `;${serializePrimitiveParam({
13038
- name,
13039
- value
13040
- })}`);
13041
- continue;
13042
- }
13043
- const replaceValue = encodeURIComponent(style === "label" ? `.${value}` : value);
13044
- url = url.replace(match, replaceValue);
13045
- }
13100
+ }
13101
+ function isSupportedGrokVersion(raw) {
13102
+ const match = /^grok (\d+)\.(\d+)\.(\d+)\b/i.exec(stripAnsi(raw).trim());
13103
+ return !!match && Number(match[1]) === 1 && (Number(match[2]) > 0 || Number(match[3]) >= 46);
13104
+ }
13105
+ function parseGrokModels(raw) {
13106
+ const text2 = stripAnsi(raw);
13107
+ const authStatus = /You are not authenticated\./.test(text2) ? "unauthenticated" : /You are logged in with |You are using XAI_API_KEY\.|is using its own API key\.|authenticated via deployment key/.test(text2) ? "authenticated" : "unknown";
13108
+ const models = [{ id: "default", label: "Default (Grok)" }];
13109
+ const seen = /* @__PURE__ */ new Set(["default"]);
13110
+ const catalog = text2.split("Available models:")[1] ?? "";
13111
+ for (const line of catalog.split(/\r?\n/)) {
13112
+ const match = /^\s+[*-]\s+([^\s]+?)(?:\s+\(default\))?\s*$/.exec(line);
13113
+ const id = match?.[1];
13114
+ if (id && id.length <= 200 && /^[\w.:/#-]+$/.test(id) && !seen.has(id)) {
13115
+ seen.add(id);
13116
+ models.push({ id, label: id });
13117
+ }
13118
+ }
13119
+ return { authStatus, models };
13120
+ }
13121
+ function parseGrokOutput(raw) {
13122
+ let envelope;
13123
+ try {
13124
+ envelope = parseJsonPayload(stripAnsi(raw));
13125
+ } catch {
13126
+ throw invalidOutput$2();
13046
13127
  }
13047
- return url;
13048
- };
13049
- const getUrl = ({ baseUrl, path: path2, query, querySerializer, url: _url2 }) => {
13050
- const pathUrl = _url2.startsWith("/") ? _url2 : `/${_url2}`;
13051
- let url = (baseUrl ?? "") + pathUrl;
13052
- if (path2) {
13053
- url = defaultPathSerializer({ path: path2, url });
13128
+ if (envelope.type === "error" || envelope.structuredOutputError || envelope.stopReason !== "end_turn") throw invalidOutput$2();
13129
+ const structured = envelope.structuredOutput;
13130
+ if (structured !== void 0) {
13131
+ if (!structured || typeof structured !== "object" || Array.isArray(structured)) throw invalidOutput$2();
13132
+ return { envelope, output: structured };
13054
13133
  }
13055
- let search = query ? querySerializer(query) : "";
13056
- if (search.startsWith("?")) {
13057
- search = search.substring(1);
13134
+ if (typeof envelope.text !== "string") throw invalidOutput$2();
13135
+ try {
13136
+ return { envelope, output: parseJsonPayload(envelope.text) };
13137
+ } catch {
13138
+ throw invalidOutput$2();
13058
13139
  }
13059
- if (search) {
13060
- url += `?${search}`;
13140
+ }
13141
+ function invalidOutput$2() {
13142
+ return new AiOperationError({ code: "AI_INVALID_OUTPUT", operation: "grok-generate", harness: "grok", message: "Grok returned an incomplete or invalid structured response.", retryable: true });
13143
+ }
13144
+ const DEFAULT_MODEL = { id: "default", label: "Default (Grok)" };
13145
+ const UNSUPPORTED = "Grok Build 1.0.46 or later in the 1.x series is required, with headless JSON and tool controls.";
13146
+ const CONFIG = "[cli]\nauto_update = false\n[subagents]\nenabled = false\n[managed_mcps]\nenabled = false\ngateway_tools_enabled = false\n[memory]\nenabled = false\n[features]\nmanaged_config = false\ntitle_refresh = false\nturn_summary = false\n";
13147
+ const REQUIRED_FLAGS = ["--prompt-file", "--json-schema", "--output-format", "--tools", "--disallowed-tools", "--deny", "--disable-web-search", "--max-turns", "--permission-mode", "--verbatim"];
13148
+ class GrokProvider {
13149
+ constructor(resolver, runner) {
13150
+ this.resolver = resolver;
13151
+ this.runner = runner;
13061
13152
  }
13062
- return url;
13063
- };
13064
- const createQuerySerializer = ({ allowReserved, array: array2, object: object2 } = {}) => {
13065
- const querySerializer = (queryParams) => {
13066
- const search = [];
13067
- if (queryParams && typeof queryParams === "object") {
13068
- for (const name in queryParams) {
13069
- const value = queryParams[name];
13070
- if (value === void 0 || value === null) {
13071
- continue;
13072
- }
13073
- if (Array.isArray(value)) {
13074
- const serializedArray = serializeArrayParam({
13075
- allowReserved,
13076
- explode: true,
13077
- name,
13078
- style: "form",
13079
- value,
13080
- ...array2
13081
- });
13082
- if (serializedArray)
13083
- search.push(serializedArray);
13084
- } else if (typeof value === "object") {
13085
- const serializedObject = serializeObjectParam({
13086
- allowReserved,
13087
- explode: true,
13088
- name,
13089
- style: "deepObject",
13090
- value,
13091
- ...object2
13092
- });
13093
- if (serializedObject)
13094
- search.push(serializedObject);
13095
- } else {
13096
- const serializedPrimitive = serializePrimitiveParam({
13097
- allowReserved,
13098
- name,
13099
- value
13100
- });
13101
- if (serializedPrimitive)
13102
- search.push(serializedPrimitive);
13103
- }
13153
+ resolver;
13154
+ runner;
13155
+ id = "grok";
13156
+ async status(forceRefresh = false) {
13157
+ const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
13158
+ const base = { id: this.id, label: "Grok Build", checkedAt, models: [DEFAULT_MODEL] };
13159
+ return this.withEnvironment(async (options) => {
13160
+ const detected = await selectCli(this.resolver, this.runner, this.id, { forceRefresh, compatible: isSupportedGrokVersion, runOptions: options });
13161
+ const failure2 = detectionFailure(detected, base, UNSUPPORTED);
13162
+ if (failure2) return failure2;
13163
+ const executable = requireCandidate(detected, this.id);
13164
+ const installed = { ...base, ...detectionFields(detected), installed: true, version: detected.version ?? "" };
13165
+ try {
13166
+ await this.checkControls(executable, options);
13167
+ const result = await runCandidate(this.runner, executable, ["models"], options);
13168
+ if (result.exitCode !== 0) return { ...installed, availability: "warning", authStatus: "unknown", message: "Could not check Grok authentication or models. Check again or run grok models in a terminal." };
13169
+ const { authStatus, models } = parseGrokModels(result.stdout);
13170
+ return {
13171
+ ...installed,
13172
+ models,
13173
+ authStatus,
13174
+ availability: authStatus === "unauthenticated" ? "error" : authStatus === "authenticated" && models.length > 1 ? "ready" : "warning",
13175
+ ...authStatus === "unauthenticated" ? { message: "Run grok login, or provide XAI_API_KEY to the OpenTig process." } : authStatus === "unknown" ? { message: "Could not confirm Grok authentication." } : {}
13176
+ };
13177
+ } catch (error) {
13178
+ return { ...installed, availability: "error", authStatus: "unknown", message: error instanceof AiOperationError ? error.message : "Could not verify Grok execution controls." };
13104
13179
  }
13105
- }
13106
- return search.join("&");
13107
- };
13108
- return querySerializer;
13109
- };
13110
- const getParseAs = (contentType) => {
13111
- if (!contentType) {
13112
- return "stream";
13113
- }
13114
- const cleanContent = contentType.split(";")[0]?.trim();
13115
- if (!cleanContent) {
13116
- return;
13117
- }
13118
- if (cleanContent.startsWith("application/json") || cleanContent.endsWith("+json")) {
13119
- return "json";
13120
- }
13121
- if (cleanContent === "multipart/form-data") {
13122
- return "formData";
13123
- }
13124
- if (["application/", "audio/", "image/", "video/"].some((type) => cleanContent.startsWith(type))) {
13125
- return "blob";
13126
- }
13127
- if (cleanContent.startsWith("text/")) {
13128
- return "text";
13129
- }
13130
- return;
13131
- };
13132
- const checkForExistence = (options, name) => {
13133
- if (!name) {
13134
- return false;
13135
- }
13136
- if (options.headers.has(name) || options.query?.[name] || options.headers.get("Cookie")?.includes(`${name}=`)) {
13137
- return true;
13180
+ });
13138
13181
  }
13139
- return false;
13140
- };
13141
- const setAuthParams = async ({ security, ...options }) => {
13142
- for (const auth of security) {
13143
- if (checkForExistence(options, auth.name)) {
13144
- continue;
13145
- }
13146
- const token = await getAuthToken(auth, options.auth);
13147
- if (!token) {
13148
- continue;
13149
- }
13150
- const name = auth.name ?? "Authorization";
13151
- switch (auth.in) {
13152
- case "query":
13153
- if (!options.query) {
13154
- options.query = {};
13155
- }
13156
- options.query[name] = token;
13157
- break;
13158
- case "cookie":
13159
- options.headers.append("Cookie", `${name}=${token}`);
13160
- break;
13161
- case "header":
13162
- default:
13163
- options.headers.set(name, token);
13164
- break;
13165
- }
13166
- }
13167
- };
13168
- const buildUrl = (options) => getUrl({
13169
- baseUrl: options.baseUrl,
13170
- path: options.path,
13171
- query: options.query,
13172
- querySerializer: typeof options.querySerializer === "function" ? options.querySerializer : createQuerySerializer(options.querySerializer),
13173
- url: options.url
13174
- });
13175
- const mergeConfigs = (a2, b) => {
13176
- const config2 = { ...a2, ...b };
13177
- if (config2.baseUrl?.endsWith("/")) {
13178
- config2.baseUrl = config2.baseUrl.substring(0, config2.baseUrl.length - 1);
13179
- }
13180
- config2.headers = mergeHeaders(a2.headers, b.headers);
13181
- return config2;
13182
- };
13183
- const mergeHeaders = (...headers) => {
13184
- const mergedHeaders = new Headers();
13185
- for (const header of headers) {
13186
- if (!header || typeof header !== "object") {
13187
- continue;
13188
- }
13189
- const iterator = header instanceof Headers ? header.entries() : Object.entries(header);
13190
- for (const [key, value] of iterator) {
13191
- if (value === null) {
13192
- mergedHeaders.delete(key);
13193
- } else if (Array.isArray(value)) {
13194
- for (const v of value) {
13195
- mergedHeaders.append(key, v);
13196
- }
13197
- } else if (value !== void 0) {
13198
- mergedHeaders.set(key, typeof value === "object" ? JSON.stringify(value) : value);
13199
- }
13182
+ async generate(input) {
13183
+ const timeout = AbortSignal.timeout(AI_PROVIDER_TIMEOUT_MS);
13184
+ const signal = AbortSignal.any([input.signal, timeout]);
13185
+ try {
13186
+ return await this.withEnvironment(async (options) => {
13187
+ const controlled = { ...options, signal };
13188
+ const executable = requireCandidate(await selectCli(this.resolver, this.runner, this.id, { compatible: isSupportedGrokVersion, runOptions: controlled }), this.id);
13189
+ await this.checkControls(executable, controlled);
13190
+ const promptFile = path.join(options.cwd, "prompt.txt");
13191
+ await writeFile(promptFile, input.prompt, { encoding: "utf8", mode: 384 });
13192
+ const args = [
13193
+ "--prompt-file",
13194
+ promptFile,
13195
+ "--output-format",
13196
+ "json",
13197
+ "--json-schema",
13198
+ JSON.stringify(input.schema),
13199
+ "--verbatim",
13200
+ "--tools",
13201
+ "read_file",
13202
+ "--disallowed-tools",
13203
+ "read_file,search_tool,use_tool,Agent",
13204
+ "--deny",
13205
+ "MCPTool",
13206
+ "--disable-web-search",
13207
+ "--max-turns",
13208
+ "1",
13209
+ "--permission-mode",
13210
+ "dontAsk",
13211
+ "--no-memory"
13212
+ ];
13213
+ if (input.model !== "default") args.push("--model", input.model);
13214
+ const result = await runCandidate(this.runner, executable, args, { ...controlled, timeoutMs: AI_PROVIDER_TIMEOUT_MS });
13215
+ requireGrokSuccess(result);
13216
+ const parsed = parseGrokOutput(result.stdout);
13217
+ return { output: parsed.output, usage: grokUsage(parsed.envelope) };
13218
+ });
13219
+ } catch (error) {
13220
+ if (input.signal.aborted) throw failure("AI_CANCELLED", "Generation canceled.");
13221
+ if (timeout.aborted) throw failure("AI_TIMEOUT", "Generation took too long.");
13222
+ throw error;
13200
13223
  }
13201
13224
  }
13202
- return mergedHeaders;
13203
- };
13204
- class Interceptors {
13205
- _fns;
13206
- constructor() {
13207
- this._fns = [];
13208
- }
13209
- clear() {
13210
- this._fns = [];
13211
- }
13212
- getInterceptorIndex(id) {
13213
- if (typeof id === "number") {
13214
- return this._fns[id] ? id : -1;
13215
- } else {
13216
- return this._fns.indexOf(id);
13225
+ async checkControls(executable, options) {
13226
+ const help = await runCandidate(this.runner, executable, ["--help"], options);
13227
+ if (help.exitCode !== 0 || REQUIRED_FLAGS.some((flag2) => !help.stdout.includes(flag2))) throw failure("AI_PROCESS_FAILED", UNSUPPORTED);
13228
+ const result = await runCandidate(this.runner, executable, ["inspect", "--json"], options);
13229
+ let report;
13230
+ try {
13231
+ report = JSON.parse(result.stdout);
13232
+ } catch {
13233
+ throw failure("AI_PROCESS_FAILED", "Could not verify the isolated Grok configuration.");
13217
13234
  }
13218
- }
13219
- exists(id) {
13220
- const index = this.getInterceptorIndex(id);
13221
- return !!this._fns[index];
13222
- }
13223
- eject(id) {
13224
- const index = this.getInterceptorIndex(id);
13225
- if (this._fns[index]) {
13226
- this._fns[index] = null;
13235
+ const surfaces = ["hooks", "plugins", "mcpServers", "projectInstructions"];
13236
+ if (result.exitCode !== 0 || !report || surfaces.some((key) => !Array.isArray(report[key]) || report[key].length > 0)) {
13237
+ throw failure("AI_PROCESS_FAILED", "Grok configuration includes hooks, plugins, MCP servers or instructions. OpenTig requires an isolated text-only configuration.");
13227
13238
  }
13228
13239
  }
13229
- update(id, fn) {
13230
- const index = this.getInterceptorIndex(id);
13231
- if (this._fns[index]) {
13232
- this._fns[index] = fn;
13233
- return id;
13234
- } else {
13235
- return false;
13240
+ async withEnvironment(run) {
13241
+ const temporary = await mkdtemp(path.join(os.tmpdir(), "opentig-grok-"));
13242
+ const userGrokHome = process.env.GROK_HOME || path.join(os.homedir(), ".grok");
13243
+ const authPath = path.resolve(process.env.GROK_AUTH_PATH || path.join(userGrokHome, "auth.json"));
13244
+ const privateGrokHome = path.join(temporary, ".grok");
13245
+ try {
13246
+ await mkdir(privateGrokHome, { mode: 448 });
13247
+ await writeFile(path.join(privateGrokHome, "config.toml"), CONFIG, { encoding: "utf8", mode: 384 });
13248
+ const env = {
13249
+ HOME: temporary,
13250
+ USERPROFILE: temporary,
13251
+ GROK_HOME: privateGrokHome,
13252
+ GROK_AUTH_PATH: authPath,
13253
+ XDG_CONFIG_HOME: path.join(temporary, "config"),
13254
+ XDG_DATA_HOME: path.join(temporary, "data"),
13255
+ XDG_CACHE_HOME: path.join(temporary, "cache"),
13256
+ GROK_SUBAGENTS: "0",
13257
+ GROK_MANAGED_MCPS_ENABLED: "0",
13258
+ GROK_MANAGED_MCP_GATEWAY_TOOLS_ENABLED: "0",
13259
+ GROK_TITLE_REFRESH: "0"
13260
+ };
13261
+ const preserved = /* @__PURE__ */ new Set([...Object.keys(env), "GROK_AUTH", "GROK_CODE_XAI_API_KEY"]);
13262
+ const removeEnv = Object.keys(process.env).filter((key) => key.startsWith("GROK_") && !preserved.has(key));
13263
+ return await run({ cwd: temporary, env, removeEnv });
13264
+ } finally {
13265
+ await rm(temporary, { recursive: true, force: true, maxRetries: 3 });
13236
13266
  }
13237
13267
  }
13238
- use(fn) {
13239
- this._fns = [...this._fns, fn];
13240
- return this._fns.length - 1;
13241
- }
13242
13268
  }
13243
- const createInterceptors = () => ({
13244
- error: new Interceptors(),
13245
- request: new Interceptors(),
13246
- response: new Interceptors()
13247
- });
13248
- const defaultQuerySerializer = createQuerySerializer({
13249
- allowReserved: false,
13250
- array: {
13251
- explode: true,
13252
- style: "form"
13253
- },
13254
- object: {
13255
- explode: true,
13256
- style: "deepObject"
13257
- }
13258
- });
13259
- const defaultHeaders = {
13260
- "Content-Type": "application/json"
13261
- };
13262
- const createConfig = (override = {}) => ({
13263
- ...jsonBodySerializer,
13264
- headers: defaultHeaders,
13265
- parseAs: "auto",
13266
- querySerializer: defaultQuerySerializer,
13267
- ...override
13268
- });
13269
- const createClient = (config2 = {}) => {
13270
- let _config = mergeConfigs(createConfig(), config2);
13271
- const getConfig = () => ({ ..._config });
13272
- const setConfig = (config3) => {
13273
- _config = mergeConfigs(_config, config3);
13274
- return getConfig();
13275
- };
13276
- const interceptors = createInterceptors();
13277
- const beforeRequest = async (options) => {
13278
- const opts = {
13279
- ..._config,
13280
- ...options,
13281
- fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,
13282
- headers: mergeHeaders(_config.headers, options.headers),
13283
- serializedBody: void 0
13284
- };
13285
- if (opts.security) {
13286
- await setAuthParams({
13287
- ...opts,
13288
- security: opts.security
13289
- });
13290
- }
13291
- if (opts.requestValidator) {
13292
- await opts.requestValidator(opts);
13293
- }
13294
- if (opts.body && opts.bodySerializer) {
13295
- opts.serializedBody = opts.bodySerializer(opts.body);
13296
- }
13297
- if (opts.serializedBody === void 0 || opts.serializedBody === "") {
13298
- opts.headers.delete("Content-Type");
13299
- }
13300
- const url = buildUrl(opts);
13301
- return { opts, url };
13302
- };
13303
- const request = async (options) => {
13304
- const { opts, url } = await beforeRequest(options);
13305
- const requestInit = {
13306
- redirect: "follow",
13307
- ...opts,
13308
- body: opts.serializedBody
13309
- };
13310
- let request2 = new Request(url, requestInit);
13311
- for (const fn of interceptors.request._fns) {
13312
- if (fn) {
13313
- request2 = await fn(request2, opts);
13314
- }
13315
- }
13316
- const _fetch = opts.fetch;
13317
- let response = await _fetch(request2);
13318
- for (const fn of interceptors.response._fns) {
13319
- if (fn) {
13320
- response = await fn(response, request2, opts);
13321
- }
13322
- }
13323
- const result = {
13324
- request: request2,
13325
- response
13326
- };
13327
- if (response.ok) {
13328
- if (response.status === 204 || response.headers.get("Content-Length") === "0") {
13329
- return opts.responseStyle === "data" ? {} : {
13330
- data: {},
13331
- ...result
13332
- };
13333
- }
13334
- const parseAs = (opts.parseAs === "auto" ? getParseAs(response.headers.get("Content-Type")) : opts.parseAs) ?? "json";
13335
- let data;
13336
- switch (parseAs) {
13337
- case "arrayBuffer":
13338
- case "blob":
13339
- case "formData":
13340
- case "json":
13341
- case "text":
13342
- data = await response[parseAs]();
13343
- break;
13344
- case "stream":
13345
- return opts.responseStyle === "data" ? response.body : {
13346
- data: response.body,
13347
- ...result
13348
- };
13349
- }
13350
- if (parseAs === "json") {
13351
- if (opts.responseValidator) {
13352
- await opts.responseValidator(data);
13353
- }
13354
- if (opts.responseTransformer) {
13355
- data = await opts.responseTransformer(data);
13356
- }
13357
- }
13358
- return opts.responseStyle === "data" ? data : {
13359
- data,
13360
- ...result
13361
- };
13362
- }
13363
- const textError = await response.text();
13364
- let jsonError;
13365
- try {
13366
- jsonError = JSON.parse(textError);
13367
- } catch {
13368
- }
13369
- const error = jsonError ?? textError;
13370
- let finalError = error;
13371
- for (const fn of interceptors.error._fns) {
13372
- if (fn) {
13373
- finalError = await fn(error, response, request2, opts);
13374
- }
13375
- }
13376
- finalError = finalError || {};
13377
- if (opts.throwOnError) {
13378
- throw finalError;
13379
- }
13380
- return opts.responseStyle === "data" ? void 0 : {
13381
- error: finalError,
13382
- ...result
13383
- };
13384
- };
13385
- const makeMethod = (method) => {
13386
- const fn = (options) => request({ ...options, method });
13387
- fn.sse = async (options) => {
13388
- const { opts, url } = await beforeRequest(options);
13389
- return createSseClient({
13390
- ...opts,
13391
- body: opts.body,
13392
- headers: opts.headers,
13393
- method,
13394
- url
13395
- });
13396
- };
13397
- return fn;
13398
- };
13399
- return {
13400
- buildUrl,
13401
- connect: makeMethod("CONNECT"),
13402
- delete: makeMethod("DELETE"),
13403
- get: makeMethod("GET"),
13404
- getConfig,
13405
- head: makeMethod("HEAD"),
13406
- interceptors,
13407
- options: makeMethod("OPTIONS"),
13408
- patch: makeMethod("PATCH"),
13409
- post: makeMethod("POST"),
13410
- put: makeMethod("PUT"),
13411
- request,
13412
- setConfig,
13413
- trace: makeMethod("TRACE")
13414
- };
13415
- };
13416
- const client = createClient(createConfig({
13417
- baseUrl: "http://localhost:4096"
13418
- }));
13419
- class _HeyApiClient {
13420
- _client = client;
13421
- constructor(args) {
13422
- if (args?.client) {
13423
- this._client = args.client;
13424
- }
13425
- }
13426
- }
13427
- class Global extends _HeyApiClient {
13428
- /**
13429
- * Get events
13430
- */
13431
- event(options) {
13432
- return (options?.client ?? this._client).get.sse({
13433
- url: "/global/event",
13434
- ...options
13435
- });
13436
- }
13437
- }
13438
- class Project extends _HeyApiClient {
13439
- /**
13440
- * List all projects
13441
- */
13442
- list(options) {
13443
- return (options?.client ?? this._client).get({
13444
- url: "/project",
13445
- ...options
13446
- });
13447
- }
13448
- /**
13449
- * Get the current project
13450
- */
13451
- current(options) {
13452
- return (options?.client ?? this._client).get({
13453
- url: "/project/current",
13454
- ...options
13455
- });
13456
- }
13457
- }
13458
- class Pty extends _HeyApiClient {
13459
- /**
13460
- * List all PTY sessions
13461
- */
13462
- list(options) {
13463
- return (options?.client ?? this._client).get({
13464
- url: "/pty",
13465
- ...options
13466
- });
13467
- }
13468
- /**
13469
- * Create a new PTY session
13470
- */
13471
- create(options) {
13472
- return (options?.client ?? this._client).post({
13473
- url: "/pty",
13474
- ...options,
13475
- headers: {
13476
- "Content-Type": "application/json",
13477
- ...options?.headers
13478
- }
13479
- });
13480
- }
13481
- /**
13482
- * Remove a PTY session
13483
- */
13484
- remove(options) {
13485
- return (options.client ?? this._client).delete({
13486
- url: "/pty/{id}",
13487
- ...options
13488
- });
13489
- }
13490
- /**
13491
- * Get PTY session info
13492
- */
13493
- get(options) {
13494
- return (options.client ?? this._client).get({
13495
- url: "/pty/{id}",
13496
- ...options
13497
- });
13498
- }
13499
- /**
13500
- * Update PTY session
13501
- */
13502
- update(options) {
13503
- return (options.client ?? this._client).put({
13504
- url: "/pty/{id}",
13505
- ...options,
13506
- headers: {
13507
- "Content-Type": "application/json",
13508
- ...options.headers
13509
- }
13510
- });
13511
- }
13512
- /**
13513
- * Connect to a PTY session
13514
- */
13515
- connect(options) {
13516
- return (options.client ?? this._client).get({
13517
- url: "/pty/{id}/connect",
13518
- ...options
13519
- });
13520
- }
13521
- }
13522
- class Config extends _HeyApiClient {
13523
- /**
13524
- * Get config info
13525
- */
13526
- get(options) {
13527
- return (options?.client ?? this._client).get({
13528
- url: "/config",
13529
- ...options
13530
- });
13531
- }
13532
- /**
13533
- * Update config
13534
- */
13535
- update(options) {
13536
- return (options?.client ?? this._client).patch({
13537
- url: "/config",
13538
- ...options,
13539
- headers: {
13540
- "Content-Type": "application/json",
13541
- ...options?.headers
13542
- }
13543
- });
13544
- }
13545
- /**
13546
- * List all providers
13547
- */
13548
- providers(options) {
13549
- return (options?.client ?? this._client).get({
13550
- url: "/config/providers",
13551
- ...options
13552
- });
13553
- }
13554
- }
13555
- class Tool extends _HeyApiClient {
13556
- /**
13557
- * List all tool IDs (including built-in and dynamically registered)
13558
- */
13559
- ids(options) {
13560
- return (options?.client ?? this._client).get({
13561
- url: "/experimental/tool/ids",
13562
- ...options
13563
- });
13564
- }
13565
- /**
13566
- * List tools with JSON schema parameters for a provider/model
13567
- */
13568
- list(options) {
13569
- return (options.client ?? this._client).get({
13570
- url: "/experimental/tool",
13571
- ...options
13572
- });
13573
- }
13574
- }
13575
- class Instance extends _HeyApiClient {
13576
- /**
13577
- * Dispose the current instance
13578
- */
13579
- dispose(options) {
13580
- return (options?.client ?? this._client).post({
13581
- url: "/instance/dispose",
13582
- ...options
13583
- });
13584
- }
13585
- }
13586
- class Path extends _HeyApiClient {
13587
- /**
13588
- * Get the current path
13589
- */
13590
- get(options) {
13591
- return (options?.client ?? this._client).get({
13592
- url: "/path",
13593
- ...options
13594
- });
13595
- }
13596
- }
13597
- class Vcs extends _HeyApiClient {
13598
- /**
13599
- * Get VCS info for the current instance
13600
- */
13601
- get(options) {
13602
- return (options?.client ?? this._client).get({
13603
- url: "/vcs",
13604
- ...options
13605
- });
13606
- }
13607
- }
13608
- class Session extends _HeyApiClient {
13609
- /**
13610
- * List all sessions
13611
- */
13612
- list(options) {
13613
- return (options?.client ?? this._client).get({
13614
- url: "/session",
13615
- ...options
13616
- });
13617
- }
13618
- /**
13619
- * Create a new session
13620
- */
13621
- create(options) {
13622
- return (options?.client ?? this._client).post({
13623
- url: "/session",
13624
- ...options,
13625
- headers: {
13626
- "Content-Type": "application/json",
13627
- ...options?.headers
13628
- }
13629
- });
13630
- }
13631
- /**
13632
- * Get session status
13633
- */
13634
- status(options) {
13635
- return (options?.client ?? this._client).get({
13636
- url: "/session/status",
13637
- ...options
13638
- });
13639
- }
13640
- /**
13641
- * Delete a session and all its data
13642
- */
13643
- delete(options) {
13644
- return (options.client ?? this._client).delete({
13645
- url: "/session/{id}",
13646
- ...options
13647
- });
13648
- }
13649
- /**
13650
- * Get session
13651
- */
13652
- get(options) {
13653
- return (options.client ?? this._client).get({
13654
- url: "/session/{id}",
13655
- ...options
13656
- });
13657
- }
13658
- /**
13659
- * Update session properties
13660
- */
13661
- update(options) {
13662
- return (options.client ?? this._client).patch({
13663
- url: "/session/{id}",
13664
- ...options,
13665
- headers: {
13666
- "Content-Type": "application/json",
13667
- ...options.headers
13668
- }
13669
- });
13670
- }
13671
- /**
13672
- * Get a session's children
13673
- */
13674
- children(options) {
13675
- return (options.client ?? this._client).get({
13676
- url: "/session/{id}/children",
13677
- ...options
13678
- });
13679
- }
13680
- /**
13681
- * Get the todo list for a session
13682
- */
13683
- todo(options) {
13684
- return (options.client ?? this._client).get({
13685
- url: "/session/{id}/todo",
13686
- ...options
13687
- });
13688
- }
13689
- /**
13690
- * Analyze the app and create an AGENTS.md file
13691
- */
13692
- init(options) {
13693
- return (options.client ?? this._client).post({
13694
- url: "/session/{id}/init",
13695
- ...options,
13696
- headers: {
13697
- "Content-Type": "application/json",
13698
- ...options.headers
13699
- }
13700
- });
13701
- }
13702
- /**
13703
- * Fork an existing session at a specific message
13704
- */
13705
- fork(options) {
13706
- return (options.client ?? this._client).post({
13707
- url: "/session/{id}/fork",
13708
- ...options,
13709
- headers: {
13710
- "Content-Type": "application/json",
13711
- ...options.headers
13712
- }
13713
- });
13714
- }
13715
- /**
13716
- * Abort a session
13717
- */
13718
- abort(options) {
13719
- return (options.client ?? this._client).post({
13720
- url: "/session/{id}/abort",
13721
- ...options
13722
- });
13723
- }
13724
- /**
13725
- * Unshare the session
13726
- */
13727
- unshare(options) {
13728
- return (options.client ?? this._client).delete({
13729
- url: "/session/{id}/share",
13730
- ...options
13731
- });
13732
- }
13733
- /**
13734
- * Share a session
13735
- */
13736
- share(options) {
13737
- return (options.client ?? this._client).post({
13738
- url: "/session/{id}/share",
13739
- ...options
13740
- });
13741
- }
13742
- /**
13743
- * Get the diff for this session
13744
- */
13745
- diff(options) {
13746
- return (options.client ?? this._client).get({
13747
- url: "/session/{id}/diff",
13748
- ...options
13749
- });
13750
- }
13751
- /**
13752
- * Summarize the session
13753
- */
13754
- summarize(options) {
13755
- return (options.client ?? this._client).post({
13756
- url: "/session/{id}/summarize",
13757
- ...options,
13758
- headers: {
13759
- "Content-Type": "application/json",
13760
- ...options.headers
13761
- }
13762
- });
13763
- }
13764
- /**
13765
- * List messages for a session
13766
- */
13767
- messages(options) {
13768
- return (options.client ?? this._client).get({
13769
- url: "/session/{id}/message",
13770
- ...options
13771
- });
13772
- }
13773
- /**
13774
- * Create and send a new message to a session
13775
- */
13776
- prompt(options) {
13777
- return (options.client ?? this._client).post({
13778
- url: "/session/{id}/message",
13779
- ...options,
13780
- headers: {
13781
- "Content-Type": "application/json",
13782
- ...options.headers
13783
- }
13784
- });
13785
- }
13786
- /**
13787
- * Get a message from a session
13788
- */
13789
- message(options) {
13790
- return (options.client ?? this._client).get({
13791
- url: "/session/{id}/message/{messageID}",
13792
- ...options
13793
- });
13794
- }
13795
- /**
13796
- * Create and send a new message to a session, start if needed and return immediately
13797
- */
13798
- promptAsync(options) {
13799
- return (options.client ?? this._client).post({
13800
- url: "/session/{id}/prompt_async",
13801
- ...options,
13802
- headers: {
13803
- "Content-Type": "application/json",
13804
- ...options.headers
13805
- }
13806
- });
13807
- }
13808
- /**
13809
- * Send a new command to a session
13810
- */
13811
- command(options) {
13812
- return (options.client ?? this._client).post({
13813
- url: "/session/{id}/command",
13814
- ...options,
13815
- headers: {
13816
- "Content-Type": "application/json",
13817
- ...options.headers
13818
- }
13819
- });
13820
- }
13821
- /**
13822
- * Run a shell command
13823
- */
13824
- shell(options) {
13825
- return (options.client ?? this._client).post({
13826
- url: "/session/{id}/shell",
13827
- ...options,
13828
- headers: {
13829
- "Content-Type": "application/json",
13830
- ...options.headers
13831
- }
13832
- });
13833
- }
13834
- /**
13835
- * Revert a message
13836
- */
13837
- revert(options) {
13838
- return (options.client ?? this._client).post({
13839
- url: "/session/{id}/revert",
13840
- ...options,
13841
- headers: {
13842
- "Content-Type": "application/json",
13843
- ...options.headers
13844
- }
13845
- });
13846
- }
13847
- /**
13848
- * Restore all reverted messages
13849
- */
13850
- unrevert(options) {
13851
- return (options.client ?? this._client).post({
13852
- url: "/session/{id}/unrevert",
13853
- ...options
13854
- });
13855
- }
13856
- }
13857
- class Command extends _HeyApiClient {
13858
- /**
13859
- * List all commands
13860
- */
13861
- list(options) {
13862
- return (options?.client ?? this._client).get({
13863
- url: "/command",
13864
- ...options
13865
- });
13866
- }
13867
- }
13868
- class Oauth extends _HeyApiClient {
13869
- /**
13870
- * Authorize a provider using OAuth
13871
- */
13872
- authorize(options) {
13873
- return (options.client ?? this._client).post({
13874
- url: "/provider/{id}/oauth/authorize",
13875
- ...options,
13876
- headers: {
13877
- "Content-Type": "application/json",
13878
- ...options.headers
13879
- }
13880
- });
13881
- }
13882
- /**
13883
- * Handle OAuth callback for a provider
13884
- */
13885
- callback(options) {
13886
- return (options.client ?? this._client).post({
13887
- url: "/provider/{id}/oauth/callback",
13888
- ...options,
13889
- headers: {
13890
- "Content-Type": "application/json",
13891
- ...options.headers
13892
- }
13893
- });
13894
- }
13895
- }
13896
- class Provider extends _HeyApiClient {
13897
- /**
13898
- * List all providers
13899
- */
13900
- list(options) {
13901
- return (options?.client ?? this._client).get({
13902
- url: "/provider",
13903
- ...options
13904
- });
13905
- }
13906
- /**
13907
- * Get provider authentication methods
13908
- */
13909
- auth(options) {
13910
- return (options?.client ?? this._client).get({
13911
- url: "/provider/auth",
13912
- ...options
13913
- });
13914
- }
13915
- oauth = new Oauth({ client: this._client });
13916
- }
13917
- class Find extends _HeyApiClient {
13918
- /**
13919
- * Find text in files
13920
- */
13921
- text(options) {
13922
- return (options.client ?? this._client).get({
13923
- url: "/find",
13924
- ...options
13925
- });
13926
- }
13927
- /**
13928
- * Find files
13929
- */
13930
- files(options) {
13931
- return (options.client ?? this._client).get({
13932
- url: "/find/file",
13933
- ...options
13934
- });
13935
- }
13936
- /**
13937
- * Find workspace symbols
13938
- */
13939
- symbols(options) {
13940
- return (options.client ?? this._client).get({
13941
- url: "/find/symbol",
13942
- ...options
13943
- });
13944
- }
13945
- }
13946
- class File extends _HeyApiClient {
13947
- /**
13948
- * List files and directories
13949
- */
13950
- list(options) {
13951
- return (options.client ?? this._client).get({
13952
- url: "/file",
13953
- ...options
13954
- });
13955
- }
13956
- /**
13957
- * Read a file
13958
- */
13959
- read(options) {
13960
- return (options.client ?? this._client).get({
13961
- url: "/file/content",
13962
- ...options
13963
- });
13964
- }
13965
- /**
13966
- * Get file status
13967
- */
13968
- status(options) {
13969
- return (options?.client ?? this._client).get({
13970
- url: "/file/status",
13971
- ...options
13972
- });
13973
- }
13974
- }
13975
- class App extends _HeyApiClient {
13976
- /**
13977
- * Write a log entry to the server logs
13978
- */
13979
- log(options) {
13980
- return (options?.client ?? this._client).post({
13981
- url: "/log",
13982
- ...options,
13983
- headers: {
13984
- "Content-Type": "application/json",
13985
- ...options?.headers
13986
- }
13987
- });
13988
- }
13989
- /**
13990
- * List all agents
13991
- */
13992
- agents(options) {
13993
- return (options?.client ?? this._client).get({
13994
- url: "/agent",
13995
- ...options
13996
- });
13997
- }
13998
- }
13999
- class Auth extends _HeyApiClient {
14000
- /**
14001
- * Remove OAuth credentials for an MCP server
14002
- */
14003
- remove(options) {
14004
- return (options.client ?? this._client).delete({
14005
- url: "/mcp/{name}/auth",
14006
- ...options
14007
- });
14008
- }
14009
- /**
14010
- * Start OAuth authentication flow for an MCP server
14011
- */
14012
- start(options) {
14013
- return (options.client ?? this._client).post({
14014
- url: "/mcp/{name}/auth",
14015
- ...options
14016
- });
14017
- }
14018
- /**
14019
- * Complete OAuth authentication with authorization code
14020
- */
14021
- callback(options) {
14022
- return (options.client ?? this._client).post({
14023
- url: "/mcp/{name}/auth/callback",
14024
- ...options,
14025
- headers: {
14026
- "Content-Type": "application/json",
14027
- ...options.headers
14028
- }
14029
- });
14030
- }
14031
- /**
14032
- * Start OAuth flow and wait for callback (opens browser)
14033
- */
14034
- authenticate(options) {
14035
- return (options.client ?? this._client).post({
14036
- url: "/mcp/{name}/auth/authenticate",
14037
- ...options
14038
- });
14039
- }
14040
- /**
14041
- * Set authentication credentials
14042
- */
14043
- set(options) {
14044
- return (options.client ?? this._client).put({
14045
- url: "/auth/{id}",
14046
- ...options,
14047
- headers: {
14048
- "Content-Type": "application/json",
14049
- ...options.headers
14050
- }
14051
- });
14052
- }
14053
- }
14054
- class Mcp extends _HeyApiClient {
14055
- /**
14056
- * Get MCP server status
14057
- */
14058
- status(options) {
14059
- return (options?.client ?? this._client).get({
14060
- url: "/mcp",
14061
- ...options
14062
- });
14063
- }
14064
- /**
14065
- * Add MCP server dynamically
14066
- */
14067
- add(options) {
14068
- return (options?.client ?? this._client).post({
14069
- url: "/mcp",
14070
- ...options,
14071
- headers: {
14072
- "Content-Type": "application/json",
14073
- ...options?.headers
14074
- }
14075
- });
14076
- }
14077
- /**
14078
- * Connect an MCP server
14079
- */
14080
- connect(options) {
14081
- return (options.client ?? this._client).post({
14082
- url: "/mcp/{name}/connect",
14083
- ...options
14084
- });
14085
- }
14086
- /**
14087
- * Disconnect an MCP server
14088
- */
14089
- disconnect(options) {
14090
- return (options.client ?? this._client).post({
14091
- url: "/mcp/{name}/disconnect",
14092
- ...options
14093
- });
14094
- }
14095
- auth = new Auth({ client: this._client });
14096
- }
14097
- class Lsp extends _HeyApiClient {
14098
- /**
14099
- * Get LSP server status
14100
- */
14101
- status(options) {
14102
- return (options?.client ?? this._client).get({
14103
- url: "/lsp",
14104
- ...options
14105
- });
14106
- }
14107
- }
14108
- class Formatter extends _HeyApiClient {
14109
- /**
14110
- * Get formatter status
14111
- */
14112
- status(options) {
14113
- return (options?.client ?? this._client).get({
14114
- url: "/formatter",
14115
- ...options
14116
- });
14117
- }
14118
- }
14119
- class Control extends _HeyApiClient {
14120
- /**
14121
- * Get the next TUI request from the queue
14122
- */
14123
- next(options) {
14124
- return (options?.client ?? this._client).get({
14125
- url: "/tui/control/next",
14126
- ...options
14127
- });
14128
- }
14129
- /**
14130
- * Submit a response to the TUI request queue
14131
- */
14132
- response(options) {
14133
- return (options?.client ?? this._client).post({
14134
- url: "/tui/control/response",
14135
- ...options,
14136
- headers: {
14137
- "Content-Type": "application/json",
14138
- ...options?.headers
14139
- }
14140
- });
14141
- }
14142
- }
14143
- class Tui extends _HeyApiClient {
14144
- /**
14145
- * Append prompt to the TUI
14146
- */
14147
- appendPrompt(options) {
14148
- return (options?.client ?? this._client).post({
14149
- url: "/tui/append-prompt",
14150
- ...options,
14151
- headers: {
14152
- "Content-Type": "application/json",
14153
- ...options?.headers
14154
- }
14155
- });
14156
- }
14157
- /**
14158
- * Open the help dialog
14159
- */
14160
- openHelp(options) {
14161
- return (options?.client ?? this._client).post({
14162
- url: "/tui/open-help",
14163
- ...options
14164
- });
14165
- }
14166
- /**
14167
- * Open the session dialog
14168
- */
14169
- openSessions(options) {
14170
- return (options?.client ?? this._client).post({
14171
- url: "/tui/open-sessions",
14172
- ...options
14173
- });
14174
- }
14175
- /**
14176
- * Open the theme dialog
14177
- */
14178
- openThemes(options) {
14179
- return (options?.client ?? this._client).post({
14180
- url: "/tui/open-themes",
14181
- ...options
14182
- });
14183
- }
14184
- /**
14185
- * Open the model dialog
14186
- */
14187
- openModels(options) {
14188
- return (options?.client ?? this._client).post({
14189
- url: "/tui/open-models",
14190
- ...options
14191
- });
14192
- }
14193
- /**
14194
- * Submit the prompt
14195
- */
14196
- submitPrompt(options) {
14197
- return (options?.client ?? this._client).post({
14198
- url: "/tui/submit-prompt",
14199
- ...options
14200
- });
14201
- }
14202
- /**
14203
- * Clear the prompt
14204
- */
14205
- clearPrompt(options) {
14206
- return (options?.client ?? this._client).post({
14207
- url: "/tui/clear-prompt",
14208
- ...options
14209
- });
14210
- }
14211
- /**
14212
- * Execute a TUI command (e.g. agent_cycle)
14213
- */
14214
- executeCommand(options) {
14215
- return (options?.client ?? this._client).post({
14216
- url: "/tui/execute-command",
14217
- ...options,
14218
- headers: {
14219
- "Content-Type": "application/json",
14220
- ...options?.headers
14221
- }
14222
- });
14223
- }
14224
- /**
14225
- * Show a toast notification in the TUI
14226
- */
14227
- showToast(options) {
14228
- return (options?.client ?? this._client).post({
14229
- url: "/tui/show-toast",
14230
- ...options,
14231
- headers: {
14232
- "Content-Type": "application/json",
14233
- ...options?.headers
14234
- }
14235
- });
14236
- }
14237
- /**
14238
- * Publish a TUI event
14239
- */
14240
- publish(options) {
14241
- return (options?.client ?? this._client).post({
14242
- url: "/tui/publish",
14243
- ...options,
14244
- headers: {
14245
- "Content-Type": "application/json",
14246
- ...options?.headers
14247
- }
14248
- });
14249
- }
14250
- control = new Control({ client: this._client });
14251
- }
14252
- class Event extends _HeyApiClient {
14253
- /**
14254
- * Get events
14255
- */
14256
- subscribe(options) {
14257
- return (options?.client ?? this._client).get.sse({
14258
- url: "/event",
14259
- ...options
14260
- });
14261
- }
14262
- }
14263
- class OpencodeClient extends _HeyApiClient {
14264
- /**
14265
- * Respond to a permission request
14266
- */
14267
- postSessionIdPermissionsPermissionId(options) {
14268
- return (options.client ?? this._client).post({
14269
- url: "/session/{id}/permissions/{permissionID}",
14270
- ...options,
14271
- headers: {
14272
- "Content-Type": "application/json",
14273
- ...options.headers
14274
- }
14275
- });
14276
- }
14277
- global = new Global({ client: this._client });
14278
- project = new Project({ client: this._client });
14279
- pty = new Pty({ client: this._client });
14280
- config = new Config({ client: this._client });
14281
- tool = new Tool({ client: this._client });
14282
- instance = new Instance({ client: this._client });
14283
- path = new Path({ client: this._client });
14284
- vcs = new Vcs({ client: this._client });
14285
- session = new Session({ client: this._client });
14286
- command = new Command({ client: this._client });
14287
- provider = new Provider({ client: this._client });
14288
- find = new Find({ client: this._client });
14289
- file = new File({ client: this._client });
14290
- app = new App({ client: this._client });
14291
- mcp = new Mcp({ client: this._client });
14292
- lsp = new Lsp({ client: this._client });
14293
- formatter = new Formatter({ client: this._client });
14294
- tui = new Tui({ client: this._client });
14295
- auth = new Auth({ client: this._client });
14296
- event = new Event({ client: this._client });
14297
- }
14298
- function wrapClientError(error, response, request, opts) {
14299
- if (!opts?.throwOnError)
14300
- return error;
14301
- if (error instanceof Error)
14302
- return error;
14303
- if (typeof error === "object" && error !== null && Object.keys(error).length > 0) {
14304
- const obj = error;
14305
- const message = typeof obj.data?.message === "string" && obj.data.message || typeof obj.message === "string" && obj.message || typeof obj.name === "string" && obj.name || describe(request, response);
14306
- return new Error(message, { cause: { body: error, status: response?.status } });
14307
- }
14308
- if (typeof error === "string" && error.length > 0) {
14309
- return new Error(error, { cause: { body: error, status: response?.status } });
14310
- }
14311
- const reason = response ? "(empty response body)" : "network error (no response)";
14312
- return new Error(`opencode server ${describe(request, response)}: ${reason}`, {
14313
- cause: { body: error, status: response?.status }
14314
- });
14315
- }
14316
- function describe(request, response) {
14317
- const method = request?.method ?? "?";
14318
- const url = request?.url ?? "?";
14319
- const status = response?.status;
14320
- const statusText = response?.statusText;
14321
- return `${method} ${url}${status ? " → " + status : ""}${statusText ? " " + statusText : ""}`;
14322
- }
14323
- function pick(value, fallback) {
14324
- if (!value)
14325
- return;
14326
- if (!fallback)
14327
- return value;
14328
- if (value === fallback)
14329
- return fallback;
14330
- if (value === encodeURIComponent(fallback))
14331
- return fallback;
14332
- return value;
14333
- }
14334
- function rewrite(request, directory) {
14335
- if (request.method !== "GET" && request.method !== "HEAD")
14336
- return request;
14337
- const value = pick(request.headers.get("x-opencode-directory"), directory);
14338
- if (!value)
14339
- return request;
14340
- const url = new URL(request.url);
14341
- if (!url.searchParams.has("directory")) {
14342
- url.searchParams.set("directory", value);
14343
- }
14344
- const next = new Request(url, request);
14345
- next.headers.delete("x-opencode-directory");
14346
- return next;
14347
- }
14348
- function createOpencodeClient(config2) {
14349
- if (!config2?.fetch) {
14350
- const customFetch = (req) => {
14351
- req.timeout = false;
14352
- return fetch(req);
14353
- };
14354
- config2 = {
14355
- ...config2,
14356
- fetch: customFetch
14357
- };
14358
- }
14359
- if (config2?.directory) {
14360
- config2.headers = {
14361
- ...config2.headers,
14362
- "x-opencode-directory": encodeURIComponent(config2.directory)
14363
- };
14364
- }
14365
- const client2 = createClient(config2);
14366
- client2.interceptors.request.use((request) => rewrite(request, config2?.directory));
14367
- client2.interceptors.error.use(wrapClientError);
14368
- return new OpencodeClient({ client: client2 });
14369
- }
14370
- var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
14371
- function getDefaultExportFromCjs(x) {
14372
- return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
14373
- }
14374
- var crossSpawn = { exports: {} };
14375
- var windows;
14376
- var hasRequiredWindows;
14377
- function requireWindows() {
14378
- if (hasRequiredWindows) return windows;
14379
- hasRequiredWindows = 1;
14380
- windows = isexe;
14381
- isexe.sync = sync;
14382
- var fs2 = require$$0;
14383
- function checkPathExt(path2, options) {
14384
- var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
14385
- if (!pathext) {
14386
- return true;
14387
- }
14388
- pathext = pathext.split(";");
14389
- if (pathext.indexOf("") !== -1) {
14390
- return true;
14391
- }
14392
- for (var i2 = 0; i2 < pathext.length; i2++) {
14393
- var p = pathext[i2].toLowerCase();
14394
- if (p && path2.substr(-p.length).toLowerCase() === p) {
14395
- return true;
14396
- }
14397
- }
14398
- return false;
14399
- }
14400
- function checkStat(stat2, path2, options) {
14401
- if (!stat2.isSymbolicLink() && !stat2.isFile()) {
14402
- return false;
14403
- }
14404
- return checkPathExt(path2, options);
14405
- }
14406
- function isexe(path2, options, cb) {
14407
- fs2.stat(path2, function(er, stat2) {
14408
- cb(er, er ? false : checkStat(stat2, path2, options));
14409
- });
14410
- }
14411
- function sync(path2, options) {
14412
- return checkStat(fs2.statSync(path2), path2, options);
14413
- }
14414
- return windows;
14415
- }
14416
- var mode;
14417
- var hasRequiredMode;
14418
- function requireMode() {
14419
- if (hasRequiredMode) return mode;
14420
- hasRequiredMode = 1;
14421
- mode = isexe;
14422
- isexe.sync = sync;
14423
- var fs2 = require$$0;
14424
- function isexe(path2, options, cb) {
14425
- fs2.stat(path2, function(er, stat2) {
14426
- cb(er, er ? false : checkStat(stat2, options));
14427
- });
14428
- }
14429
- function sync(path2, options) {
14430
- return checkStat(fs2.statSync(path2), options);
14431
- }
14432
- function checkStat(stat2, options) {
14433
- return stat2.isFile() && checkMode(stat2, options);
14434
- }
14435
- function checkMode(stat2, options) {
14436
- var mod = stat2.mode;
14437
- var uid = stat2.uid;
14438
- var gid = stat2.gid;
14439
- var myUid = options.uid !== void 0 ? options.uid : process.getuid && process.getuid();
14440
- var myGid = options.gid !== void 0 ? options.gid : process.getgid && process.getgid();
14441
- var u2 = parseInt("100", 8);
14442
- var g = parseInt("010", 8);
14443
- var o2 = parseInt("001", 8);
14444
- var ug = u2 | g;
14445
- var ret = mod & o2 || mod & g && gid === myGid || mod & u2 && uid === myUid || mod & ug && myUid === 0;
14446
- return ret;
14447
- }
14448
- return mode;
14449
- }
14450
- var isexe_1;
14451
- var hasRequiredIsexe;
14452
- function requireIsexe() {
14453
- if (hasRequiredIsexe) return isexe_1;
14454
- hasRequiredIsexe = 1;
14455
- var core;
14456
- if (process.platform === "win32" || commonjsGlobal.TESTING_WINDOWS) {
14457
- core = requireWindows();
14458
- } else {
14459
- core = requireMode();
14460
- }
14461
- isexe_1 = isexe;
14462
- isexe.sync = sync;
14463
- function isexe(path2, options, cb) {
14464
- if (typeof options === "function") {
14465
- cb = options;
14466
- options = {};
14467
- }
14468
- if (!cb) {
14469
- if (typeof Promise !== "function") {
14470
- throw new TypeError("callback not provided");
14471
- }
14472
- return new Promise(function(resolve2, reject) {
14473
- isexe(path2, options || {}, function(er, is) {
14474
- if (er) {
14475
- reject(er);
14476
- } else {
14477
- resolve2(is);
14478
- }
14479
- });
14480
- });
14481
- }
14482
- core(path2, options || {}, function(er, is) {
14483
- if (er) {
14484
- if (er.code === "EACCES" || options && options.ignoreErrors) {
14485
- er = null;
14486
- is = false;
14487
- }
14488
- }
14489
- cb(er, is);
14490
- });
14491
- }
14492
- function sync(path2, options) {
14493
- try {
14494
- return core.sync(path2, options || {});
14495
- } catch (er) {
14496
- if (options && options.ignoreErrors || er.code === "EACCES") {
14497
- return false;
14498
- } else {
14499
- throw er;
14500
- }
14501
- }
14502
- }
14503
- return isexe_1;
14504
- }
14505
- var which_1;
14506
- var hasRequiredWhich;
14507
- function requireWhich() {
14508
- if (hasRequiredWhich) return which_1;
14509
- hasRequiredWhich = 1;
14510
- const isWindows2 = process.platform === "win32" || process.env.OSTYPE === "cygwin" || process.env.OSTYPE === "msys";
14511
- const path2 = require$$0$1;
14512
- const COLON = isWindows2 ? ";" : ":";
14513
- const isexe = requireIsexe();
14514
- const getNotFoundError = (cmd) => Object.assign(new Error(`not found: ${cmd}`), { code: "ENOENT" });
14515
- const getPathInfo = (cmd, opt) => {
14516
- const colon = opt.colon || COLON;
14517
- const pathEnv = cmd.match(/\//) || isWindows2 && cmd.match(/\\/) ? [""] : [
14518
- // windows always checks the cwd first
14519
- ...isWindows2 ? [process.cwd()] : [],
14520
- ...(opt.path || process.env.PATH || /* istanbul ignore next: very unusual */
14521
- "").split(colon)
14522
- ];
14523
- const pathExtExe = isWindows2 ? opt.pathExt || process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM" : "";
14524
- const pathExt = isWindows2 ? pathExtExe.split(colon) : [""];
14525
- if (isWindows2) {
14526
- if (cmd.indexOf(".") !== -1 && pathExt[0] !== "")
14527
- pathExt.unshift("");
14528
- }
14529
- return {
14530
- pathEnv,
14531
- pathExt,
14532
- pathExtExe
14533
- };
14534
- };
14535
- const which = (cmd, opt, cb) => {
14536
- if (typeof opt === "function") {
14537
- cb = opt;
14538
- opt = {};
14539
- }
14540
- if (!opt)
14541
- opt = {};
14542
- const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
14543
- const found = [];
14544
- const step = (i2) => new Promise((resolve2, reject) => {
14545
- if (i2 === pathEnv.length)
14546
- return opt.all && found.length ? resolve2(found) : reject(getNotFoundError(cmd));
14547
- const ppRaw = pathEnv[i2];
14548
- const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
14549
- const pCmd = path2.join(pathPart, cmd);
14550
- const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
14551
- resolve2(subStep(p, i2, 0));
14552
- });
14553
- const subStep = (p, i2, ii) => new Promise((resolve2, reject) => {
14554
- if (ii === pathExt.length)
14555
- return resolve2(step(i2 + 1));
14556
- const ext = pathExt[ii];
14557
- isexe(p + ext, { pathExt: pathExtExe }, (er, is) => {
14558
- if (!er && is) {
14559
- if (opt.all)
14560
- found.push(p + ext);
14561
- else
14562
- return resolve2(p + ext);
14563
- }
14564
- return resolve2(subStep(p, i2, ii + 1));
14565
- });
14566
- });
14567
- return cb ? step(0).then((res) => cb(null, res), cb) : step(0);
14568
- };
14569
- const whichSync = (cmd, opt) => {
14570
- opt = opt || {};
14571
- const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
14572
- const found = [];
14573
- for (let i2 = 0; i2 < pathEnv.length; i2++) {
14574
- const ppRaw = pathEnv[i2];
14575
- const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
14576
- const pCmd = path2.join(pathPart, cmd);
14577
- const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
14578
- for (let j = 0; j < pathExt.length; j++) {
14579
- const cur = p + pathExt[j];
14580
- try {
14581
- const is = isexe.sync(cur, { pathExt: pathExtExe });
14582
- if (is) {
14583
- if (opt.all)
14584
- found.push(cur);
14585
- else
14586
- return cur;
14587
- }
14588
- } catch (ex) {
14589
- }
14590
- }
14591
- }
14592
- if (opt.all && found.length)
14593
- return found;
14594
- if (opt.nothrow)
14595
- return null;
14596
- throw getNotFoundError(cmd);
14597
- };
14598
- which_1 = which;
14599
- which.sync = whichSync;
14600
- return which_1;
14601
- }
14602
- var pathKey$1 = { exports: {} };
14603
- var hasRequiredPathKey;
14604
- function requirePathKey() {
14605
- if (hasRequiredPathKey) return pathKey$1.exports;
14606
- hasRequiredPathKey = 1;
14607
- const pathKey2 = (options = {}) => {
14608
- const environment = options.env || process.env;
14609
- const platform2 = options.platform || process.platform;
14610
- if (platform2 !== "win32") {
14611
- return "PATH";
14612
- }
14613
- return Object.keys(environment).reverse().find((key) => key.toUpperCase() === "PATH") || "Path";
14614
- };
14615
- pathKey$1.exports = pathKey2;
14616
- pathKey$1.exports.default = pathKey2;
14617
- return pathKey$1.exports;
14618
- }
14619
- var resolveCommand_1;
14620
- var hasRequiredResolveCommand;
14621
- function requireResolveCommand() {
14622
- if (hasRequiredResolveCommand) return resolveCommand_1;
14623
- hasRequiredResolveCommand = 1;
14624
- const path2 = require$$0$1;
14625
- const which = requireWhich();
14626
- const getPathKey = requirePathKey();
14627
- function resolveCommandAttempt(parsed, withoutPathExt) {
14628
- const env = parsed.options.env || process.env;
14629
- const cwd = process.cwd();
14630
- const hasCustomCwd = parsed.options.cwd != null;
14631
- const shouldSwitchCwd = hasCustomCwd && process.chdir !== void 0 && !process.chdir.disabled;
14632
- if (shouldSwitchCwd) {
14633
- try {
14634
- process.chdir(parsed.options.cwd);
14635
- } catch (err) {
14636
- }
14637
- }
14638
- let resolved;
14639
- try {
14640
- resolved = which.sync(parsed.command, {
14641
- path: env[getPathKey({ env })],
14642
- pathExt: withoutPathExt ? path2.delimiter : void 0
14643
- });
14644
- } catch (e) {
14645
- } finally {
14646
- if (shouldSwitchCwd) {
14647
- process.chdir(cwd);
14648
- }
14649
- }
14650
- if (resolved) {
14651
- resolved = path2.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
14652
- }
14653
- return resolved;
14654
- }
14655
- function resolveCommand(parsed) {
14656
- return resolveCommandAttempt(parsed) || resolveCommandAttempt(parsed, true);
14657
- }
14658
- resolveCommand_1 = resolveCommand;
14659
- return resolveCommand_1;
14660
- }
14661
- var _escape = {};
14662
- var hasRequired_escape;
14663
- function require_escape() {
14664
- if (hasRequired_escape) return _escape;
14665
- hasRequired_escape = 1;
14666
- const metaCharsRegExp2 = /([()\][%!^"`<>&|;, *?])/g;
14667
- function escapeCommand(arg) {
14668
- arg = arg.replace(metaCharsRegExp2, "^$1");
14669
- return arg;
14670
- }
14671
- function escapeArgument2(arg, doubleEscapeMetaChars) {
14672
- arg = `${arg}`;
14673
- arg = arg.replace(/(?=(\\+?)?)\1"/g, '$1$1\\"');
14674
- arg = arg.replace(/(?=(\\+?)?)\1$/, "$1$1");
14675
- arg = `"${arg}"`;
14676
- arg = arg.replace(metaCharsRegExp2, "^$1");
14677
- if (doubleEscapeMetaChars) {
14678
- arg = arg.replace(metaCharsRegExp2, "^$1");
14679
- }
14680
- return arg;
14681
- }
14682
- _escape.command = escapeCommand;
14683
- _escape.argument = escapeArgument2;
14684
- return _escape;
14685
- }
14686
- var shebangRegex;
14687
- var hasRequiredShebangRegex;
14688
- function requireShebangRegex() {
14689
- if (hasRequiredShebangRegex) return shebangRegex;
14690
- hasRequiredShebangRegex = 1;
14691
- shebangRegex = /^#!(.*)/;
14692
- return shebangRegex;
14693
- }
14694
- var shebangCommand;
14695
- var hasRequiredShebangCommand;
14696
- function requireShebangCommand() {
14697
- if (hasRequiredShebangCommand) return shebangCommand;
14698
- hasRequiredShebangCommand = 1;
14699
- const shebangRegex2 = requireShebangRegex();
14700
- shebangCommand = (string2 = "") => {
14701
- const match = string2.match(shebangRegex2);
14702
- if (!match) {
14703
- return null;
14704
- }
14705
- const [path2, argument] = match[0].replace(/#! ?/, "").split(" ");
14706
- const binary = path2.split("/").pop();
14707
- if (binary === "env") {
14708
- return argument;
14709
- }
14710
- return argument ? `${binary} ${argument}` : binary;
14711
- };
14712
- return shebangCommand;
14713
- }
14714
- var readShebang_1;
14715
- var hasRequiredReadShebang;
14716
- function requireReadShebang() {
14717
- if (hasRequiredReadShebang) return readShebang_1;
14718
- hasRequiredReadShebang = 1;
14719
- const fs2 = require$$0;
14720
- const shebangCommand2 = requireShebangCommand();
14721
- function readShebang2(command2) {
14722
- const size = 150;
14723
- const buffer = Buffer.alloc(size);
14724
- let fd;
14725
- try {
14726
- fd = fs2.openSync(command2, "r");
14727
- fs2.readSync(fd, buffer, 0, size, 0);
14728
- fs2.closeSync(fd);
14729
- } catch (e) {
14730
- }
14731
- return shebangCommand2(buffer.toString());
14732
- }
14733
- readShebang_1 = readShebang2;
14734
- return readShebang_1;
14735
- }
14736
- var parse_1;
14737
- var hasRequiredParse;
14738
- function requireParse() {
14739
- if (hasRequiredParse) return parse_1;
14740
- hasRequiredParse = 1;
14741
- const path2 = require$$0$1;
14742
- const resolveCommand = requireResolveCommand();
14743
- const escape = require_escape();
14744
- const readShebang2 = requireReadShebang();
14745
- const isWin = process.platform === "win32";
14746
- const isExecutableRegExp = /\.(?:com|exe)$/i;
14747
- const isCmdShimRegExp = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i;
14748
- function detectShebang(parsed) {
14749
- parsed.file = resolveCommand(parsed);
14750
- const shebang = parsed.file && readShebang2(parsed.file);
14751
- if (shebang) {
14752
- parsed.args.unshift(parsed.file);
14753
- parsed.command = shebang;
14754
- return resolveCommand(parsed);
14755
- }
14756
- return parsed.file;
14757
- }
14758
- function parseNonShell(parsed) {
14759
- if (!isWin) {
14760
- return parsed;
14761
- }
14762
- const commandFile = detectShebang(parsed);
14763
- const needsShell = !isExecutableRegExp.test(commandFile);
14764
- if (parsed.options.forceShell || needsShell) {
14765
- const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile);
14766
- parsed.command = path2.normalize(parsed.command);
14767
- parsed.command = escape.command(parsed.command);
14768
- parsed.args = parsed.args.map((arg) => escape.argument(arg, needsDoubleEscapeMetaChars));
14769
- const shellCommand = [parsed.command].concat(parsed.args).join(" ");
14770
- parsed.args = ["/d", "/s", "/c", `"${shellCommand}"`];
14771
- parsed.command = process.env.comspec || "cmd.exe";
14772
- parsed.options.windowsVerbatimArguments = true;
14773
- }
14774
- return parsed;
14775
- }
14776
- function parse2(command2, args, options) {
14777
- if (args && !Array.isArray(args)) {
14778
- options = args;
14779
- args = null;
14780
- }
14781
- args = args ? args.slice(0) : [];
14782
- options = Object.assign({}, options);
14783
- const parsed = {
14784
- command: command2,
14785
- args,
14786
- options,
14787
- file: void 0,
14788
- original: {
14789
- command: command2,
14790
- args
14791
- }
14792
- };
14793
- return options.shell ? parsed : parseNonShell(parsed);
14794
- }
14795
- parse_1 = parse2;
14796
- return parse_1;
14797
- }
14798
- var enoent;
14799
- var hasRequiredEnoent;
14800
- function requireEnoent() {
14801
- if (hasRequiredEnoent) return enoent;
14802
- hasRequiredEnoent = 1;
14803
- const isWin = process.platform === "win32";
14804
- function notFoundError(original, syscall) {
14805
- return Object.assign(new Error(`${syscall} ${original.command} ENOENT`), {
14806
- code: "ENOENT",
14807
- errno: "ENOENT",
14808
- syscall: `${syscall} ${original.command}`,
14809
- path: original.command,
14810
- spawnargs: original.args
14811
- });
14812
- }
14813
- function hookChildProcess(cp2, parsed) {
14814
- if (!isWin) {
14815
- return;
14816
- }
14817
- const originalEmit = cp2.emit;
14818
- cp2.emit = function(name, arg1) {
14819
- if (name === "exit") {
14820
- const err = verifyENOENT(arg1, parsed);
14821
- if (err) {
14822
- return originalEmit.call(cp2, "error", err);
14823
- }
14824
- }
14825
- return originalEmit.apply(cp2, arguments);
14826
- };
14827
- }
14828
- function verifyENOENT(status, parsed) {
14829
- if (isWin && status === 1 && !parsed.file) {
14830
- return notFoundError(parsed.original, "spawn");
14831
- }
14832
- return null;
14833
- }
14834
- function verifyENOENTSync(status, parsed) {
14835
- if (isWin && status === 1 && !parsed.file) {
14836
- return notFoundError(parsed.original, "spawnSync");
14837
- }
14838
- return null;
14839
- }
14840
- enoent = {
14841
- hookChildProcess,
14842
- verifyENOENT,
14843
- verifyENOENTSync,
14844
- notFoundError
14845
- };
14846
- return enoent;
14847
- }
14848
- var hasRequiredCrossSpawn;
14849
- function requireCrossSpawn() {
14850
- if (hasRequiredCrossSpawn) return crossSpawn.exports;
14851
- hasRequiredCrossSpawn = 1;
14852
- const cp2 = require$$0$2;
14853
- const parse2 = requireParse();
14854
- const enoent2 = requireEnoent();
14855
- function spawn2(command2, args, options) {
14856
- const parsed = parse2(command2, args, options);
14857
- const spawned = cp2.spawn(parsed.command, parsed.args, parsed.options);
14858
- enoent2.hookChildProcess(spawned, parsed);
14859
- return spawned;
14860
- }
14861
- function spawnSync2(command2, args, options) {
14862
- const parsed = parse2(command2, args, options);
14863
- const result = cp2.spawnSync(parsed.command, parsed.args, parsed.options);
14864
- result.error = result.error || enoent2.verifyENOENTSync(result.status, parsed);
14865
- return result;
14866
- }
14867
- crossSpawn.exports = spawn2;
14868
- crossSpawn.exports.spawn = spawn2;
14869
- crossSpawn.exports.sync = spawnSync2;
14870
- crossSpawn.exports._parse = parse2;
14871
- crossSpawn.exports._enoent = enoent2;
14872
- return crossSpawn.exports;
14873
- }
14874
- var crossSpawnExports = requireCrossSpawn();
14875
- const launch = /* @__PURE__ */ getDefaultExportFromCjs(crossSpawnExports);
14876
- function stop(proc) {
14877
- if (proc.exitCode !== null || proc.signalCode !== null)
14878
- return;
14879
- if (process.platform === "win32" && proc.pid) {
14880
- const out = spawnSync("taskkill", ["/pid", String(proc.pid), "/T", "/F"], { windowsHide: true });
14881
- if (!out.error && out.status === 0)
14882
- return;
14883
- }
14884
- proc.kill();
14885
- }
14886
- function bindAbort$1(proc, signal, onAbort) {
14887
- if (!signal)
14888
- return () => {
14889
- };
14890
- const abort = () => {
14891
- clear();
14892
- stop(proc);
14893
- onAbort?.();
14894
- };
14895
- const clear = () => {
14896
- signal.removeEventListener("abort", abort);
14897
- proc.off("exit", clear);
14898
- proc.off("error", clear);
14899
- };
14900
- signal.addEventListener("abort", abort, { once: true });
14901
- proc.on("exit", clear);
14902
- proc.on("error", clear);
14903
- if (signal.aborted)
14904
- abort();
14905
- return clear;
14906
- }
14907
- async function createOpencodeServer(options) {
14908
- options = Object.assign({
14909
- hostname: "127.0.0.1",
14910
- port: 4096,
14911
- timeout: 5e3
14912
- }, options ?? {});
14913
- const args = [`serve`, `--hostname=${options.hostname}`, `--port=${options.port}`];
14914
- if (options.config?.logLevel)
14915
- args.push(`--log-level=${options.config.logLevel}`);
14916
- const proc = launch(`opencode`, args, {
14917
- env: {
14918
- ...process.env,
14919
- OPENCODE_CONFIG_CONTENT: JSON.stringify(options.config ?? {})
14920
- }
14921
- });
14922
- let clear = () => {
14923
- };
14924
- const url = await new Promise((resolve2, reject) => {
14925
- const id = setTimeout(() => {
14926
- clear();
14927
- stop(proc);
14928
- reject(new Error(`Timeout waiting for server to start after ${options.timeout}ms`));
14929
- }, options.timeout);
14930
- let output = "";
14931
- let resolved = false;
14932
- proc.stdout?.on("data", (chunk) => {
14933
- if (resolved)
14934
- return;
14935
- output += chunk.toString();
14936
- const lines = output.split("\n");
14937
- for (const line of lines) {
14938
- if (line.startsWith("opencode server listening")) {
14939
- const match = line.match(/on\s+(https?:\/\/[^\s]+)/);
14940
- if (!match) {
14941
- clear();
14942
- stop(proc);
14943
- clearTimeout(id);
14944
- reject(new Error(`Failed to parse server url from output: ${line}`));
14945
- return;
14946
- }
14947
- clearTimeout(id);
14948
- resolved = true;
14949
- resolve2(match[1]);
14950
- return;
14951
- }
14952
- }
14953
- });
14954
- proc.stderr?.on("data", (chunk) => {
14955
- output += chunk.toString();
14956
- });
14957
- proc.on("exit", (code) => {
14958
- clearTimeout(id);
14959
- let msg = `Server exited with code ${code}`;
14960
- if (output.trim()) {
14961
- msg += `
14962
- Server output: ${output}`;
14963
- }
14964
- reject(new Error(msg));
14965
- });
14966
- proc.on("error", (error) => {
14967
- clearTimeout(id);
14968
- reject(error);
14969
- });
14970
- clear = bindAbort$1(proc, options.signal, () => {
14971
- clearTimeout(id);
14972
- reject(options.signal?.reason);
14973
- });
14974
- });
14975
- return {
14976
- url,
14977
- close() {
14978
- clear();
14979
- stop(proc);
14980
- }
14981
- };
14982
- }
14983
- function openCodeCliName(executable) {
14984
- return path.parse(executable).name.toLowerCase() === "opencode2" ? "opencode2" : "opencode";
14985
- }
14986
- function isOpenCodeV2(executable) {
14987
- return openCodeCliName(executable) === "opencode2";
14988
- }
14989
- function openCodeLoginCommand(cliName) {
14990
- return cliName === "opencode2" ? "opencode2 auth login" : "opencode auth login";
14991
- }
14992
- function parseOpenCodeAuthList(raw, exitCode) {
14993
- if (exitCode !== 0) return "unknown";
14994
- const text2 = stripAnsi(raw);
14995
- if (/no authenticated/i.test(text2)) return "unauthenticated";
14996
- if (/credential/i.test(text2)) return "authenticated";
14997
- const lines = text2.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
14998
- if (lines.some((line) => /^[A-Za-z0-9._-]+$/.test(line) && !/^(providers?|credentials?|integrations?)$/i.test(line))) {
14999
- return "authenticated";
13269
+ function requireGrokSuccess(result) {
13270
+ if (result.exitCode === 0) return;
13271
+ const diagnostic = `${result.stdout}
13272
+ ${result.stderr}`.toLowerCase();
13273
+ if (/not authenticated|not logged|login required|unauth|authentication|sign in/.test(diagnostic)) throw failure("AI_AUTH_REQUIRED", "Sign in with grok login to generate content.");
13274
+ if (/rate.?limit|quota|usage limit|too many requests|credit/.test(diagnostic)) throw failure("AI_RATE_LIMITED", "Grok rejected the request because of a usage limit.");
13275
+ if (/model.*(not found|unavailable|invalid|access|not supported)|unknown model/.test(diagnostic)) throw failure("AI_MODEL_UNAVAILABLE", "The selected Grok model is unavailable.");
13276
+ throw failure("AI_PROCESS_FAILED", "Grok could not generate content.");
13277
+ }
13278
+ function failure(code, message) {
13279
+ return new AiOperationError({ code, operation: "grok-generate", harness: "grok", message });
13280
+ }
13281
+ function openCodeCliName(executable) {
13282
+ return path.parse(executable).name.toLowerCase() === "opencode2" ? "opencode2" : "opencode";
13283
+ }
13284
+ function isOpenCodeV2Version(raw) {
13285
+ return /^(?:opencode2?\s+)?v?2\.\d+\.\d+(?:[-+][\w.-]+)?$/.test(stripAnsi(raw).trim());
13286
+ }
13287
+ function openCodeLoginCommand(cliName) {
13288
+ return cliName === "opencode2" ? "opencode2 auth login" : "opencode auth login";
13289
+ }
13290
+ function parseOpenCodeAuthList(raw, exitCode) {
13291
+ if (exitCode !== 0) return "unknown";
13292
+ try {
13293
+ const integrations = JSON.parse(stripAnsi(raw).trim());
13294
+ if (!Array.isArray(integrations) || integrations.some((item) => !item || typeof item !== "object" || !Array.isArray(item.connections))) return "unknown";
13295
+ return integrations.some((item) => item.connections.length > 0) ? "authenticated" : "unauthenticated";
13296
+ } catch {
15000
13297
  }
15001
13298
  return "unknown";
15002
13299
  }
15003
13300
  function parseOpenCodeModels(raw) {
15004
13301
  const seen = /* @__PURE__ */ new Set();
15005
- const models = stripAnsi(raw).split(/\r?\n/).map((line) => line.trim()).filter((line) => /^[A-Za-z0-9._-]+\/[A-Za-z0-9._:/-]+$/.test(line) && !seen.has(line) && seen.add(line)).map((id) => ({ id, label: id }));
15006
- return [DEFAULT_MODEL, ...models];
15007
- }
15008
- function parseOpenCodeV1Model(value) {
15009
- const slash = value.indexOf("/");
15010
- if (slash <= 0 || slash === value.length - 1) throw invalidModel();
15011
- return { providerID: value.slice(0, slash), modelID: value.slice(slash + 1) };
13302
+ const models = stripAnsi(raw).split(/\r?\n/).map((line) => line.trim()).filter((line) => /^[A-Za-z0-9._-]+\/[A-Za-z0-9._:/-]+(?:#[A-Za-z0-9._-]+)?$/.test(line) && !seen.has(line) && seen.add(line)).map((id) => ({ id, label: id }));
13303
+ return [DEFAULT_MODEL$1, ...models];
15012
13304
  }
15013
13305
  function parseOpenCodeV2ModelRef(value) {
15014
- const parsed = parseOpenCodeV1Model(value);
15015
- const hash = parsed.modelID.lastIndexOf("#");
15016
- if (hash > 0 && hash < parsed.modelID.length - 1) {
15017
- return { providerID: parsed.providerID, id: parsed.modelID.slice(0, hash), variant: parsed.modelID.slice(hash + 1) };
15018
- }
15019
- return { providerID: parsed.providerID, id: parsed.modelID };
13306
+ const match = /^([^/#\s]+)\/([^#\s]+)(?:#([^#\s]+))?$/.exec(value);
13307
+ if (!match?.[1] || !match[2]) throw invalidModel();
13308
+ return { providerID: match[1], id: match[2], ...match[3] ? { variant: match[3] } : {} };
15020
13309
  }
15021
13310
  function parseOpenCodeV2GenerateText(payload) {
15022
13311
  if (!payload || typeof payload !== "object") throw invalidOutput$1();
@@ -15046,13 +13335,14 @@ async function startOpenCodeV2Server(options) {
15046
13335
  const timeoutMs = options.timeoutMs;
15047
13336
  const password = randomBytes(24).toString("base64url");
15048
13337
  const port = await freePort();
15049
- const environment = Object.fromEntries(
13338
+ const environment = cleanEnvironment(Object.fromEntries(
15050
13339
  Object.entries({
15051
13340
  ...process.env,
13341
+ ...options.env,
15052
13342
  OPENCODE_SERVER_USERNAME: SERVER_USERNAME,
15053
13343
  OPENCODE_SERVER_PASSWORD: password
15054
13344
  }).filter((entry) => entry[1] !== void 0)
15055
- );
13345
+ ));
15056
13346
  const resolved = resolveProcessCommand(options.executable, options.cwd ?? process.cwd(), environment);
15057
13347
  if (!resolved.found) {
15058
13348
  throw new AiOperationError({ code: "AI_PROCESS_FAILED", operation: "opencode-server", harness: "opencode", message: "Could not start the local OpenCode server.", retryable: true });
@@ -15070,12 +13360,12 @@ async function startOpenCodeV2Server(options) {
15070
13360
  });
15071
13361
  void subprocess.catch(() => void 0);
15072
13362
  let settled = false;
15073
- const stop2 = () => {
13363
+ const stop = () => {
15074
13364
  if (settled) return;
15075
13365
  settled = true;
15076
13366
  stopProcess(subprocess);
15077
13367
  };
15078
- const unbind = bindAbort(options.signal, stop2);
13368
+ const unbind = bindAbort(options.signal, stop);
15079
13369
  try {
15080
13370
  const url = await waitForListenUrl(subprocess, timeoutMs, options.signal);
15081
13371
  return {
@@ -15083,12 +13373,12 @@ async function startOpenCodeV2Server(options) {
15083
13373
  password,
15084
13374
  close() {
15085
13375
  unbind();
15086
- stop2();
13376
+ stop();
15087
13377
  }
15088
13378
  };
15089
13379
  } catch (error) {
15090
13380
  unbind();
15091
- stop2();
13381
+ stop();
15092
13382
  if (options.signal?.aborted) {
15093
13383
  throw new AiOperationError({ code: "AI_CANCELLED", operation: "opencode-server", harness: "opencode", message: "Generation canceled." });
15094
13384
  }
@@ -15101,8 +13391,9 @@ async function generateOpenCodeV2Text(server, input) {
15101
13391
  const created = await v2Request(server, "/api/session", {
15102
13392
  method: "POST",
15103
13393
  body: {
15104
- title: "OpenTig commit message",
13394
+ title: "OpenTig text generation",
15105
13395
  location: { directory: input.cwd },
13396
+ permissions: [{ action: "*", resource: "*", effect: "deny" }],
15106
13397
  ...model ? { model } : {}
15107
13398
  },
15108
13399
  signal
@@ -15155,7 +13446,7 @@ function throwIfV2Failed(status, payload, signal) {
15155
13446
  if (status === 401 || /not logged|login required|unauth|authentication|sign in/.test(raw)) {
15156
13447
  throw new AiOperationError({ code: "AI_AUTH_REQUIRED", operation: "opencode-generate", harness: "opencode", message: "Sign in to OpenCode to generate the message." });
15157
13448
  }
15158
- if (/rate.?limit|quota|usage limit|too many requests|credit/.test(raw)) {
13449
+ if (status === 429 || /rate.?limit|quota|usage limit|too many requests|credit/.test(raw)) {
15159
13450
  throw new AiOperationError({ code: "AI_RATE_LIMITED", operation: "opencode-generate", harness: "opencode", message: "OpenCode rejected the request because of a usage limit.", retryable: true });
15160
13451
  }
15161
13452
  if (/model.*(not found|unavailable|invalid|access)|unknown model|no model specified/.test(raw)) {
@@ -15238,7 +13529,7 @@ function freePort() {
15238
13529
  server.on("error", reject);
15239
13530
  });
15240
13531
  }
15241
- const DISABLED_TOOLS = { bash: false, edit: false, write: false, read: false, glob: false, grep: false, webfetch: false, websearch: false, task: false };
13532
+ const UNSUPPORTED_MESSAGE = "OpenCode 2.x is required. Upgrade the installed CLI; OpenCode 1 and legacy beta builds are not supported.";
15242
13533
  class OpenCodeProvider {
15243
13534
  constructor(resolver, runner) {
15244
13535
  this.resolver = resolver;
@@ -15250,40 +13541,41 @@ class OpenCodeProvider {
15250
13541
  servers = /* @__PURE__ */ new Set();
15251
13542
  async status(forceRefresh = false) {
15252
13543
  const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
15253
- const executable = await this.resolver.resolve("opencode", forceRefresh);
15254
- if (!executable) return { id: this.id, label: "OpenCode", availability: "error", installed: false, authStatus: "unknown", message: "Install OpenCode to use this harness.", models: [DEFAULT_MODEL], checkedAt };
15255
- const cliName = openCodeCliName(executable);
15256
- const catalogArgs = cliName === "opencode2" ? ["models", "--standalone"] : ["models"];
15257
- const [version2, auth, catalog] = await Promise.all([
15258
- this.runner.run(executable, ["--version"]),
15259
- this.runner.run(executable, ["auth", "list"]),
15260
- this.runner.run(executable, catalogArgs, cliName === "opencode2" ? { timeoutMs: 3e4 } : {})
13544
+ const detected = await this.detect(forceRefresh);
13545
+ const failure2 = detectionFailure(detected, { id: this.id, label: "OpenCode", models: [DEFAULT_MODEL$1], checkedAt }, UNSUPPORTED_MESSAGE);
13546
+ if (failure2) return failure2;
13547
+ const executable = requireCandidate(detected, this.id);
13548
+ const version2 = detected.version ?? "";
13549
+ const cliName = openCodeCliName(executable.executable);
13550
+ const [auth, catalog] = await Promise.all([
13551
+ runCandidate(this.runner, executable, ["auth", "list", "--format", "json", "--standalone"]),
13552
+ runCandidate(this.runner, executable, ["models", "--standalone"], { timeoutMs: 3e4 })
15261
13553
  ]);
15262
- const models = catalog.exitCode === 0 ? parseOpenCodeModels(catalog.stdout) : [DEFAULT_MODEL];
15263
- let authStatus = parseOpenCodeAuthList(`${auth.stdout}
15264
- ${auth.stderr}`, auth.exitCode);
13554
+ const models = catalog.exitCode === 0 ? parseOpenCodeModels(catalog.stdout) : [DEFAULT_MODEL$1];
13555
+ let authStatus = parseOpenCodeAuthList(auth.stdout, auth.exitCode);
15265
13556
  if (authStatus === "unauthenticated" && models.length > 1) authStatus = "unknown";
15266
13557
  const confirmed = authStatus === "authenticated";
15267
13558
  return {
15268
13559
  id: this.id,
15269
13560
  label: "OpenCode",
15270
13561
  availability: models.length > 1 ? confirmed ? "ready" : "warning" : authStatus === "unauthenticated" ? "error" : "warning",
13562
+ ...detectionFields(detected),
15271
13563
  installed: true,
15272
13564
  authStatus,
15273
13565
  cliName,
15274
- version: version2.stdout.trim() || version2.stderr.trim(),
13566
+ version: version2,
15275
13567
  models,
15276
13568
  checkedAt,
15277
13569
  ...authStatus === "unauthenticated" ? { message: `Run ${openCodeLoginCommand(cliName)}.` } : !confirmed ? { message: "Could not confirm authentication for the selected provider." } : {}
15278
13570
  };
15279
13571
  }
15280
13572
  async generate(input) {
15281
- const executable = await this.resolver.resolve("opencode");
15282
- if (!executable) throw new AiOperationError({ code: "AI_CLI_NOT_FOUND", operation: "opencode-generate", harness: this.id, message: "OpenCode is not installed." });
13573
+ const detected = await this.detect(false, input.signal);
13574
+ const executable = requireCandidate(detected, this.id);
15283
13575
  const timeoutSignal = AbortSignal.timeout(AI_PROVIDER_TIMEOUT_MS);
15284
13576
  const boundedInput = { ...input, signal: AbortSignal.any([input.signal, timeoutSignal]) };
15285
13577
  try {
15286
- return await (isOpenCodeV2(executable) ? this.generateV2(executable, boundedInput) : this.generateV1(boundedInput));
13578
+ return await this.generateV2(executable, boundedInput);
15287
13579
  } catch (error) {
15288
13580
  if (timeoutSignal.aborted && !input.signal.aborted) {
15289
13581
  throw new AiOperationError({ code: "AI_TIMEOUT", operation: "opencode-generate", harness: this.id, message: "Generation took too long.", retryable: true });
@@ -15292,13 +13584,16 @@ ${auth.stderr}`, auth.exitCode);
15292
13584
  }
15293
13585
  }
15294
13586
  async generateV2(executable, input) {
15295
- const server = await startOpenCodeV2Server({ executable, cwd: input.repositoryPath, timeoutMs: 15e3, signal: input.signal }).catch((error) => {
13587
+ const server = await startOpenCodeV2Server({ executable: executable.executable, env: executable.env, cwd: input.repositoryPath, timeoutMs: 15e3, signal: input.signal }).catch((error) => {
15296
13588
  if (error instanceof AiOperationError) throw error;
15297
13589
  throw new AiOperationError({ code: "AI_PROCESS_FAILED", operation: "opencode-server", harness: this.id, message: "Could not start the local OpenCode server.", retryable: true });
15298
13590
  });
15299
13591
  this.servers.add(server);
15300
13592
  try {
15301
- const text2 = await generateOpenCodeV2Text(server, { prompt: input.prompt, model: input.model, cwd: input.repositoryPath, signal: input.signal });
13593
+ const text2 = await generateOpenCodeV2Text(server, { prompt: `Reply with a single JSON object satisfying this JSON Schema:
13594
+ ${JSON.stringify(input.schema)}
13595
+
13596
+ ${input.prompt}`, model: input.model, cwd: input.repositoryPath, signal: input.signal });
15302
13597
  return { output: parseJsonPayload(text2), usage: { ...EMPTY_AI_USAGE } };
15303
13598
  } catch (error) {
15304
13599
  if (input.signal.aborted) throw new AiOperationError({ code: "AI_CANCELLED", operation: "opencode-generate", harness: this.id, message: "Generation canceled." });
@@ -15311,53 +13606,8 @@ ${auth.stderr}`, auth.exitCode);
15311
13606
  this.servers.delete(server);
15312
13607
  }
15313
13608
  }
15314
- async generateV1(input) {
15315
- const server = await createOpencodeServer({
15316
- hostname: "127.0.0.1",
15317
- port: 0,
15318
- timeout: 15e3,
15319
- signal: input.signal,
15320
- config: { share: "disabled", autoupdate: false, plugin: [], instructions: [], tools: DISABLED_TOOLS, permission: { edit: "deny", bash: "deny", webfetch: "deny", external_directory: "deny" } }
15321
- }).catch(() => {
15322
- throw new AiOperationError({ code: "AI_PROCESS_FAILED", operation: "opencode-server", harness: this.id, message: "Could not start the local OpenCode server.", retryable: true });
15323
- });
15324
- this.servers.add(server);
15325
- const client2 = createOpencodeClient({ baseUrl: server.url, directory: input.repositoryPath });
15326
- let sessionId = null;
15327
- try {
15328
- const created = await client2.session.create({ body: { title: "OpenTig commit message" }, query: { directory: input.repositoryPath } });
15329
- if (!created.data?.id) throw new Error("missing session");
15330
- sessionId = created.data.id;
15331
- const model = input.model === "default" ? void 0 : parseOpenCodeV1Model(input.model);
15332
- const response = await client2.session.prompt({
15333
- path: { id: sessionId },
15334
- query: { directory: input.repositoryPath },
15335
- body: {
15336
- parts: [{ type: "text", text: input.prompt }],
15337
- tools: DISABLED_TOOLS,
15338
- // The server API takes no schema, unlike the Codex and Claude CLIs, so
15339
- // the only way to hold this harness to the contract is the system
15340
- // prompt. Without it the model answers in whatever shape it likes.
15341
- system: `Reply with a single JSON object and nothing else. It must satisfy this JSON Schema:
15342
- ${JSON.stringify(input.schema)}`,
15343
- ...model ? { model } : {}
15344
- }
15345
- });
15346
- const infoError = response.data?.info && "error" in response.data.info ? response.data.info.error : void 0;
15347
- if (infoError) throw new Error("provider rejected request");
15348
- const text2 = (response.data?.parts ?? []).filter((part) => part.type === "text" && "text" in part && typeof part.text === "string").map((part) => part.text).join("").trim();
15349
- return { output: parseJsonPayload(text2), usage: openCodeUsage(response.data?.info) };
15350
- } catch (error) {
15351
- if (input.signal?.aborted) throw new AiOperationError({ code: "AI_CANCELLED", operation: "opencode-generate", harness: this.id, message: "Generation canceled." });
15352
- if (error instanceof AiOperationError) throw error;
15353
- const synthetic = { exitCode: 1, stdout: "", stderr: error instanceof Error ? error.message : "" };
15354
- requireSuccess(synthetic, this.id, "opencode-generate");
15355
- throw error;
15356
- } finally {
15357
- if (sessionId) await client2.session.delete({ path: { id: sessionId }, query: { directory: input.repositoryPath } }).catch(() => void 0);
15358
- server.close();
15359
- this.servers.delete(server);
15360
- }
13609
+ async detect(forceRefresh = false, signal) {
13610
+ return selectCli(this.resolver, this.runner, this.id, { forceRefresh, compatible: isOpenCodeV2Version, runOptions: signal ? { signal } : {} });
15361
13611
  }
15362
13612
  close() {
15363
13613
  for (const server of this.servers) server.close();
@@ -15646,7 +13896,7 @@ class FileService {
15646
13896
  // UI pulls its children through here instead of paying for them at startup.
15647
13897
  async listDirectory(repositoryId, relativePath) {
15648
13898
  const repository = this.repositories.get(repositoryId);
15649
- const directory = this.repositories.resolvePath(repositoryId, relativePath);
13899
+ const directory = relativePath === "" ? repository.path : this.repositories.resolvePath(repositoryId, relativePath);
15650
13900
  const parent = normalizeGitPath(relativePath).replace(/\/+$/, "");
15651
13901
  let dirents;
15652
13902
  try {
@@ -15656,13 +13906,12 @@ class FileService {
15656
13906
  if (code === "ENOENT" || code === "ENOTDIR") return [];
15657
13907
  throw error;
15658
13908
  }
15659
- const ignored = (await this.findIgnoredDirectories(repository.path, [parent])).has(parent);
15660
13909
  const entries = [];
15661
13910
  for (const dirent of dirents) {
15662
13911
  if (dirent.name === ".git") continue;
15663
13912
  const entryPath = parent ? `${parent}/${dirent.name}` : dirent.name;
15664
13913
  if (dirent.isDirectory() && !dirent.isSymbolicLink()) {
15665
- entries.push({ path: entryPath, name: dirent.name, type: "directory", ...ignored ? { ignored: true } : {}, children: [] });
13914
+ entries.push({ path: entryPath, name: dirent.name, type: "directory", children: [] });
15666
13915
  continue;
15667
13916
  }
15668
13917
  if (!dirent.isFile() && !dirent.isSymbolicLink()) continue;
@@ -15673,15 +13922,25 @@ class FileService {
15673
13922
  name: dirent.name,
15674
13923
  type: "file",
15675
13924
  size: metadata.size,
15676
- mtimeMs: metadata.mtimeMs,
15677
- ...ignored ? { ignored: true } : {}
13925
+ mtimeMs: metadata.mtimeMs
15678
13926
  });
15679
13927
  } catch (error) {
15680
13928
  const code = error.code;
15681
13929
  if (code !== "ENOENT" && code !== "ENOTDIR") throw error;
15682
13930
  }
15683
13931
  }
15684
- return entries.sort((a2, b) => a2.type === b.type ? a2.name.localeCompare(b.name) : a2.type === "directory" ? -1 : 1);
13932
+ const ignoredChildren = await this.findIgnoredDirectories(repository.path, entries.map((entry) => entry.path));
13933
+ const ignoredFolders = entries.filter((entry) => entry.type === "directory" && ignoredChildren.has(entry.path));
13934
+ if (ignoredFolders.length > 0) {
13935
+ const tracked = await this.git.run(repository.path, ["ls-files", "-z", "--", ...ignoredFolders.map((entry) => entry.path)], {
13936
+ operation: "list-tracked-folder-children",
13937
+ readOnly: true,
13938
+ maxOutputBytes: 64 * 1024 * 1024
13939
+ });
13940
+ const ancestors = directoryAncestors(tracked.stdout.toString("utf8").split("\0").filter(Boolean).map(normalizeGitPath));
13941
+ for (const folder of ignoredFolders) if (ancestors.has(folder.path)) ignoredChildren.delete(folder.path);
13942
+ }
13943
+ return entries.map((entry) => ignoredChildren.has(entry.path) ? { ...entry, ignored: true } : entry).sort((a2, b) => a2.type === b.type ? a2.name.localeCompare(b.name) : a2.type === "directory" ? -1 : 1);
15685
13944
  }
15686
13945
  async findIgnoredDirectories(repositoryPath, directories) {
15687
13946
  const ignored = /* @__PURE__ */ new Set();
@@ -18709,6 +16968,12 @@ class GitHubService {
18709
16968
  const operation = "gh-pr-for-branch";
18710
16969
  const { repository, nameWithOwner } = await this.requireGitHub(repositoryId, operation);
18711
16970
  const branch = validateRef(branchName, operation);
16971
+ 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 }
16974
+ );
16975
+ const openPull = sortPullRequestsNewestFirst(parsePullRequestList(openResult.stdout)).find((pull) => pull.state === "OPEN");
16976
+ if (openPull) return openPull;
18712
16977
  const result = await this.runGh(
18713
16978
  ["pr", "list", "-R", nameWithOwner, "--head", branch, "--state", "all", "--json", PR_SUMMARY_FIELDS, "--limit", "10"],
18714
16979
  { cwd: repository.path, operation, timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 1024 * 1024 }
@@ -18821,12 +17086,13 @@ class GitHubService {
18821
17086
  }
18822
17087
  async checkStatus(forceRefresh) {
18823
17088
  const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
18824
- const executable = await this.resolver.resolve("gh", forceRefresh);
17089
+ const detected = await selectCli(this.resolver, this.runner, "gh", { forceRefresh, runOptions: { cwd: os.tmpdir(), env: { ...GH_ENV } } });
17090
+ const executable = detected.candidate;
18825
17091
  if (!executable) {
18826
17092
  return { installed: false, availability: "error", authStatus: "unknown", message: "Install GitHub CLI (cli.github.com) to work with pull requests.", checkedAt };
18827
17093
  }
18828
- const version2 = await this.run(executable, ["--version"], { cwd: os.tmpdir(), operation: "gh-version" });
18829
- const versionLabel = (version2.stdout.split(/\r?\n/, 1)[0] ?? "").trim() || version2.stderr.trim();
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 ?? "";
18830
17096
  const auth = await this.run(executable, ["auth", "status", "--hostname", "github.com", "--active"], { cwd: os.tmpdir(), operation: "gh-auth-status" });
18831
17097
  if (auth.exitCode !== 0) {
18832
17098
  return { installed: true, availability: "error", authStatus: "unauthenticated", version: versionLabel, message: "Run gh auth login.", checkedAt };
@@ -18845,7 +17111,8 @@ class GitHubService {
18845
17111
  return { repository, nameWithOwner: info.nameWithOwner };
18846
17112
  }
18847
17113
  async runGh(args, options) {
18848
- const executable = await this.resolver.resolve("gh");
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;
18849
17116
  if (!executable) throw new GhOperationError({ code: "GH_CLI_NOT_FOUND", operation: options.operation, message: "GitHub CLI is not installed." });
18850
17117
  const result = await this.run(executable, args, options);
18851
17118
  if (result.exitCode !== 0) throw classifyFailure(result, options.operation);
@@ -18853,9 +17120,9 @@ class GitHubService {
18853
17120
  }
18854
17121
  async run(executable, args, options) {
18855
17122
  try {
18856
- return await this.runner.run(executable, args, {
17123
+ return await this.runner.run(executable.executable, args, {
18857
17124
  cwd: options.cwd,
18858
- env: { ...GH_ENV },
17125
+ env: { ...executable.env, ...GH_ENV },
18859
17126
  ...options.timeoutMs !== void 0 ? { timeoutMs: options.timeoutMs } : {},
18860
17127
  ...options.maxOutputBytes !== void 0 ? { maxOutputBytes: options.maxOutputBytes } : {},
18861
17128
  ...options.stdin !== void 0 ? { stdin: options.stdin } : {}
@@ -19319,7 +17586,8 @@ const defaults = {
19319
17586
  uiFont: "plus-jakarta-sans",
19320
17587
  monoFont: "jetbrains-mono",
19321
17588
  commitMessageHarness: "codex",
19322
- commitMessageModels: { codex: "default", claude: "default", opencode: "default" },
17589
+ commitMessageModels: { codex: "default", claude: "default", opencode: "default", grok: "default" },
17590
+ aiExecutablePaths: {},
19323
17591
  shortcutOverrides: {},
19324
17592
  doubleControlShortcutEnabled: true,
19325
17593
  remoteFetchIntervalSeconds: DEFAULT_REMOTE_FETCH_INTERVAL_SECONDS
@@ -19399,7 +17667,7 @@ class SettingsStore {
19399
17667
  return this.data.activeRepositoryId;
19400
17668
  }
19401
17669
  get preferences() {
19402
- return { ...this.data.preferences, commitMessageModels: { ...this.data.preferences.commitMessageModels }, shortcutOverrides: { ...this.data.preferences.shortcutOverrides } };
17670
+ return { ...this.data.preferences, aiExecutablePaths: { ...this.data.preferences.aiExecutablePaths }, commitMessageModels: { ...this.data.preferences.commitMessageModels }, shortcutOverrides: { ...this.data.preferences.shortcutOverrides } };
19403
17671
  }
19404
17672
  get windowBounds() {
19405
17673
  return { ...this.data.windowBounds };
@@ -19528,6 +17796,8 @@ class SettingsStore {
19528
17796
  next.monoFont = isMonoFont(next.monoFont) ? next.monoFont : "jetbrains-mono";
19529
17797
  next.commitMessageHarness = isHarness(next.commitMessageHarness) ? next.commitMessageHarness : "codex";
19530
17798
  next.commitMessageModels = modelPreferences(next.commitMessageModels);
17799
+ next.aiExecutablePaths = executablePaths(next.aiExecutablePaths, true);
17800
+ delete next.aiShellEnvironment;
19531
17801
  next.shortcutOverrides = sanitizeShortcutOverrides(next.shortcutOverrides);
19532
17802
  next.doubleControlShortcutEnabled = typeof next.doubleControlShortcutEnabled === "boolean" ? next.doubleControlShortcutEnabled : this.defaultDoubleControlShortcutEnabled;
19533
17803
  next.remoteFetchIntervalSeconds = normalizeRemoteFetchIntervalSeconds(next.remoteFetchIntervalSeconds);
@@ -19656,6 +17926,7 @@ function validate(value, defaultDoubleControlShortcutEnabled) {
19656
17926
  monoFont: isMonoFont(parsedPreferences.data.monoFont) ? parsedPreferences.data.monoFont : "jetbrains-mono",
19657
17927
  commitMessageHarness: isHarness(parsedPreferences.data.commitMessageHarness) ? parsedPreferences.data.commitMessageHarness : "codex",
19658
17928
  commitMessageModels: modelPreferences(parsedPreferences.data.commitMessageModels),
17929
+ aiExecutablePaths: executablePaths(parsedPreferences.data.aiExecutablePaths),
19659
17930
  shortcutOverrides: sanitizeShortcutOverrides(parsedPreferences.data.shortcutOverrides),
19660
17931
  doubleControlShortcutEnabled: typeof parsedPreferences.data.doubleControlShortcutEnabled === "boolean" ? parsedPreferences.data.doubleControlShortcutEnabled : defaultDoubleControlShortcutEnabled,
19661
17932
  remoteFetchIntervalSeconds: normalizeRemoteFetchIntervalSeconds(
@@ -19726,7 +17997,7 @@ function projectError(message) {
19726
17997
  return new GitOperationError({ code: "INVALID_ARGUMENT", operation: "repository-projects", message });
19727
17998
  }
19728
17999
  function isHarness(value) {
19729
- return value === "codex" || value === "claude" || value === "opencode";
18000
+ return value === "codex" || value === "claude" || value === "opencode" || value === "grok";
19730
18001
  }
19731
18002
  function normalizeUiFont(value) {
19732
18003
  if (value === "cabinet-grotesk" || value === "satoshi") return "plus-jakarta-sans";
@@ -19741,9 +18012,9 @@ function normalizeSidebarWidth(value) {
19741
18012
  return Math.max(300, Math.min(680, width));
19742
18013
  }
19743
18014
  function modelPreferences(value) {
19744
- const result = { codex: "default", claude: "default", opencode: "default" };
18015
+ const result = { codex: "default", claude: "default", opencode: "default", grok: "default" };
19745
18016
  if (!value || typeof value !== "object") return result;
19746
- for (const harness of ["codex", "claude", "opencode"]) {
18017
+ for (const harness of ["codex", "claude", "opencode", "grok"]) {
19747
18018
  const model = value[harness];
19748
18019
  if (typeof model === "string" && model.length > 0 && model.length <= 200 && !hasControlCharacters$1(model)) result[harness] = model;
19749
18020
  }
@@ -19763,6 +18034,31 @@ function normalizeRecent(item) {
19763
18034
  repositoryName: typeof item.repositoryName === "string" && item.repositoryName ? item.repositoryName : path.basename(path.dirname(commonDir))
19764
18035
  };
19765
18036
  }
18037
+ function executablePaths(value, strict = false, platform2 = process.platform, home = os.homedir()) {
18038
+ const result = {};
18039
+ const invalid2 = () => {
18040
+ if (strict) throw projectError("CLI executable paths must be absolute paths on the backend host, without arguments or control characters.");
18041
+ };
18042
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
18043
+ invalid2();
18044
+ return result;
18045
+ }
18046
+ const p = platform2 === "win32" ? path.win32 : path.posix;
18047
+ for (const [key, raw] of Object.entries(value)) {
18048
+ if (!isHarness(key) || typeof raw !== "string") {
18049
+ invalid2();
18050
+ continue;
18051
+ }
18052
+ if (!raw.trim()) continue;
18053
+ const expanded = raw.startsWith("~/") ? p.join(home, raw.slice(2)) : raw;
18054
+ if (raw.length > 4096 || hasControlCharacters$1(raw) || !p.isAbsolute(expanded) || platform2 === "win32" && !/^(?:[a-z]:[\\/]|\\\\)/i.test(expanded)) {
18055
+ invalid2();
18056
+ continue;
18057
+ }
18058
+ result[key] = expanded;
18059
+ }
18060
+ return result;
18061
+ }
19766
18062
  class SystemTrash {
19767
18063
  available;
19768
18064
  operation;
@@ -19881,16 +18177,17 @@ async function createOpenTigRuntime(options) {
19881
18177
  const fileHistory = new FileOperationHistory(files, trash);
19882
18178
  const search = new SearchService(git, repositories, files, fileHistory);
19883
18179
  const operations = new GitRepositoryOperations(git, repositories, files);
19884
- const cliResolver = new CliResolver();
18180
+ const cliResolver = new CliResolver(void 0, () => settings.preferences);
19885
18181
  const cliRunner = new CliProcessRunner();
19886
18182
  const providers = [
19887
18183
  new CodexProvider(cliResolver, cliRunner),
19888
18184
  new ClaudeProvider(cliResolver, cliRunner),
19889
- new OpenCodeProvider(cliResolver, cliRunner)
18185
+ new OpenCodeProvider(cliResolver, cliRunner),
18186
+ new GrokProvider(cliResolver, cliRunner)
19890
18187
  ];
19891
18188
  const aiLog = new AiLogStore(options.aiLogPath);
19892
18189
  await aiLog.load();
19893
- const ai = new CommitMessageService(operations, providers, aiLog);
18190
+ const ai = new CommitMessageService(operations, providers, aiLog, () => cliResolver.invalidate());
19894
18191
  const prDrafts = new PullRequestDraftService(operations, providers, aiLog);
19895
18192
  const github = new GitHubService(cliResolver, cliRunner, git, repositories);
19896
18193
  let runtime = null;
@@ -20370,22 +18667,66 @@ function mimeTypeOf(relativePath, bytes) {
20370
18667
  if (fromPath === "image/svg+xml") return fromPath;
20371
18668
  return detectRasterImageMime(bytes) ?? fromPath;
20372
18669
  }
20373
- async function browseServerDirectories(requestedPath) {
20374
- const input = requestedPath ?? homedir();
20375
- const expanded = input === "~" ? homedir() : /^~[/\\]/.test(input) ? join(homedir(), input.slice(2)) : input;
20376
- if (!isAbsolute(expanded)) throw new Error("Enter an absolute folder path on the server.");
18670
+ async function browseServerDirectories(requestedPath, git = new GitProcess()) {
18671
+ const home = homedir();
18672
+ const input = requestedPath ?? home;
18673
+ const expanded = input === "~" ? home : /^~[/\\]/.test(input) ? join(home, input.slice(2)) : input;
18674
+ if (!isAbsolute(expanded)) throw new Error("Enter an absolute folder path.");
20377
18675
  const path2 = resolve(expanded);
20378
- const entries = await readdir(path2, { withFileTypes: true });
18676
+ let entries;
18677
+ try {
18678
+ entries = await readdir(path2, { withFileTypes: true });
18679
+ } catch (error) {
18680
+ const code = error.code;
18681
+ if (code === "EACCES" || code === "EPERM") throw new Error("You do not have permission to browse this folder.", { cause: error });
18682
+ if (code === "ENOENT") throw new Error("That folder was not found. Check the path and try again.", { cause: error });
18683
+ if (code === "ENOTDIR") throw new Error("This path points to a file. Choose a folder instead.", { cause: error });
18684
+ throw new Error("Could not read this folder. Try another location.", { cause: error });
18685
+ }
20379
18686
  const directories = [];
20380
- for (const entry of entries) {
20381
- const entryPath = join(path2, entry.name);
20382
- if (entry.isDirectory() || entry.isSymbolicLink() && await stat(entryPath).then((value) => value.isDirectory(), () => false)) {
20383
- directories.push({ name: entry.name, path: entryPath });
18687
+ let index = 0;
18688
+ await Promise.all(Array.from({ length: Math.min(6, entries.length) }, async () => {
18689
+ while (index < entries.length) {
18690
+ const entry = entries[index++];
18691
+ const entryPath = join(path2, entry.name);
18692
+ if (entry.isDirectory() || entry.isSymbolicLink() && await stat(entryPath).then((value) => value.isDirectory(), () => false)) {
18693
+ directories.push({ name: entry.name, path: entryPath, repository: await inspectRepository(entryPath, git) });
18694
+ }
20384
18695
  }
20385
- }
18696
+ }));
20386
18697
  directories.sort((a2, b) => a2.name.localeCompare(b.name, void 0, { numeric: true, sensitivity: "base" }));
18698
+ const locations = [{ name: "Home", path: home, kind: "home" }];
18699
+ for (const [name, kind] of [["Documents", "documents"], ["Downloads", "downloads"]]) {
18700
+ const location = join(home, name);
18701
+ if (await stat(location).then((value) => value.isDirectory(), () => false)) locations.push({ name, path: location, kind });
18702
+ }
20387
18703
  const parentPath = dirname(path2);
20388
- return { path: path2, parentPath: parentPath === path2 ? null : parentPath, directories };
18704
+ return { path: path2, parentPath: parentPath === path2 ? null : parentPath, repository: await inspectRepository(path2, git), locations, directories };
18705
+ }
18706
+ async function inspectRepository(path2, git) {
18707
+ if (!await stat(join(path2, ".git")).then((value) => value.isDirectory() || value.isFile(), () => false)) return null;
18708
+ try {
18709
+ const result = await git.run(path2, ["rev-parse", "--is-inside-work-tree", "--absolute-git-dir"], {
18710
+ operation: "browse-repository",
18711
+ readOnly: true,
18712
+ timeoutMs: 2e3,
18713
+ maxOutputBytes: 32768
18714
+ });
18715
+ const [inside, gitDir] = result.stdout.toString("utf8").trim().split(/\r?\n/);
18716
+ if (inside !== "true" || !gitDir || !isAbsolute(gitDir)) return null;
18717
+ const headFile = await open(join(gitDir, "HEAD"), "r");
18718
+ let head;
18719
+ try {
18720
+ const buffer = Buffer.alloc(4096);
18721
+ const { bytesRead } = await headFile.read(buffer, 0, buffer.length, 0);
18722
+ head = buffer.subarray(0, bytesRead).toString("utf8").trim();
18723
+ } finally {
18724
+ await headFile.close();
18725
+ }
18726
+ return { branch: head.startsWith("ref: refs/heads/") ? head.slice("ref: refs/heads/".length) : null };
18727
+ } catch {
18728
+ return null;
18729
+ }
20389
18730
  }
20390
18731
  const requestRecordSchema = looseObject({});
20391
18732
  function boundedStringSchema(maxLength, options = {}) {
@@ -20408,7 +18749,7 @@ const searchOptionsSchema = object({
20408
18749
  includeIgnored: unknown().optional()
20409
18750
  });
20410
18751
  const openFileTabSchema = object({ path: unknown(), pinned: boolean() });
20411
- const aiHarnessSchema = _enum(["codex", "claude", "opencode"]);
18752
+ const aiHarnessSchema = _enum(["codex", "claude", "opencode", "grok"]);
20412
18753
  const pullRequestStateSchema = _enum(["OPEN", "CLOSED", "MERGED"]);
20413
18754
  function stringArg(value, operation, maxLength = 32768) {
20414
18755
  const parsed = boundedStringSchema(maxLength, { controls: false }).safeParse(value);
@@ -20729,6 +19070,7 @@ function registerServerCommands(registry2, services, host) {
20729
19070
  });
20730
19071
  handle(IPC.preferences, "preferences", async (partial2) => {
20731
19072
  const preferences = await services.settings.setPreferences(partial2 ?? {});
19073
+ if (partial2 && typeof partial2 === "object" && "aiExecutablePaths" in partial2) services.ai.invalidateStatuses();
20732
19074
  return preferences;
20733
19075
  });
20734
19076
  handle(IPC.filesTreeStateUpdate, "files-tree-state", (repositoryId, expandedPaths) => {
@@ -20748,7 +19090,8 @@ function registerServerCommands(registry2, services, host) {
20748
19090
  context
20749
19091
  ));
20750
19092
  handle(IPC.repositoryBrowseDirectories, "browse-directories", (path2) => browseServerDirectories(
20751
- path2 === void 0 ? void 0 : stringArg(path2, "browse-directories", 32768)
19093
+ path2 === void 0 ? void 0 : stringArg(path2, "browse-directories", 32768),
19094
+ services.git
20752
19095
  ));
20753
19096
  handleWithContext(IPC.repositoryOpenRecent, "open-recent", async (context, id) => {
20754
19097
  const repositoryId = stringArg(id, "open-recent", 64);
@@ -20786,7 +19129,7 @@ function registerServerCommands(registry2, services, host) {
20786
19129
  ));
20787
19130
  handle(IPC.repositoryDirectoryEntries, "directory-entries", (id, directoryPath) => services.files.listDirectory(
20788
19131
  stringArg(id, "directory-entries", 64),
20789
- stringArg(directoryPath, "directory-entries")
19132
+ directoryPath === "" ? "" : stringArg(directoryPath, "directory-entries")
20790
19133
  ));
20791
19134
  handle(IPC.repositoryReadFile, "read-file", (id, filePath, allowLarge) => services.files.read(stringArg(id, "read-file", 64), stringArg(filePath, "read-file"), allowLarge === true));
20792
19135
  handle(IPC.repositoryReadImage, "read-image", (id, filePath) => services.files.readImage(
@@ -22098,12 +20441,12 @@ class OpenTigWebSocketTransport {
22098
20441
  upgradeHandler;
22099
20442
  closePromise = null;
22100
20443
  get connectedSessionCount() {
22101
- return new Set([...this.clients.values()].map((client2) => client2.sessionId)).size;
20444
+ return new Set([...this.clients.values()].map((client) => client.sessionId)).size;
22102
20445
  }
22103
20446
  connectionCount(sessionId) {
22104
20447
  let count2 = 0;
22105
- for (const client2 of this.clients.values()) {
22106
- if (client2.sessionId === sessionId) count2 += 1;
20448
+ for (const client of this.clients.values()) {
20449
+ if (client.sessionId === sessionId) count2 += 1;
22107
20450
  }
22108
20451
  return count2;
22109
20452
  }
@@ -22149,7 +20492,7 @@ class OpenTigWebSocketTransport {
22149
20492
  socket.on("close", () => {
22150
20493
  for (const controller of state.requests.values()) controller.abort();
22151
20494
  this.clients.delete(socket);
22152
- if (![...this.clients.values()].some((client2) => client2.sessionId === sessionId)) {
20495
+ if (![...this.clients.values()].some((client) => client.sessionId === sessionId)) {
22153
20496
  this.options.registry.clearSession(sessionId);
22154
20497
  }
22155
20498
  });