@opentig/cli 0.1.8 → 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.
- package/THIRD_PARTY_NOTICES.md +3 -1
- package/dist/bin.mjs +2 -2
- package/dist/chunks/{cli-BgNif2D1.mjs → cli-Bi6ET5UT.mjs} +15 -2
- package/dist/client/assets/App-BXZLGT86.js +74 -0
- package/dist/client/assets/CreatePullRequestDialog-DFonMYEx.js +2 -0
- package/dist/client/assets/{DiffWorkspace-Ca2ZUlVn.js → DiffWorkspace-RDG_Y70N.js} +5 -5
- package/dist/client/assets/{EditableFileViewer-85aEeMBK.js → EditableFileViewer-CsbifqBO.js} +1 -1
- package/dist/client/assets/{File-DTtWrbYf.js → File-CHjvYpb2.js} +1 -1
- package/dist/client/assets/FilesView-Dl8QFbFA.js +1 -0
- package/dist/client/assets/GitHubAvatar-CnBaloV0.js +1 -0
- package/dist/client/assets/HistoryView-BgCZU2JO.js +1 -0
- package/dist/client/assets/{HtmlFileViewer-bL6T57PQ.js → HtmlFileViewer-h8ULU8Gy.js} +1 -1
- package/dist/client/assets/{IconEdit-DePJMGIi.js → IconEdit-Cg8S2wqW.js} +1 -1
- package/dist/client/assets/IconFolderSymlink-BZSLX1ez.js +1 -0
- package/dist/client/assets/{ImageFileViewer-DJaHH3hJ.js → ImageFileViewer-Cjw58ckv.js} +1 -1
- package/dist/client/assets/LocalRefsDialog-DNgrd7Qp.js +1 -0
- package/dist/client/assets/{MarkdownFileViewer-BNPb9v8u.js → MarkdownFileViewer-kLiduag6.js} +2 -2
- package/dist/client/assets/{PairingPage-D9oguvKU.js → PairingPage-C97sv-8s.js} +1 -1
- package/dist/client/assets/{PierreDiffViewer-8wTLYd5-.js → PierreDiffViewer-eAcugmoF.js} +1 -1
- package/dist/client/assets/{PierreEditBoundary-KzU3HDc2.js → PierreEditBoundary-DlGd1pqO.js} +1 -1
- package/dist/client/assets/{PierreWorkerPool-DH2jp9oe.js → PierreWorkerPool-Col4k7qz.js} +1 -1
- package/dist/client/assets/{PullRequestDiff-DK-m07ZP.js → PullRequestDiff-DhKTXyIF.js} +1 -1
- package/dist/client/assets/PullRequestViewer-BgI8HFMe.js +2 -0
- package/dist/client/assets/PullRequestsView-PYWLy8We.js +1 -0
- package/dist/client/assets/QuickOpenDialog-BFDDZVCL.js +1 -0
- package/dist/client/assets/RepositoryProjectsDialog-CvB8KcFp.js +1 -0
- package/dist/client/assets/SearchView-BwF_MsxK.js +1 -0
- package/dist/client/assets/SettingsDialog-DIcLmXSE.js +9 -0
- package/dist/client/assets/Viewer-o1hDU33O.js +3 -0
- package/dist/client/assets/{abnfDiagram-N423BO3Z-CkHOGdAl.js → abnfDiagram-N423BO3Z-BvrnK_R-.js} +1 -1
- package/dist/client/assets/{arc-Dj8F8CO5.js → arc-DLa3_7ua.js} +1 -1
- package/dist/client/assets/{architectureDiagram-T3A2C74G-g87Y5LXk.js → architectureDiagram-T3A2C74G-BexwhnyX.js} +1 -1
- package/dist/client/assets/base-ui-CJ8ZIx3d.js +1 -0
- package/dist/client/assets/{blockDiagram-VBNYF7ZC-D6efB7jD.js → blockDiagram-VBNYF7ZC-CoOttUUo.js} +1 -1
- package/dist/client/assets/{c4Diagram-5PPSVZJV-DoZEC2xg.js → c4Diagram-5PPSVZJV-Bgp6ppUh.js} +1 -1
- package/dist/client/assets/channel-1k1AYpyV.js +1 -0
- package/dist/client/assets/{checkbox-DYwmgDPS.js → checkbox-BRH0ZNIC.js} +1 -1
- package/dist/client/assets/{chunk-2GRJ4B5K-Dr44hDNV.js → chunk-2GRJ4B5K-C_kzp38w.js} +1 -1
- package/dist/client/assets/{chunk-2Q5K7J3B-YIEyVOOL.js → chunk-2Q5K7J3B-CysYnRkJ.js} +1 -1
- package/dist/client/assets/{chunk-5RXB4S5H-gjmGBtkE.js → chunk-5RXB4S5H-oMS6x_ag.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-DCOQko4g.js → chunk-5VM5RSS4-C5-dW5wf.js} +1 -1
- package/dist/client/assets/{chunk-6Q2QTUOP-DfGlvPVm.js → chunk-6Q2QTUOP-DxahInnA.js} +1 -1
- package/dist/client/assets/{chunk-GF5L2VYU-CCWOLPVL.js → chunk-GF5L2VYU-D7V-3eRg.js} +1 -1
- package/dist/client/assets/{chunk-JWPE2WC7-CFRTGh8r.js → chunk-JWPE2WC7-BrLmjbVP.js} +1 -1
- package/dist/client/assets/{chunk-KBJHAD2P-By3wsJVD.js → chunk-KBJHAD2P-CyOvijwM.js} +1 -1
- package/dist/client/assets/{chunk-RYQCIY6F-C0wmsHmy.js → chunk-RYQCIY6F-BvEjVjQs.js} +1 -1
- package/dist/client/assets/{chunk-XXDRQBXY-t3SnJvV_.js → chunk-XXDRQBXY-C_IY9A64.js} +1 -1
- package/dist/client/assets/classDiagram-JCYQIIEL-ChyNFpay.js +1 -0
- package/dist/client/assets/classDiagram-v2-OCEON4UE-ChyNFpay.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-D3VltoVk.js → cose-bilkent-JH36ORCC-Cu7qZi7h.js} +1 -1
- package/dist/client/assets/{cynefin-VYW2F7L2-DwLwbeoe.js → cynefin-VYW2F7L2-B4M1t9lL.js} +1 -1
- package/dist/client/assets/{cynefinDiagram-MW4NZA55-B23kuaQF.js → cynefinDiagram-MW4NZA55-CAosOLmT.js} +1 -1
- package/dist/client/assets/{dagre-VZM6K2ZE-SgMrTr2p.js → dagre-VZM6K2ZE-q354V538.js} +1 -1
- package/dist/client/assets/{diagram-7IWD3JNH-C7cQN4aD.js → diagram-7IWD3JNH-C_CzkBPt.js} +1 -1
- package/dist/client/assets/{diagram-B4RE2ZJO-CzeI3yOw.js → diagram-B4RE2ZJO-VvvYHE9c.js} +1 -1
- package/dist/client/assets/{diagram-LBJQPF4R-DhKm4hlI.js → diagram-LBJQPF4R-D8vRQwb5.js} +1 -1
- package/dist/client/assets/{diagram-Q27KOJAE-BXlU6Wp3.js → diagram-Q27KOJAE-Bul4aDKg.js} +1 -1
- package/dist/client/assets/{diagram-UB23O5K3-BOwY_YwR.js → diagram-UB23O5K3-DNadEE58.js} +1 -1
- package/dist/client/assets/{ebnfDiagram-BXEA7PRR-CuIQuYPa.js → ebnfDiagram-BXEA7PRR-Bf-68MMZ.js} +1 -1
- package/dist/client/assets/{erDiagram-JOGREHBK-d45NhKfk.js → erDiagram-JOGREHBK-CJ-jHFsf.js} +1 -1
- package/dist/client/assets/{flowDiagram-UKHOOZJN-Dzq-6qo7.js → flowDiagram-UKHOOZJN-DMOdinBU.js} +1 -1
- package/dist/client/assets/{ganttDiagram-PKOTCBZU-RVlL9mX2.js → ganttDiagram-PKOTCBZU-DzezS3Zy.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-DS77QQ5N-8kEJVUFH.js → gitGraphDiagram-DS77QQ5N-Dftpvns8.js} +1 -1
- package/dist/client/assets/index-BfA4tAc1.css +1 -0
- package/dist/client/assets/{index-CQcx9aRt.js → index-DAUK3rnz.js} +4 -4
- package/dist/client/assets/{infoDiagram-6WML65LV-Co21Ql9M.js → infoDiagram-6WML65LV-DmgUBq17.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-WSZJBQD7-BvPB_G97.js → ishikawaDiagram-WSZJBQD7-CL0PQhpQ.js} +1 -1
- package/dist/client/assets/{journeyDiagram-NVQOT4AX-k7x89IE9.js → journeyDiagram-NVQOT4AX-Ck4pQx_A.js} +1 -1
- package/dist/client/assets/{kanban-definition-27J2QSJJ-BjrgN30t.js → kanban-definition-27J2QSJJ-DuRZxBql.js} +1 -1
- package/dist/client/assets/{linear-m158kske.js → linear-C2JDCaz4.js} +1 -1
- package/dist/client/assets/{mermaid.core-C0FKucv-.js → mermaid.core-9dt2ugQA.js} +4 -4
- package/dist/client/assets/{mindmap-definition-FAOFIHXS-CF8noycK.js → mindmap-definition-FAOFIHXS-vy0fdDF0.js} +1 -1
- package/dist/client/assets/{pegDiagram-VL7TDLO6-_y7nfJFV.js → pegDiagram-VL7TDLO6-DjVJy-Gm.js} +1 -1
- package/dist/client/assets/{pieDiagram-7S7Q4E2Y-DhdWHi45.js → pieDiagram-7S7Q4E2Y-ATbd_8vw.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-CIZ2JOQS-BRvEIECQ.js → quadrantDiagram-CIZ2JOQS-C8o6ofcl.js} +1 -1
- package/dist/client/assets/{railroadDiagram-AXF67PYL-BgLZ8XU1.js → railroadDiagram-AXF67PYL-Qvij9Ubo.js} +1 -1
- package/dist/client/assets/{render-markdown-2GjhuGA-.js → render-markdown-BtCybfvQ.js} +6 -6
- package/dist/client/assets/{requirementDiagram-LRYGKXZP-CX-gxyGF.js → requirementDiagram-LRYGKXZP-yLfrba2h.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-W5VNT64P-CLSjSU_X.js → sankeyDiagram-W5VNT64P-BTXRtTyG.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-SI44F4Z6-dGlHyZRB.js → sequenceDiagram-SI44F4Z6-BN1CCZIX.js} +1 -1
- package/dist/client/assets/{sizeCapture-X5ZJPWSS-BH3qO9T3.js → sizeCapture-X5ZJPWSS-BfXjBBYo.js} +1 -1
- package/dist/client/assets/{stateDiagram-OKZ733FA-FXxo1iIM.js → stateDiagram-OKZ733FA-DMAWiBsP.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-UEYNNEHI-D_ofZeaa.js +1 -0
- package/dist/client/assets/{swimlanes-SLNWSIFB-VJGIuM4S.js → swimlanes-SLNWSIFB-BD20m5cw.js} +2 -2
- package/dist/client/assets/swimlanesDiagram-ULZ7WXOC-CF9m5JSB.js +8 -0
- package/dist/client/assets/{tanstack-CtfgE41X.js → tanstack-B0Sf7wNU.js} +1 -1
- package/dist/client/assets/{timeline-definition-Z64GVDOM-BXNTAanY.js → timeline-definition-Z64GVDOM-gtc0-AHB.js} +1 -1
- package/dist/client/assets/{vennDiagram-T6HMQDX7-D9nXAts5.js → vennDiagram-T6HMQDX7-Cdr4AXTp.js} +1 -1
- package/dist/client/assets/viewer-tabs-D-T0xQaq.js +1 -0
- package/dist/client/assets/{wardleyDiagram-T6FBY63Y-D89EaEfa.js → wardleyDiagram-T6FBY63Y-BcU4vxO8.js} +1 -1
- package/dist/client/assets/{xychartDiagram-ELKLHX3M-B_xFDiIh.js → xychartDiagram-ELKLHX3M-B3Zuadj8.js} +1 -1
- package/dist/client/index.html +4 -4
- package/dist/dev.mjs +1 -1
- package/dist/manifest.json +1 -1
- package/dist/server.mjs +526 -127
- package/dist/utility.mjs +1 -1
- package/package.json +2 -2
- package/dist/client/assets/App-9EfSdZB8.js +0 -74
- package/dist/client/assets/CreatePullRequestDialog-BgVrWdkH.js +0 -2
- package/dist/client/assets/FilesView-rT5hzb7y.js +0 -1
- package/dist/client/assets/GitHubAvatar-EO1TD_1z.js +0 -1
- package/dist/client/assets/HistoryView-C4c0CqMC.js +0 -1
- package/dist/client/assets/IconFolderPlus-ByGyA0Di.js +0 -1
- package/dist/client/assets/LocalRefsDialog-D9wYZWBg.js +0 -1
- package/dist/client/assets/PullRequestViewer-CpLwq4o2.js +0 -2
- package/dist/client/assets/PullRequestsView-DnvXaJ4t.js +0 -1
- package/dist/client/assets/QuickOpenDialog-DIWjfUkt.js +0 -1
- package/dist/client/assets/RepositoryProjectsDialog-BsQn5Yjr.js +0 -1
- package/dist/client/assets/SearchView-D-91dtSv.js +0 -1
- package/dist/client/assets/SettingsDialog-dJUYk2cP.js +0 -9
- package/dist/client/assets/Viewer-GH-5bJW4.js +0 -3
- package/dist/client/assets/base-ui-DT3JsyND.js +0 -1
- package/dist/client/assets/channel-Dsy2VD23.js +0 -1
- package/dist/client/assets/classDiagram-JCYQIIEL-BBQJgoWd.js +0 -1
- package/dist/client/assets/classDiagram-v2-OCEON4UE-BBQJgoWd.js +0 -1
- package/dist/client/assets/gh-utils-LsOSydCg.js +0 -1
- package/dist/client/assets/index-DMbFAH51.css +0 -1
- package/dist/client/assets/stateDiagram-v2-UEYNNEHI-DqxQ14yP.js +0 -1
- package/dist/client/assets/swimlanesDiagram-ULZ7WXOC-No0dCAkd.js +0 -8
- package/dist/client/assets/viewer-tabs-DkLmDzFg.js +0 -1
package/dist/server.mjs
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
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,
|
|
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";
|
|
@@ -6872,6 +6872,153 @@ createExeca(() => ({ isSync: true }));
|
|
|
6872
6872
|
createExeca(mapNode);
|
|
6873
6873
|
createExeca(mapScriptAsync, {}, deepScriptOptions, setScriptSync);
|
|
6874
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
|
+
}
|
|
6875
7022
|
class GitOperationError extends Error {
|
|
6876
7023
|
detail;
|
|
6877
7024
|
constructor(detail) {
|
|
@@ -6939,17 +7086,18 @@ class CliProcessRunner {
|
|
|
6939
7086
|
async run(command2, args, options = {}) {
|
|
6940
7087
|
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
6941
7088
|
const maxOutputBytes = options.maxOutputBytes ?? 2 * 1024 * 1024;
|
|
6942
|
-
const environment = Object.fromEntries(
|
|
7089
|
+
const environment = cleanEnvironment(Object.fromEntries(
|
|
6943
7090
|
Object.entries({ ...process.env, ...options.env }).filter((entry) => entry[1] !== void 0)
|
|
6944
|
-
);
|
|
7091
|
+
));
|
|
6945
7092
|
for (const key of options.removeEnv ?? []) delete environment[key];
|
|
6946
7093
|
if (this.closePromise || options.signal?.aborted) throw cancelled();
|
|
6947
7094
|
const lifecycle = new AbortController();
|
|
6948
7095
|
this.active.add(lifecycle);
|
|
6949
7096
|
const signal = options.signal ? AbortSignal.any([options.signal, lifecycle.signal]) : lifecycle.signal;
|
|
6950
|
-
|
|
7097
|
+
let resolvedCommand;
|
|
6951
7098
|
let result;
|
|
6952
7099
|
try {
|
|
7100
|
+
resolvedCommand = resolveProcessCommand(command2, options.cwd ?? process.cwd(), environment);
|
|
6953
7101
|
result = await execa(resolvedCommand.file, args, {
|
|
6954
7102
|
...options.cwd ? { cwd: options.cwd } : {},
|
|
6955
7103
|
cancelSignal: signal,
|
|
@@ -6964,9 +7112,9 @@ class CliProcessRunner {
|
|
|
6964
7112
|
input: options.stdin ?? "",
|
|
6965
7113
|
stripFinalNewline: false,
|
|
6966
7114
|
reject: false
|
|
6967
|
-
}).catch(() => {
|
|
7115
|
+
}).catch((error) => {
|
|
6968
7116
|
if (signal.aborted) throw cancelled();
|
|
6969
|
-
throw processFailed();
|
|
7117
|
+
throw processFailed(error);
|
|
6970
7118
|
});
|
|
6971
7119
|
} finally {
|
|
6972
7120
|
this.active.delete(lifecycle);
|
|
@@ -6983,7 +7131,7 @@ class CliProcessRunner {
|
|
|
6983
7131
|
throw new AiOperationError({ code: "AI_CONTEXT_TOO_LARGE", operation: "ai-process", message: "The AI tool produced too much output." });
|
|
6984
7132
|
}
|
|
6985
7133
|
if (result.exitCode === void 0 || !resolvedCommand.found) {
|
|
6986
|
-
throw processFailed();
|
|
7134
|
+
throw processFailed(result);
|
|
6987
7135
|
}
|
|
6988
7136
|
return { exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr };
|
|
6989
7137
|
}
|
|
@@ -7008,49 +7156,143 @@ class CliProcessRunner {
|
|
|
7008
7156
|
function cancelled() {
|
|
7009
7157
|
return new AiOperationError({ code: "AI_CANCELLED", operation: "ai-process", message: "Generation canceled." });
|
|
7010
7158
|
}
|
|
7011
|
-
|
|
7012
|
-
|
|
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");
|
|
7013
7169
|
}
|
|
7014
|
-
const ALLOWED = /* @__PURE__ */ new Set(["codex", "claude", "opencode", "grok", "gh"]);
|
|
7015
7170
|
const ALIASES = {
|
|
7016
7171
|
codex: ["codex"],
|
|
7017
7172
|
claude: ["claude"],
|
|
7018
|
-
// Current v2 uses opencode; older distributions also provide opencode2.
|
|
7019
7173
|
opencode: ["opencode", "opencode2"],
|
|
7020
7174
|
grok: ["grok"],
|
|
7021
7175
|
gh: ["gh"]
|
|
7022
7176
|
};
|
|
7023
7177
|
class CliResolver {
|
|
7178
|
+
constructor(environment = new CliEnvironment(), preferences = () => ({ aiExecutablePaths: {} })) {
|
|
7179
|
+
this.environment = environment;
|
|
7180
|
+
this.preferences = preferences;
|
|
7181
|
+
}
|
|
7182
|
+
environment;
|
|
7183
|
+
preferences;
|
|
7024
7184
|
cache = /* @__PURE__ */ new Map();
|
|
7185
|
+
settingsKey = "";
|
|
7186
|
+
generation = 0;
|
|
7187
|
+
invalidate() {
|
|
7188
|
+
this.generation++;
|
|
7189
|
+
this.cache.clear();
|
|
7190
|
+
this.environment.invalidate();
|
|
7191
|
+
}
|
|
7025
7192
|
async resolve(name, forceRefresh = false) {
|
|
7026
7193
|
return (await this.resolveAll(name, forceRefresh))[0] ?? null;
|
|
7027
7194
|
}
|
|
7028
7195
|
async resolveAll(name, forceRefresh = false) {
|
|
7029
|
-
|
|
7030
|
-
|
|
7031
|
-
|
|
7032
|
-
|
|
7033
|
-
|
|
7034
|
-
|
|
7035
|
-
|
|
7036
|
-
|
|
7037
|
-
|
|
7038
|
-
|
|
7039
|
-
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
|
|
7045
|
-
|
|
7046
|
-
|
|
7047
|
-
|
|
7048
|
-
return
|
|
7049
|
-
}
|
|
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
|
+
}
|
|
7050
7248
|
}
|
|
7051
7249
|
}
|
|
7250
|
+
if (generation === this.generation) this.cache.set(key, { at: Date.now(), candidates });
|
|
7251
|
+
return structuredClone(candidates);
|
|
7052
7252
|
}
|
|
7053
|
-
|
|
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)))];
|
|
7054
7296
|
}
|
|
7055
7297
|
var _a$1;
|
|
7056
7298
|
function $constructor(name, initializer2, params) {
|
|
@@ -12216,22 +12458,58 @@ function cleanText(value, maxLength) {
|
|
|
12216
12458
|
const text2 = value.trim();
|
|
12217
12459
|
return !text2 || text2.length > maxLength || hasControlCharacters$6(text2, true) ? null : text2;
|
|
12218
12460
|
}
|
|
12461
|
+
const DEFAULT_MODEL$1 = { id: "default", label: "Default (CLI)" };
|
|
12219
12462
|
class CommitMessageService {
|
|
12220
|
-
constructor(operations, providers, log) {
|
|
12463
|
+
constructor(operations, providers, log, invalidateDiscovery) {
|
|
12221
12464
|
this.operations = operations;
|
|
12222
12465
|
this.log = log;
|
|
12466
|
+
this.invalidateDiscovery = invalidateDiscovery;
|
|
12223
12467
|
for (const provider of providers) this.providers.set(provider.id, provider);
|
|
12224
12468
|
}
|
|
12225
12469
|
operations;
|
|
12226
12470
|
log;
|
|
12471
|
+
invalidateDiscovery;
|
|
12227
12472
|
providers = /* @__PURE__ */ new Map();
|
|
12228
12473
|
active = /* @__PURE__ */ new Map();
|
|
12229
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
|
+
}
|
|
12230
12483
|
async statuses(forceRefresh = false) {
|
|
12231
12484
|
if (!forceRefresh && this.statusCache && Date.now() - this.statusCache.at < 3e4) return this.statusCache.value;
|
|
12232
|
-
|
|
12233
|
-
|
|
12234
|
-
|
|
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
|
+
}
|
|
12235
12513
|
}
|
|
12236
12514
|
async generate(input, externalSignal) {
|
|
12237
12515
|
if (this.active.has(input.requestId) || [...this.active.values()].some((entry) => entry.repositoryId === input.repositoryId) || this.active.size >= 3) {
|
|
@@ -12468,9 +12746,53 @@ function throwIfCancelled(signal, harness) {
|
|
|
12468
12746
|
if (!signal?.aborted) return;
|
|
12469
12747
|
throw new AiOperationError({ code: "AI_CANCELLED", operation: "ai-pr-draft", harness, message: "Generation canceled." });
|
|
12470
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
|
+
}
|
|
12471
12794
|
const AI_PROVIDER_TIMEOUT_MS = 9 * 6e4;
|
|
12472
12795
|
const AI_SERVER_TIMEOUT_MS = AI_PROVIDER_TIMEOUT_MS + 3e4;
|
|
12473
|
-
const DEFAULT_MODEL$1 = { id: "default", label: "Default (CLI)" };
|
|
12474
12796
|
function grokUsage(envelope) {
|
|
12475
12797
|
if (!envelope || typeof envelope !== "object") return { ...EMPTY_AI_USAGE };
|
|
12476
12798
|
const record2 = envelope;
|
|
@@ -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
|
|
12678
|
-
|
|
12679
|
-
|
|
12680
|
-
const
|
|
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:
|
|
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.
|
|
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
|
|
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
|
|
12728
|
-
|
|
12729
|
-
|
|
12730
|
-
const
|
|
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:
|
|
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
|
|
13059
|
+
const catalog = await runCandidate(this.runner, executable, ["debug", "models", "--bundled"]);
|
|
12736
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:
|
|
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.
|
|
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
|
|
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 {
|
|
@@ -12832,16 +13156,15 @@ class GrokProvider {
|
|
|
12832
13156
|
async status(forceRefresh = false) {
|
|
12833
13157
|
const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
12834
13158
|
const base = { id: this.id, label: "Grok Build", checkedAt, models: [DEFAULT_MODEL] };
|
|
12835
|
-
const executable = await this.resolver.resolve("grok", forceRefresh);
|
|
12836
|
-
if (!executable) return { ...base, installed: false, availability: "error", authStatus: "unknown", message: "Install the official Grok Build CLI and run grok login." };
|
|
12837
13159
|
return this.withEnvironment(async (options) => {
|
|
12838
|
-
const
|
|
12839
|
-
const
|
|
12840
|
-
|
|
12841
|
-
|
|
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 ?? "" };
|
|
12842
13165
|
try {
|
|
12843
13166
|
await this.checkControls(executable, options);
|
|
12844
|
-
const result = await this.runner
|
|
13167
|
+
const result = await runCandidate(this.runner, executable, ["models"], options);
|
|
12845
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." };
|
|
12846
13169
|
const { authStatus, models } = parseGrokModels(result.stdout);
|
|
12847
13170
|
return {
|
|
@@ -12857,15 +13180,12 @@ class GrokProvider {
|
|
|
12857
13180
|
});
|
|
12858
13181
|
}
|
|
12859
13182
|
async generate(input) {
|
|
12860
|
-
const executable = await this.resolver.resolve("grok");
|
|
12861
|
-
if (!executable) throw failure("AI_CLI_NOT_FOUND", "Grok Build is not installed.");
|
|
12862
13183
|
const timeout = AbortSignal.timeout(AI_PROVIDER_TIMEOUT_MS);
|
|
12863
13184
|
const signal = AbortSignal.any([input.signal, timeout]);
|
|
12864
13185
|
try {
|
|
12865
13186
|
return await this.withEnvironment(async (options) => {
|
|
12866
13187
|
const controlled = { ...options, signal };
|
|
12867
|
-
const
|
|
12868
|
-
if (version2.exitCode !== 0 || !isSupportedGrokVersion(version2.stdout)) throw failure("AI_PROCESS_FAILED", UNSUPPORTED);
|
|
13188
|
+
const executable = requireCandidate(await selectCli(this.resolver, this.runner, this.id, { compatible: isSupportedGrokVersion, runOptions: controlled }), this.id);
|
|
12869
13189
|
await this.checkControls(executable, controlled);
|
|
12870
13190
|
const promptFile = path.join(options.cwd, "prompt.txt");
|
|
12871
13191
|
await writeFile(promptFile, input.prompt, { encoding: "utf8", mode: 384 });
|
|
@@ -12891,7 +13211,7 @@ class GrokProvider {
|
|
|
12891
13211
|
"--no-memory"
|
|
12892
13212
|
];
|
|
12893
13213
|
if (input.model !== "default") args.push("--model", input.model);
|
|
12894
|
-
const result = await this.runner
|
|
13214
|
+
const result = await runCandidate(this.runner, executable, args, { ...controlled, timeoutMs: AI_PROVIDER_TIMEOUT_MS });
|
|
12895
13215
|
requireGrokSuccess(result);
|
|
12896
13216
|
const parsed = parseGrokOutput(result.stdout);
|
|
12897
13217
|
return { output: parsed.output, usage: grokUsage(parsed.envelope) };
|
|
@@ -12903,9 +13223,9 @@ class GrokProvider {
|
|
|
12903
13223
|
}
|
|
12904
13224
|
}
|
|
12905
13225
|
async checkControls(executable, options) {
|
|
12906
|
-
const help = await this.runner
|
|
13226
|
+
const help = await runCandidate(this.runner, executable, ["--help"], options);
|
|
12907
13227
|
if (help.exitCode !== 0 || REQUIRED_FLAGS.some((flag2) => !help.stdout.includes(flag2))) throw failure("AI_PROCESS_FAILED", UNSUPPORTED);
|
|
12908
|
-
const result = await this.runner
|
|
13228
|
+
const result = await runCandidate(this.runner, executable, ["inspect", "--json"], options);
|
|
12909
13229
|
let report;
|
|
12910
13230
|
try {
|
|
12911
13231
|
report = JSON.parse(result.stdout);
|
|
@@ -13015,13 +13335,14 @@ async function startOpenCodeV2Server(options) {
|
|
|
13015
13335
|
const timeoutMs = options.timeoutMs;
|
|
13016
13336
|
const password = randomBytes(24).toString("base64url");
|
|
13017
13337
|
const port = await freePort();
|
|
13018
|
-
const environment = Object.fromEntries(
|
|
13338
|
+
const environment = cleanEnvironment(Object.fromEntries(
|
|
13019
13339
|
Object.entries({
|
|
13020
13340
|
...process.env,
|
|
13341
|
+
...options.env,
|
|
13021
13342
|
OPENCODE_SERVER_USERNAME: SERVER_USERNAME,
|
|
13022
13343
|
OPENCODE_SERVER_PASSWORD: password
|
|
13023
13344
|
}).filter((entry) => entry[1] !== void 0)
|
|
13024
|
-
);
|
|
13345
|
+
));
|
|
13025
13346
|
const resolved = resolveProcessCommand(options.executable, options.cwd ?? process.cwd(), environment);
|
|
13026
13347
|
if (!resolved.found) {
|
|
13027
13348
|
throw new AiOperationError({ code: "AI_PROCESS_FAILED", operation: "opencode-server", harness: "opencode", message: "Could not start the local OpenCode server.", retryable: true });
|
|
@@ -13221,13 +13542,14 @@ class OpenCodeProvider {
|
|
|
13221
13542
|
async status(forceRefresh = false) {
|
|
13222
13543
|
const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
13223
13544
|
const detected = await this.detect(forceRefresh);
|
|
13224
|
-
|
|
13225
|
-
|
|
13226
|
-
const
|
|
13227
|
-
|
|
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);
|
|
13228
13550
|
const [auth, catalog] = await Promise.all([
|
|
13229
|
-
this.runner
|
|
13230
|
-
this.runner
|
|
13551
|
+
runCandidate(this.runner, executable, ["auth", "list", "--format", "json", "--standalone"]),
|
|
13552
|
+
runCandidate(this.runner, executable, ["models", "--standalone"], { timeoutMs: 3e4 })
|
|
13231
13553
|
]);
|
|
13232
13554
|
const models = catalog.exitCode === 0 ? parseOpenCodeModels(catalog.stdout) : [DEFAULT_MODEL$1];
|
|
13233
13555
|
let authStatus = parseOpenCodeAuthList(auth.stdout, auth.exitCode);
|
|
@@ -13237,6 +13559,7 @@ class OpenCodeProvider {
|
|
|
13237
13559
|
id: this.id,
|
|
13238
13560
|
label: "OpenCode",
|
|
13239
13561
|
availability: models.length > 1 ? confirmed ? "ready" : "warning" : authStatus === "unauthenticated" ? "error" : "warning",
|
|
13562
|
+
...detectionFields(detected),
|
|
13240
13563
|
installed: true,
|
|
13241
13564
|
authStatus,
|
|
13242
13565
|
cliName,
|
|
@@ -13248,9 +13571,7 @@ class OpenCodeProvider {
|
|
|
13248
13571
|
}
|
|
13249
13572
|
async generate(input) {
|
|
13250
13573
|
const detected = await this.detect(false, input.signal);
|
|
13251
|
-
|
|
13252
|
-
if (!detected.compatible) throw new AiOperationError({ code: "AI_PROCESS_FAILED", operation: "opencode-generate", harness: this.id, message: UNSUPPORTED_MESSAGE });
|
|
13253
|
-
const executable = detected.executable;
|
|
13574
|
+
const executable = requireCandidate(detected, this.id);
|
|
13254
13575
|
const timeoutSignal = AbortSignal.timeout(AI_PROVIDER_TIMEOUT_MS);
|
|
13255
13576
|
const boundedInput = { ...input, signal: AbortSignal.any([input.signal, timeoutSignal]) };
|
|
13256
13577
|
try {
|
|
@@ -13263,7 +13584,7 @@ class OpenCodeProvider {
|
|
|
13263
13584
|
}
|
|
13264
13585
|
}
|
|
13265
13586
|
async generateV2(executable, input) {
|
|
13266
|
-
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) => {
|
|
13267
13588
|
if (error instanceof AiOperationError) throw error;
|
|
13268
13589
|
throw new AiOperationError({ code: "AI_PROCESS_FAILED", operation: "opencode-server", harness: this.id, message: "Could not start the local OpenCode server.", retryable: true });
|
|
13269
13590
|
});
|
|
@@ -13286,21 +13607,7 @@ ${input.prompt}`, model: input.model, cwd: input.repositoryPath, signal: input.s
|
|
|
13286
13607
|
}
|
|
13287
13608
|
}
|
|
13288
13609
|
async detect(forceRefresh = false, signal) {
|
|
13289
|
-
|
|
13290
|
-
let first = null;
|
|
13291
|
-
for (const executable of candidates) {
|
|
13292
|
-
try {
|
|
13293
|
-
const result = await this.runner.run(executable, ["--version"], signal ? { signal } : {});
|
|
13294
|
-
const version2 = result.stdout.trim() || result.stderr.trim();
|
|
13295
|
-
const detected = { executable, version: version2, compatible: result.exitCode === 0 && isOpenCodeV2Version(version2) };
|
|
13296
|
-
if (detected.compatible) return detected;
|
|
13297
|
-
first ??= detected;
|
|
13298
|
-
} catch (error) {
|
|
13299
|
-
if (signal?.aborted) throw error;
|
|
13300
|
-
first ??= { executable, version: "", compatible: false };
|
|
13301
|
-
}
|
|
13302
|
-
}
|
|
13303
|
-
return first;
|
|
13610
|
+
return selectCli(this.resolver, this.runner, this.id, { forceRefresh, compatible: isOpenCodeV2Version, runOptions: signal ? { signal } : {} });
|
|
13304
13611
|
}
|
|
13305
13612
|
close() {
|
|
13306
13613
|
for (const server of this.servers) server.close();
|
|
@@ -13589,7 +13896,7 @@ class FileService {
|
|
|
13589
13896
|
// UI pulls its children through here instead of paying for them at startup.
|
|
13590
13897
|
async listDirectory(repositoryId, relativePath) {
|
|
13591
13898
|
const repository = this.repositories.get(repositoryId);
|
|
13592
|
-
const directory = this.repositories.resolvePath(repositoryId, relativePath);
|
|
13899
|
+
const directory = relativePath === "" ? repository.path : this.repositories.resolvePath(repositoryId, relativePath);
|
|
13593
13900
|
const parent = normalizeGitPath(relativePath).replace(/\/+$/, "");
|
|
13594
13901
|
let dirents;
|
|
13595
13902
|
try {
|
|
@@ -13599,13 +13906,12 @@ class FileService {
|
|
|
13599
13906
|
if (code === "ENOENT" || code === "ENOTDIR") return [];
|
|
13600
13907
|
throw error;
|
|
13601
13908
|
}
|
|
13602
|
-
const ignored = (await this.findIgnoredDirectories(repository.path, [parent])).has(parent);
|
|
13603
13909
|
const entries = [];
|
|
13604
13910
|
for (const dirent of dirents) {
|
|
13605
13911
|
if (dirent.name === ".git") continue;
|
|
13606
13912
|
const entryPath = parent ? `${parent}/${dirent.name}` : dirent.name;
|
|
13607
13913
|
if (dirent.isDirectory() && !dirent.isSymbolicLink()) {
|
|
13608
|
-
entries.push({ path: entryPath, name: dirent.name, type: "directory",
|
|
13914
|
+
entries.push({ path: entryPath, name: dirent.name, type: "directory", children: [] });
|
|
13609
13915
|
continue;
|
|
13610
13916
|
}
|
|
13611
13917
|
if (!dirent.isFile() && !dirent.isSymbolicLink()) continue;
|
|
@@ -13616,15 +13922,25 @@ class FileService {
|
|
|
13616
13922
|
name: dirent.name,
|
|
13617
13923
|
type: "file",
|
|
13618
13924
|
size: metadata.size,
|
|
13619
|
-
mtimeMs: metadata.mtimeMs
|
|
13620
|
-
...ignored ? { ignored: true } : {}
|
|
13925
|
+
mtimeMs: metadata.mtimeMs
|
|
13621
13926
|
});
|
|
13622
13927
|
} catch (error) {
|
|
13623
13928
|
const code = error.code;
|
|
13624
13929
|
if (code !== "ENOENT" && code !== "ENOTDIR") throw error;
|
|
13625
13930
|
}
|
|
13626
13931
|
}
|
|
13627
|
-
|
|
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);
|
|
13628
13944
|
}
|
|
13629
13945
|
async findIgnoredDirectories(repositoryPath, directories) {
|
|
13630
13946
|
const ignored = /* @__PURE__ */ new Set();
|
|
@@ -16652,6 +16968,12 @@ class GitHubService {
|
|
|
16652
16968
|
const operation = "gh-pr-for-branch";
|
|
16653
16969
|
const { repository, nameWithOwner } = await this.requireGitHub(repositoryId, operation);
|
|
16654
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;
|
|
16655
16977
|
const result = await this.runGh(
|
|
16656
16978
|
["pr", "list", "-R", nameWithOwner, "--head", branch, "--state", "all", "--json", PR_SUMMARY_FIELDS, "--limit", "10"],
|
|
16657
16979
|
{ cwd: repository.path, operation, timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 1024 * 1024 }
|
|
@@ -16764,12 +17086,13 @@ class GitHubService {
|
|
|
16764
17086
|
}
|
|
16765
17087
|
async checkStatus(forceRefresh) {
|
|
16766
17088
|
const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
16767
|
-
const
|
|
17089
|
+
const detected = await selectCli(this.resolver, this.runner, "gh", { forceRefresh, runOptions: { cwd: os.tmpdir(), env: { ...GH_ENV } } });
|
|
17090
|
+
const executable = detected.candidate;
|
|
16768
17091
|
if (!executable) {
|
|
16769
17092
|
return { installed: false, availability: "error", authStatus: "unknown", message: "Install GitHub CLI (cli.github.com) to work with pull requests.", checkedAt };
|
|
16770
17093
|
}
|
|
16771
|
-
|
|
16772
|
-
const versionLabel =
|
|
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 ?? "";
|
|
16773
17096
|
const auth = await this.run(executable, ["auth", "status", "--hostname", "github.com", "--active"], { cwd: os.tmpdir(), operation: "gh-auth-status" });
|
|
16774
17097
|
if (auth.exitCode !== 0) {
|
|
16775
17098
|
return { installed: true, availability: "error", authStatus: "unauthenticated", version: versionLabel, message: "Run gh auth login.", checkedAt };
|
|
@@ -16788,7 +17111,8 @@ class GitHubService {
|
|
|
16788
17111
|
return { repository, nameWithOwner: info.nameWithOwner };
|
|
16789
17112
|
}
|
|
16790
17113
|
async runGh(args, options) {
|
|
16791
|
-
const
|
|
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;
|
|
16792
17116
|
if (!executable) throw new GhOperationError({ code: "GH_CLI_NOT_FOUND", operation: options.operation, message: "GitHub CLI is not installed." });
|
|
16793
17117
|
const result = await this.run(executable, args, options);
|
|
16794
17118
|
if (result.exitCode !== 0) throw classifyFailure(result, options.operation);
|
|
@@ -16796,9 +17120,9 @@ class GitHubService {
|
|
|
16796
17120
|
}
|
|
16797
17121
|
async run(executable, args, options) {
|
|
16798
17122
|
try {
|
|
16799
|
-
return await this.runner.run(executable, args, {
|
|
17123
|
+
return await this.runner.run(executable.executable, args, {
|
|
16800
17124
|
cwd: options.cwd,
|
|
16801
|
-
env: { ...GH_ENV },
|
|
17125
|
+
env: { ...executable.env, ...GH_ENV },
|
|
16802
17126
|
...options.timeoutMs !== void 0 ? { timeoutMs: options.timeoutMs } : {},
|
|
16803
17127
|
...options.maxOutputBytes !== void 0 ? { maxOutputBytes: options.maxOutputBytes } : {},
|
|
16804
17128
|
...options.stdin !== void 0 ? { stdin: options.stdin } : {}
|
|
@@ -17263,6 +17587,7 @@ const defaults = {
|
|
|
17263
17587
|
monoFont: "jetbrains-mono",
|
|
17264
17588
|
commitMessageHarness: "codex",
|
|
17265
17589
|
commitMessageModels: { codex: "default", claude: "default", opencode: "default", grok: "default" },
|
|
17590
|
+
aiExecutablePaths: {},
|
|
17266
17591
|
shortcutOverrides: {},
|
|
17267
17592
|
doubleControlShortcutEnabled: true,
|
|
17268
17593
|
remoteFetchIntervalSeconds: DEFAULT_REMOTE_FETCH_INTERVAL_SECONDS
|
|
@@ -17342,7 +17667,7 @@ class SettingsStore {
|
|
|
17342
17667
|
return this.data.activeRepositoryId;
|
|
17343
17668
|
}
|
|
17344
17669
|
get preferences() {
|
|
17345
|
-
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 } };
|
|
17346
17671
|
}
|
|
17347
17672
|
get windowBounds() {
|
|
17348
17673
|
return { ...this.data.windowBounds };
|
|
@@ -17471,6 +17796,8 @@ class SettingsStore {
|
|
|
17471
17796
|
next.monoFont = isMonoFont(next.monoFont) ? next.monoFont : "jetbrains-mono";
|
|
17472
17797
|
next.commitMessageHarness = isHarness(next.commitMessageHarness) ? next.commitMessageHarness : "codex";
|
|
17473
17798
|
next.commitMessageModels = modelPreferences(next.commitMessageModels);
|
|
17799
|
+
next.aiExecutablePaths = executablePaths(next.aiExecutablePaths, true);
|
|
17800
|
+
delete next.aiShellEnvironment;
|
|
17474
17801
|
next.shortcutOverrides = sanitizeShortcutOverrides(next.shortcutOverrides);
|
|
17475
17802
|
next.doubleControlShortcutEnabled = typeof next.doubleControlShortcutEnabled === "boolean" ? next.doubleControlShortcutEnabled : this.defaultDoubleControlShortcutEnabled;
|
|
17476
17803
|
next.remoteFetchIntervalSeconds = normalizeRemoteFetchIntervalSeconds(next.remoteFetchIntervalSeconds);
|
|
@@ -17599,6 +17926,7 @@ function validate(value, defaultDoubleControlShortcutEnabled) {
|
|
|
17599
17926
|
monoFont: isMonoFont(parsedPreferences.data.monoFont) ? parsedPreferences.data.monoFont : "jetbrains-mono",
|
|
17600
17927
|
commitMessageHarness: isHarness(parsedPreferences.data.commitMessageHarness) ? parsedPreferences.data.commitMessageHarness : "codex",
|
|
17601
17928
|
commitMessageModels: modelPreferences(parsedPreferences.data.commitMessageModels),
|
|
17929
|
+
aiExecutablePaths: executablePaths(parsedPreferences.data.aiExecutablePaths),
|
|
17602
17930
|
shortcutOverrides: sanitizeShortcutOverrides(parsedPreferences.data.shortcutOverrides),
|
|
17603
17931
|
doubleControlShortcutEnabled: typeof parsedPreferences.data.doubleControlShortcutEnabled === "boolean" ? parsedPreferences.data.doubleControlShortcutEnabled : defaultDoubleControlShortcutEnabled,
|
|
17604
17932
|
remoteFetchIntervalSeconds: normalizeRemoteFetchIntervalSeconds(
|
|
@@ -17706,6 +18034,31 @@ function normalizeRecent(item) {
|
|
|
17706
18034
|
repositoryName: typeof item.repositoryName === "string" && item.repositoryName ? item.repositoryName : path.basename(path.dirname(commonDir))
|
|
17707
18035
|
};
|
|
17708
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
|
+
}
|
|
17709
18062
|
class SystemTrash {
|
|
17710
18063
|
available;
|
|
17711
18064
|
operation;
|
|
@@ -17824,7 +18177,7 @@ async function createOpenTigRuntime(options) {
|
|
|
17824
18177
|
const fileHistory = new FileOperationHistory(files, trash);
|
|
17825
18178
|
const search = new SearchService(git, repositories, files, fileHistory);
|
|
17826
18179
|
const operations = new GitRepositoryOperations(git, repositories, files);
|
|
17827
|
-
const cliResolver = new CliResolver();
|
|
18180
|
+
const cliResolver = new CliResolver(void 0, () => settings.preferences);
|
|
17828
18181
|
const cliRunner = new CliProcessRunner();
|
|
17829
18182
|
const providers = [
|
|
17830
18183
|
new CodexProvider(cliResolver, cliRunner),
|
|
@@ -17834,7 +18187,7 @@ async function createOpenTigRuntime(options) {
|
|
|
17834
18187
|
];
|
|
17835
18188
|
const aiLog = new AiLogStore(options.aiLogPath);
|
|
17836
18189
|
await aiLog.load();
|
|
17837
|
-
const ai = new CommitMessageService(operations, providers, aiLog);
|
|
18190
|
+
const ai = new CommitMessageService(operations, providers, aiLog, () => cliResolver.invalidate());
|
|
17838
18191
|
const prDrafts = new PullRequestDraftService(operations, providers, aiLog);
|
|
17839
18192
|
const github = new GitHubService(cliResolver, cliRunner, git, repositories);
|
|
17840
18193
|
let runtime = null;
|
|
@@ -18314,22 +18667,66 @@ function mimeTypeOf(relativePath, bytes) {
|
|
|
18314
18667
|
if (fromPath === "image/svg+xml") return fromPath;
|
|
18315
18668
|
return detectRasterImageMime(bytes) ?? fromPath;
|
|
18316
18669
|
}
|
|
18317
|
-
async function browseServerDirectories(requestedPath) {
|
|
18318
|
-
const
|
|
18319
|
-
const
|
|
18320
|
-
|
|
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.");
|
|
18321
18675
|
const path2 = resolve(expanded);
|
|
18322
|
-
|
|
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
|
+
}
|
|
18323
18686
|
const directories = [];
|
|
18324
|
-
|
|
18325
|
-
|
|
18326
|
-
|
|
18327
|
-
|
|
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
|
+
}
|
|
18328
18695
|
}
|
|
18329
|
-
}
|
|
18696
|
+
}));
|
|
18330
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
|
+
}
|
|
18331
18703
|
const parentPath = dirname(path2);
|
|
18332
|
-
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
|
+
}
|
|
18333
18730
|
}
|
|
18334
18731
|
const requestRecordSchema = looseObject({});
|
|
18335
18732
|
function boundedStringSchema(maxLength, options = {}) {
|
|
@@ -18673,6 +19070,7 @@ function registerServerCommands(registry2, services, host) {
|
|
|
18673
19070
|
});
|
|
18674
19071
|
handle(IPC.preferences, "preferences", async (partial2) => {
|
|
18675
19072
|
const preferences = await services.settings.setPreferences(partial2 ?? {});
|
|
19073
|
+
if (partial2 && typeof partial2 === "object" && "aiExecutablePaths" in partial2) services.ai.invalidateStatuses();
|
|
18676
19074
|
return preferences;
|
|
18677
19075
|
});
|
|
18678
19076
|
handle(IPC.filesTreeStateUpdate, "files-tree-state", (repositoryId, expandedPaths) => {
|
|
@@ -18692,7 +19090,8 @@ function registerServerCommands(registry2, services, host) {
|
|
|
18692
19090
|
context
|
|
18693
19091
|
));
|
|
18694
19092
|
handle(IPC.repositoryBrowseDirectories, "browse-directories", (path2) => browseServerDirectories(
|
|
18695
|
-
path2 === void 0 ? void 0 : stringArg(path2, "browse-directories", 32768)
|
|
19093
|
+
path2 === void 0 ? void 0 : stringArg(path2, "browse-directories", 32768),
|
|
19094
|
+
services.git
|
|
18696
19095
|
));
|
|
18697
19096
|
handleWithContext(IPC.repositoryOpenRecent, "open-recent", async (context, id) => {
|
|
18698
19097
|
const repositoryId = stringArg(id, "open-recent", 64);
|
|
@@ -18730,7 +19129,7 @@ function registerServerCommands(registry2, services, host) {
|
|
|
18730
19129
|
));
|
|
18731
19130
|
handle(IPC.repositoryDirectoryEntries, "directory-entries", (id, directoryPath) => services.files.listDirectory(
|
|
18732
19131
|
stringArg(id, "directory-entries", 64),
|
|
18733
|
-
stringArg(directoryPath, "directory-entries")
|
|
19132
|
+
directoryPath === "" ? "" : stringArg(directoryPath, "directory-entries")
|
|
18734
19133
|
));
|
|
18735
19134
|
handle(IPC.repositoryReadFile, "read-file", (id, filePath, allowLarge) => services.files.read(stringArg(id, "read-file", 64), stringArg(filePath, "read-file"), allowLarge === true));
|
|
18736
19135
|
handle(IPC.repositoryReadImage, "read-image", (id, filePath) => services.files.readImage(
|