token-goat 2.9.26 → 2.9.29
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/README.md +30 -88
- package/SECURITY.md +14 -0
- package/dist/{token-goat-chunk-OZRSREKR.mjs → token-goat-chunk-2AWQ2R5X.mjs} +2 -2
- package/dist/{token-goat-chunk-BH4ZVRB2.mjs → token-goat-chunk-2NNUA2LR.mjs} +4 -4
- package/dist/token-goat-chunk-2WOUYWLM.mjs +955 -0
- package/dist/{token-goat-chunk-IKZLSRII.mjs → token-goat-chunk-435UQTKD.mjs} +5 -3
- package/dist/{token-goat-chunk-62IHOU6L.mjs → token-goat-chunk-44BM7DDQ.mjs} +5 -3
- package/dist/token-goat-chunk-52NSDWYY.mjs +96 -0
- package/dist/{token-goat-chunk-CXBOFPF4.mjs → token-goat-chunk-67BNIKKO.mjs} +32 -18
- package/dist/{token-goat-chunk-XBESNWEX.mjs → token-goat-chunk-6AK46ZT7.mjs} +2 -3
- package/dist/{token-goat-chunk-7SXPJOSZ.mjs → token-goat-chunk-7CPMDFGB.mjs} +166 -40
- package/dist/token-goat-chunk-ASLWOQ3I.mjs +14 -0
- package/dist/{token-goat-chunk-MWID3YOY.mjs → token-goat-chunk-BGYOZ6DO.mjs} +6 -4
- package/dist/{token-goat-chunk-PAV6NNY7.mjs → token-goat-chunk-C5YPNZKQ.mjs} +22 -8
- package/dist/{token-goat-chunk-D2X7XN5M.mjs → token-goat-chunk-CIQ2FTY3.mjs} +23 -242
- package/dist/token-goat-chunk-D4ETHJD6.mjs +246 -0
- package/dist/{token-goat-chunk-7Y6GWTII.mjs → token-goat-chunk-DNRPXHYH.mjs} +25 -17
- package/dist/{token-goat-chunk-L43RKNHA.mjs → token-goat-chunk-DNSPXQXQ.mjs} +2 -2
- package/dist/token-goat-chunk-DPNLUJWB.mjs +220 -0
- package/dist/token-goat-chunk-E4ZOJIZK.mjs +1382 -0
- package/dist/token-goat-chunk-E5TK5DNO.mjs +718 -0
- package/dist/{token-goat-chunk-VVROQBQJ.mjs → token-goat-chunk-EUSDXRXU.mjs} +62 -12
- package/dist/{token-goat-chunk-POOI26O6.mjs → token-goat-chunk-FTP4U4QA.mjs} +12 -8
- package/dist/token-goat-chunk-G23DWHV4.mjs +142 -0
- package/dist/{token-goat-chunk-MDV5VWF4.mjs → token-goat-chunk-G7IWXCFF.mjs} +223 -15
- package/dist/{token-goat-chunk-C2PG4K5D.mjs → token-goat-chunk-GLXRTTLE.mjs} +7 -5
- package/dist/{token-goat-chunk-M6CVNHTW.mjs → token-goat-chunk-H5J3C56Q.mjs} +95 -573
- package/dist/{token-goat-chunk-BAWKGODL.mjs → token-goat-chunk-HNFZZHFW.mjs} +2 -2
- package/dist/{token-goat-chunk-TG5ZPD6B.mjs → token-goat-chunk-HUYUHODZ.mjs} +1036 -759
- package/dist/token-goat-chunk-IVE6U7KV.mjs +53 -0
- package/dist/{token-goat-chunk-ZZT6PVB3.mjs → token-goat-chunk-IVWI2OCK.mjs} +72 -45
- package/dist/{token-goat-chunk-GMOUBOX4.mjs → token-goat-chunk-JTKCQMQA.mjs} +37 -5
- package/dist/{token-goat-chunk-H2CPLEMP.mjs → token-goat-chunk-KOPIR3KZ.mjs} +46 -23
- package/dist/token-goat-chunk-L7H3U27L.mjs +35 -0
- package/dist/{token-goat-chunk-FLEYOCAS.mjs → token-goat-chunk-LZNBQTDK.mjs} +6 -4
- package/dist/token-goat-chunk-MELOKFDJ.mjs +199 -0
- package/dist/{token-goat-chunk-RH27ZSPF.mjs → token-goat-chunk-NMBWLRHW.mjs} +164 -50
- package/dist/{token-goat-chunk-43JFN26X.mjs → token-goat-chunk-O6I2XU4P.mjs} +27 -967
- package/dist/{token-goat-chunk-G6EYO5CD.mjs → token-goat-chunk-ONXH6NZ5.mjs} +2 -87
- package/dist/token-goat-chunk-ORNFMIER.mjs +295 -0
- package/dist/{token-goat-chunk-LHUP4DXZ.mjs → token-goat-chunk-PGTH2U4C.mjs} +1 -1
- package/dist/{token-goat-chunk-5BQ7W7D7.mjs → token-goat-chunk-PKRWMHVP.mjs} +76 -59
- package/dist/{token-goat-chunk-DA43OE7L.mjs → token-goat-chunk-PQ53BSCF.mjs} +1 -1
- package/dist/{token-goat-chunk-STYRLQIW.mjs → token-goat-chunk-PYSUADX4.mjs} +2 -2
- package/dist/{token-goat-chunk-JALB3KWJ.mjs → token-goat-chunk-QO3LXVVR.mjs} +4247 -5005
- package/dist/{token-goat-chunk-L45IWWMC.mjs → token-goat-chunk-RLP6J7WR.mjs} +6 -6
- package/dist/token-goat-chunk-RQGGMPCR.mjs +205 -0
- package/dist/{token-goat-chunk-MBDJR6HU.mjs → token-goat-chunk-RTMVLHWX.mjs} +3708 -4003
- package/dist/{token-goat-chunk-E3K3BTEQ.mjs → token-goat-chunk-S4VSXURL.mjs} +8 -6
- package/dist/{token-goat-chunk-LKSXAMJB.mjs → token-goat-chunk-SE6E4BKJ.mjs} +1 -1
- package/dist/token-goat-chunk-TCQANXYC.mjs +351 -0
- package/dist/token-goat-chunk-TDUT2CL6.mjs +564 -0
- package/dist/{token-goat-chunk-LRIZ7K3F.mjs → token-goat-chunk-TGOXNAHW.mjs} +54 -111
- package/dist/{token-goat-chunk-WNBSCSAI.mjs → token-goat-chunk-UA5BKWWQ.mjs} +1 -1
- package/dist/{token-goat-chunk-RIB6XY2V.mjs → token-goat-chunk-UXSXQQIY.mjs} +1 -1
- package/dist/{token-goat-chunk-7EPYB34H.mjs → token-goat-chunk-VMFWNHGR.mjs} +84 -73
- package/dist/token-goat-chunk-VQ6F6T7X.mjs +36 -0
- package/dist/{token-goat-chunk-5M3NRJD2.mjs → token-goat-chunk-VZQEWRVH.mjs} +6 -4
- package/dist/{token-goat-chunk-OSUFN2FV.mjs → token-goat-chunk-XB5KKOCG.mjs} +5 -25
- package/dist/{token-goat-chunk-6E2IDLHF.mjs → token-goat-chunk-XYWPOSCN.mjs} +57 -615
- package/dist/{token-goat-chunk-CQHY4SL7.mjs → token-goat-chunk-YAGLESS3.mjs} +8 -5
- package/dist/token-goat-chunk-YKSRAWUU.mjs +33 -0
- package/dist/{token-goat-chunk-F2ARFHLJ.mjs → token-goat-chunk-YOOVFPTP.mjs} +2 -2
- package/dist/{token-goat-chunk-EEIDFMEM.mjs → token-goat-chunk-YZAAZU2S.mjs} +12 -3
- package/dist/token-goat-chunk-Z6KBMOBI.mjs +53 -0
- package/dist/{token-goat-chunk-4GEDH2WU.mjs → token-goat-chunk-ZER6EAXZ.mjs} +81 -70
- package/dist/token-goat-hook-client.cjs +475 -0
- package/dist/token-goat-hook-client.mjs +26 -0
- package/dist/token-goat-hook.mjs +31 -21
- package/dist/token-goat.core.mjs +9 -39
- package/docs/C4_RUNTIME_ARCHITECTURE.md +2 -2
- package/docs/cli.md +35 -23
- package/docs/install.md +18 -14
- package/package.json +1 -1
- package/dist/token-goat-chunk-ADMR3QVF.mjs +0 -41
- package/dist/token-goat-chunk-E6B2UFDF.mjs +0 -30
- package/dist/token-goat-chunk-GKZ2US5B.mjs +0 -187
- package/dist/token-goat-chunk-MZTO4FXH.mjs +0 -26
|
@@ -0,0 +1,1382 @@
|
|
|
1
|
+
import { createRequire as __cjsRequire } from 'node:module';
|
|
2
|
+
const require = __cjsRequire(import.meta.url);
|
|
3
|
+
import {
|
|
4
|
+
DELIVERS_CONTENT_RE,
|
|
5
|
+
commandPathIsTouchable,
|
|
6
|
+
leadWithCommand
|
|
7
|
+
} from "./token-goat-chunk-E5TK5DNO.mjs";
|
|
8
|
+
import {
|
|
9
|
+
getFileLineRanges
|
|
10
|
+
} from "./token-goat-chunk-TDUT2CL6.mjs";
|
|
11
|
+
import {
|
|
12
|
+
hasBareBackgroundOrNewline,
|
|
13
|
+
hasUnquotedOperator,
|
|
14
|
+
isRedirectAmpersand
|
|
15
|
+
} from "./token-goat-chunk-2WOUYWLM.mjs";
|
|
16
|
+
import {
|
|
17
|
+
isUnderSystemTemp
|
|
18
|
+
} from "./token-goat-chunk-TGOXNAHW.mjs";
|
|
19
|
+
import {
|
|
20
|
+
FALSY_ENV_VALUES
|
|
21
|
+
} from "./token-goat-chunk-IVE6U7KV.mjs";
|
|
22
|
+
import {
|
|
23
|
+
escapeRegExp,
|
|
24
|
+
safeSlice
|
|
25
|
+
} from "./token-goat-chunk-XYWPOSCN.mjs";
|
|
26
|
+
import {
|
|
27
|
+
TOOL_RESULTS_ID_CHARS,
|
|
28
|
+
normalizePath,
|
|
29
|
+
resolveIndexPath
|
|
30
|
+
} from "./token-goat-chunk-NMTKNYGF.mjs";
|
|
31
|
+
import {
|
|
32
|
+
init_define_import_meta_env
|
|
33
|
+
} from "./token-goat-chunk-A37V4PBF.mjs";
|
|
34
|
+
|
|
35
|
+
// src/tool_error_class.ts
|
|
36
|
+
init_define_import_meta_env();
|
|
37
|
+
|
|
38
|
+
// src/bash_extractors.ts
|
|
39
|
+
init_define_import_meta_env();
|
|
40
|
+
import { statSync, openSync, readSync, closeSync } from "node:fs";
|
|
41
|
+
function outlineCommand(shown) {
|
|
42
|
+
return 'token-goat outline "' + shown + '"';
|
|
43
|
+
}
|
|
44
|
+
function structuredDataHint(hintPath, target, reason = "") {
|
|
45
|
+
const ext = /\.(json|ya?ml)$/i.exec(hintPath)?.[1];
|
|
46
|
+
if (ext === void 0) return null;
|
|
47
|
+
const fmt = ext.toLowerCase() === "json" ? "json" : "yaml";
|
|
48
|
+
const outline = "token-goat " + fmt + '-outline "' + hintPath + '"';
|
|
49
|
+
const query = (path2) => "token-goat " + fmt + '-query "' + hintPath + '" "' + path2 + '"';
|
|
50
|
+
if (target === null || /["'`$\\]/.test(target)) {
|
|
51
|
+
return leadWithCommand(outline, "to list the top-level keys with their type and size (`--filter TEXT` narrows a large one), then `" + query("KEY") + "` to read one value (`\"['a.b']\"` for a key holding a dot)", reason);
|
|
52
|
+
}
|
|
53
|
+
const path = /^[\w-]+$/.test(target) ? target : "['" + target + "']";
|
|
54
|
+
return leadWithCommand(query(path), "to read that value, or `" + outline + "` for every top-level key with its type and size", reason);
|
|
55
|
+
}
|
|
56
|
+
function sqlTableHint(hintPath, target, reason = "") {
|
|
57
|
+
return leadWithCommand('token-goat read "' + hintPath + "::" + target.name + '"', "to pull one CREATE TABLE / CREATE TYPE block", reason);
|
|
58
|
+
}
|
|
59
|
+
function surgicalHintFor(hintPath, isEnv, isConfig, isDoc, isXml, target, reason = "") {
|
|
60
|
+
const outline = outlineCommand(hintPath);
|
|
61
|
+
if (isXml) return leadWithCommand('token-goat xml-outline "' + hintPath + '"', 'to inspect structure, or `token-goat xml-query "' + hintPath + '" "<selector>"` to query specific nodes', reason);
|
|
62
|
+
if (isEnv) return leadWithCommand('token-goat config-get "' + hintPath + '" ' + target.name, "to read a specific variable", reason);
|
|
63
|
+
if (isConfig) {
|
|
64
|
+
const structured = structuredDataHint(hintPath, target.real ? target.name : null, reason);
|
|
65
|
+
if (structured !== null) return structured;
|
|
66
|
+
return target.slice === "key" ? leadWithCommand('token-goat config-get "' + hintPath + '" ' + target.name, "to read a specific value, or `" + outline + "` for every key with line ranges", reason) : leadWithCommand('token-goat section "' + hintPath + "::" + target.name + '"', "to read one table, or `" + outline + "` for every key with line ranges", reason);
|
|
67
|
+
}
|
|
68
|
+
if (isDoc) return leadWithCommand('token-goat section "' + hintPath + "::" + target.name + '"', "to read one section, or `" + outline + "` for every heading with line ranges", reason);
|
|
69
|
+
return target.real ? leadWithCommand('token-goat read "' + hintPath + "::" + target.name + '"', "to read one function or class, or `" + outline + "` for all of them", reason) : leadWithCommand(outline, "to list every function and class with its line range", reason);
|
|
70
|
+
}
|
|
71
|
+
function surgicalHintForConfigDoc(filePath, isConfig, isDoc, isSql, isXml, target, reason = "") {
|
|
72
|
+
if (isXml || isConfig || isDoc) return surgicalHintFor(filePath, false, isConfig, isDoc, isXml, target, reason);
|
|
73
|
+
if (isSql) return sqlTableHint(filePath, target, reason);
|
|
74
|
+
return leadWithCommand(outlineCommand(filePath), 'or `token-goat skeleton "' + filePath + '"` to see the file structure', reason);
|
|
75
|
+
}
|
|
76
|
+
function splitShellSegments(cmd) {
|
|
77
|
+
const segments = [];
|
|
78
|
+
let cur = "";
|
|
79
|
+
let quote = null;
|
|
80
|
+
for (let i = 0; i < cmd.length; i++) {
|
|
81
|
+
const ch = cmd[i];
|
|
82
|
+
if (quote !== null) {
|
|
83
|
+
cur += ch;
|
|
84
|
+
if (ch === quote) quote = null;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (ch === '"' || ch === "'") {
|
|
88
|
+
quote = ch;
|
|
89
|
+
cur += ch;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (ch === "\\" && i + 1 < cmd.length) {
|
|
93
|
+
cur += ch + cmd[i + 1];
|
|
94
|
+
i++;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (ch === "&" && isRedirectAmpersand(cmd, i)) {
|
|
98
|
+
cur += ch;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (ch === "|" || ch === ";" || ch === "\n" || ch === "&") {
|
|
102
|
+
segments.push(cur);
|
|
103
|
+
cur = "";
|
|
104
|
+
if ((ch === "|" || ch === "&") && cmd[i + 1] === ch) i++;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
cur += ch;
|
|
108
|
+
}
|
|
109
|
+
segments.push(cur);
|
|
110
|
+
return segments.map((s) => s.trim()).filter((s) => s.length > 0);
|
|
111
|
+
}
|
|
112
|
+
function isTempPath(fp) {
|
|
113
|
+
const norm = fp.replace(/\\/g, "/");
|
|
114
|
+
return /^\/tmp\//i.test(norm) || /\/var\/folders\//i.test(norm) || // Anchored to a local drive root on purpose. Unanchored, this clause said yes to `//host/share/AppData/Local/Temp/x.md`, and the caller's answer to yes is to stat the path -- which on Windows opens an SMB session to whatever host a command named. A temp directory is somewhere on a local volume; nothing that starts with two separators is one.
|
|
115
|
+
/^[a-z]:\/(?:[^/]+\/)*AppData\/Local\/Temp\//i.test(norm) || norm.startsWith("/c/Users/") && norm.includes("/AppData/Local/Temp/") || isUnderSystemTemp(fp);
|
|
116
|
+
}
|
|
117
|
+
function isOrchestratorStateFile(filePath) {
|
|
118
|
+
const basename = (filePath.includes("/") ? filePath.split("/").at(-1) : filePath.split("\\").at(-1)) ?? filePath;
|
|
119
|
+
return /^\.improve-state-/.test(basename);
|
|
120
|
+
}
|
|
121
|
+
function extractCatSourceFile(cmd) {
|
|
122
|
+
const m = /^cat\s+(\S+\.(?:java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj|css|scss|sass|less))\s*$/.exec(cmd);
|
|
123
|
+
return m?.[1] ?? null;
|
|
124
|
+
}
|
|
125
|
+
function classifyFileExtensions(filePath) {
|
|
126
|
+
const basename = (filePath.includes("/") ? filePath.split("/").at(-1) : filePath.split("\\").at(-1)) ?? filePath;
|
|
127
|
+
const isEnvFile = /^\.env(\.\w+)?$/i.test(basename);
|
|
128
|
+
const hasKnownExt = /\.(?:java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj|css|scss|sass|less|md|mdx|rst|txt|json|yaml|yml|toml|xml|dtsx|ampkg|xaml|conf|cfg|ini|properties|sql|ps1|psm1|env)$/i.test(filePath);
|
|
129
|
+
if (!hasKnownExt && !isEnvFile) return null;
|
|
130
|
+
const isSql = /\.sql$/i.test(filePath);
|
|
131
|
+
const isDoc = /\.(?:md|mdx|rst|txt)$/i.test(filePath);
|
|
132
|
+
const isEnv = isEnvFile || /\.env$/i.test(filePath);
|
|
133
|
+
const isConfig = /\.(?:json|yaml|yml|toml|conf|cfg|ini|properties)$/i.test(filePath);
|
|
134
|
+
const isXml = /\.(?:xml|dtsx|ampkg|xaml)$/i.test(filePath);
|
|
135
|
+
return { isDoc, isEnv, isConfig, isSql, isXml };
|
|
136
|
+
}
|
|
137
|
+
function classifyDocConfig(filePath) {
|
|
138
|
+
const isDoc = /\.(?:md|mdx|rst|txt)$/i.test(filePath);
|
|
139
|
+
const isConfig = /\.(?:json|yaml|yml|toml|conf|cfg|ini|properties)$/i.test(filePath);
|
|
140
|
+
const isSql = /\.sql$/i.test(filePath);
|
|
141
|
+
const isXml = /\.(?:xml|dtsx|ampkg|xaml)$/i.test(filePath);
|
|
142
|
+
return { isDoc, isConfig, isSql, isXml };
|
|
143
|
+
}
|
|
144
|
+
function classifyCatPath(filePath, cmd0) {
|
|
145
|
+
if (isTempPath(filePath)) return null;
|
|
146
|
+
const flags = classifyFileExtensions(filePath);
|
|
147
|
+
if (flags === null) return null;
|
|
148
|
+
return { filePath, ...flags, cmd0 };
|
|
149
|
+
}
|
|
150
|
+
function extractCatFile(cmd) {
|
|
151
|
+
if (/-(?:TotalCount|Head|First|Tail)\b/i.test(cmd)) return null;
|
|
152
|
+
const m = /^(cat|bat|type|Get-Content|gc)(?:\s+(?:-[a-zA-Z]+|--[a-zA-Z-]+))*\s+(?:"([^"]+)"|'([^']+)'|(\S+?))(?:\s+-[a-zA-Z].*?)?(?:\s+2>(&1|\/dev\/null))?\s*$/i.exec(cmd);
|
|
153
|
+
if (!m) return null;
|
|
154
|
+
const cmd0 = m[1];
|
|
155
|
+
const filePath = m[2] ?? m[3] ?? m[4];
|
|
156
|
+
if (filePath === void 0) return null;
|
|
157
|
+
const r = classifyCatPath(filePath, cmd0);
|
|
158
|
+
if (r === null) return null;
|
|
159
|
+
return { ...r, advisoryOnly: m[5] === "/dev/null" };
|
|
160
|
+
}
|
|
161
|
+
function extractCatFilesMulti(cmd) {
|
|
162
|
+
if (/[|<>;&`]/.test(cmd) || cmd.includes("$(")) return null;
|
|
163
|
+
const m = /^(cat|bat|type|Get-Content|gc)\s+(.+?)\s*$/i.exec(cmd);
|
|
164
|
+
if (!m) return null;
|
|
165
|
+
const cmd0 = m[1];
|
|
166
|
+
const tokens = m[2].match(/"[^"]+"|'[^']+'|\S+/g) ?? [];
|
|
167
|
+
const paths = tokens.filter((t) => !/^-/.test(t)).map((t) => t.replace(/^["']|["']$/g, ""));
|
|
168
|
+
if (paths.length < 2) return null;
|
|
169
|
+
const out = paths.map((p) => classifyCatPath(p, cmd0)).filter((r) => r !== null);
|
|
170
|
+
return out.length >= 2 ? out : null;
|
|
171
|
+
}
|
|
172
|
+
var POWERSHELL_WRAP_RE = /^(?:powershell|pwsh)(?:\.exe)?(?:\s+-[a-zA-Z]+(?:\s+\S+)?)*\s+(?:-Command|-c|-EncodedCommand)\s+(?:"([^"]*)"|'([^']*)')\s*$/i;
|
|
173
|
+
var PS_GETCONTENT_INNER_RE = /^(?:Get-Content|gc|cat|type)(?:\s+(?:-[a-zA-Z]+|--[a-zA-Z-]+))*\s+(?:"([^"]+)"|'([^']+)'|(\S+?))(?:\s+-[a-zA-Z].*)?\s*$/i;
|
|
174
|
+
var PS_FILE_METHOD_RE = /\[(?:System\.)?IO\.File\]::(?:ReadAllText|ReadAllLines|ReadAllBytes|ReadLines|OpenText)\(\s*['"]([^'"]+)['"]/i;
|
|
175
|
+
function writesBackSamePath(cmd, filePath, methods) {
|
|
176
|
+
return new RegExp(`(?:${methods})\\(\\s*['"]${escapeRegExp(filePath)}['"]`, "i").test(cmd);
|
|
177
|
+
}
|
|
178
|
+
var NODE_WRITE_METHODS = "writeFileSync|appendFileSync";
|
|
179
|
+
var PS_WRITE_METHODS = "WriteAllText|WriteAllLines|WriteAllBytes|AppendAllText|AppendAllLines";
|
|
180
|
+
var PS_TEMP_READ_FLOOD_BYTES = 16 * 1024;
|
|
181
|
+
function isLargeFileOnDisk(filePath, floor) {
|
|
182
|
+
try {
|
|
183
|
+
return statSync(filePath).size >= floor;
|
|
184
|
+
} catch {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function extractPowerShellWrappedGetContent(cmd, event) {
|
|
189
|
+
const w = POWERSHELL_WRAP_RE.exec(cmd);
|
|
190
|
+
if (!w) return null;
|
|
191
|
+
const inner = (w[1] ?? w[2] ?? "").trim();
|
|
192
|
+
if (!inner) return null;
|
|
193
|
+
if (/-(?:TotalCount|Head|First|Tail)\b/i.test(inner)) return null;
|
|
194
|
+
const m = PS_GETCONTENT_INNER_RE.exec(inner);
|
|
195
|
+
if (!m) return null;
|
|
196
|
+
const filePath = m[1] ?? m[2] ?? m[3];
|
|
197
|
+
if (filePath === void 0) return null;
|
|
198
|
+
const flags = classifyFileExtensions(filePath);
|
|
199
|
+
if (flags === null) return null;
|
|
200
|
+
if (isTempPath(filePath)) {
|
|
201
|
+
if (!commandPathIsTouchable(filePath, event)) return null;
|
|
202
|
+
if (!isLargeFileOnDisk(filePath, PS_TEMP_READ_FLOOD_BYTES)) return null;
|
|
203
|
+
}
|
|
204
|
+
return { filePath, ...flags };
|
|
205
|
+
}
|
|
206
|
+
function extractPowerShellFileMethodRead(cmd, event) {
|
|
207
|
+
let inner = cmd.trim();
|
|
208
|
+
const w = POWERSHELL_WRAP_RE.exec(inner);
|
|
209
|
+
if (w) {
|
|
210
|
+
inner = (w[1] ?? w[2] ?? "").trim();
|
|
211
|
+
}
|
|
212
|
+
const m = PS_FILE_METHOD_RE.exec(inner);
|
|
213
|
+
if (!m?.[1]) return null;
|
|
214
|
+
const filePath = m[1];
|
|
215
|
+
if (isOrchestratorStateFile(filePath)) return null;
|
|
216
|
+
if (writesBackSamePath(inner, filePath, PS_WRITE_METHODS)) return null;
|
|
217
|
+
if (isTempPath(filePath)) {
|
|
218
|
+
if (!commandPathIsTouchable(filePath, event)) return null;
|
|
219
|
+
if (!isLargeFileOnDisk(filePath, PS_TEMP_READ_FLOOD_BYTES)) return null;
|
|
220
|
+
}
|
|
221
|
+
const flags = classifyFileExtensions(filePath);
|
|
222
|
+
if (flags === null) {
|
|
223
|
+
const { isDoc, isConfig, isSql, isXml } = classifyDocConfig(filePath);
|
|
224
|
+
return { filePath, isDoc, isEnv: false, isConfig, isSql, isXml };
|
|
225
|
+
}
|
|
226
|
+
return { filePath, isDoc: flags.isDoc, isEnv: flags.isEnv, isConfig: flags.isConfig, isSql: flags.isSql, isXml: flags.isXml };
|
|
227
|
+
}
|
|
228
|
+
function extractRgSymbolSearch(cmd) {
|
|
229
|
+
if (!/^(?:rg|grep)\s+/.test(cmd)) return null;
|
|
230
|
+
if (!/-n\b/.test(cmd)) return null;
|
|
231
|
+
const patternMatch = /["']([^"']+)["']/.exec(cmd);
|
|
232
|
+
const pattern = patternMatch?.[1];
|
|
233
|
+
if (!pattern) return null;
|
|
234
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*(\|[A-Za-z_][A-Za-z0-9_]*)*$/.test(pattern)) return null;
|
|
235
|
+
const fileMatch = /(?:^|\s)(?:"([^"]+\.(?:ts|tsx|js|jsx|py|go|rs|java|rb|php|swift|kt|cpp|cc|cxx|c|h))"|'([^']+\.(?:ts|tsx|js|jsx|py|go|rs|java|rb|php|swift|kt|cpp|cc|cxx|c|h))'|([^\s"'|<>]+\.(?:ts|tsx|js|jsx|py|go|rs|java|rb|php|swift|kt|cpp|cc|cxx|c|h)))(?:\s|$|\|)/i.exec(cmd);
|
|
236
|
+
if (!fileMatch) return null;
|
|
237
|
+
const filePath = fileMatch[1] ?? fileMatch[2] ?? fileMatch[3];
|
|
238
|
+
if (!filePath) return null;
|
|
239
|
+
if (isTempPath(filePath)) return null;
|
|
240
|
+
if (/(?:^|\s)-[a-zA-Z]*[rR][a-zA-Z]*(?=\s|$)/.test(cmd) || /(?:^|\s)--recursive(?=\s|$)/.test(cmd)) return null;
|
|
241
|
+
return { filePath, identifier: pattern };
|
|
242
|
+
}
|
|
243
|
+
function extractCatJsonPipe(cmd) {
|
|
244
|
+
const m = /^cat\s+(?:"([^"]+)"|'([^']+)'|(\S+))\s*\|\s*jq\b/.exec(cmd);
|
|
245
|
+
if (m) {
|
|
246
|
+
const filePath = m[1] ?? m[2] ?? m[3];
|
|
247
|
+
if (filePath && !isTempPath(filePath) && /\.(?:json|yaml|yml|toml)$/i.test(filePath)) {
|
|
248
|
+
return { filePath, isDirectJq: false };
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const jqDirect = /^jq(?:\s+(?:-[a-zA-Z]+|--[a-zA-Z0-9-]+(?:=\S+)?))*\s+(?:"[^"]*"|'[^']*'|(?:\.[a-zA-Z0-9_.*[\]]+))\s+(?:"([^"]+)"|'([^']+)'|(\S+))\s*$/.exec(cmd);
|
|
252
|
+
if (jqDirect) {
|
|
253
|
+
const filePath = jqDirect[1] ?? jqDirect[2] ?? jqDirect[3];
|
|
254
|
+
if (filePath && !isTempPath(filePath) && /\.(?:json|yaml|yml|toml)$/i.test(filePath)) {
|
|
255
|
+
return { filePath, isDirectJq: true };
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
function extractPowerShellJsonPipeline(cmd) {
|
|
261
|
+
let inner = cmd.trim();
|
|
262
|
+
const w = POWERSHELL_WRAP_RE.exec(inner);
|
|
263
|
+
if (w) {
|
|
264
|
+
inner = (w[1] ?? w[2] ?? "").trim();
|
|
265
|
+
}
|
|
266
|
+
if (!/\bConvertFrom-Json\b/i.test(inner)) return null;
|
|
267
|
+
const parseTarget = (segment) => {
|
|
268
|
+
const readAll = /\[(?:System\.)?IO\.File\]::ReadAllText\(\s*['"]?([^'")\s]+)['"]?\s*\)/i.exec(segment);
|
|
269
|
+
if (readAll) {
|
|
270
|
+
const p = readAll[1];
|
|
271
|
+
return p && /\.(?:json|txt|log|temp)$/i.test(p) ? p : null;
|
|
272
|
+
}
|
|
273
|
+
const tokens = segment.trim().split(/\s+/);
|
|
274
|
+
const cmdlets = /* @__PURE__ */ new Set(["get-content", "gc", "cat", "type"]);
|
|
275
|
+
const idx = tokens.findIndex((t) => cmdlets.has(t.toLowerCase()));
|
|
276
|
+
if (idx === -1) return null;
|
|
277
|
+
let argIdx = idx + 1;
|
|
278
|
+
while (argIdx < tokens.length) {
|
|
279
|
+
const tok = tokens[argIdx];
|
|
280
|
+
if (!tok) break;
|
|
281
|
+
if (tok.startsWith("-")) {
|
|
282
|
+
const flag = tok.toLowerCase();
|
|
283
|
+
if (flag === "-path" && argIdx + 1 < tokens.length) {
|
|
284
|
+
const next = tokens[argIdx + 1];
|
|
285
|
+
if (next && !next.startsWith("-")) {
|
|
286
|
+
const clean2 = next.replace(/^['"]|['"]$/g, "");
|
|
287
|
+
return /\.(?:json|txt|log|temp)$/i.test(clean2) ? clean2 : null;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if ((flag === "-encoding" || flag === "-totalcount") && argIdx + 1 < tokens.length) {
|
|
291
|
+
argIdx += 2;
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
argIdx++;
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
const clean = tok.replace(/^['"(]|['")]$/g, "");
|
|
298
|
+
return /\.(?:json|txt|log|temp)$/i.test(clean) ? clean : null;
|
|
299
|
+
}
|
|
300
|
+
return null;
|
|
301
|
+
};
|
|
302
|
+
const pipeIdx = inner.search(/\|\s*ConvertFrom-Json\b/i);
|
|
303
|
+
if (pipeIdx !== -1) {
|
|
304
|
+
const upstream = inner.slice(0, pipeIdx).trim();
|
|
305
|
+
return { filePath: parseTarget(upstream) };
|
|
306
|
+
}
|
|
307
|
+
const parenMatch = /ConvertFrom-Json\s*\(([^)]+)\)/i.exec(inner);
|
|
308
|
+
if (parenMatch) {
|
|
309
|
+
const inside = parenMatch[1] ?? "";
|
|
310
|
+
return { filePath: parseTarget(inside) };
|
|
311
|
+
}
|
|
312
|
+
if (/ConvertFrom-Json\s*\(/i.test(inner)) {
|
|
313
|
+
return { filePath: null };
|
|
314
|
+
}
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
function extractWslCatFile(cmd) {
|
|
318
|
+
const wslMatch = /^wsl(?:\s+-d\s+\S+)?\s+bash\s+-c\s+"cat(?:\s+(?:-[a-zA-Z]+|--[a-zA-Z-]+))*\s+\/mnt\/([a-z])\/([^"]*)"/.exec(cmd);
|
|
319
|
+
if (!wslMatch) return null;
|
|
320
|
+
const drive = wslMatch[1]?.toUpperCase();
|
|
321
|
+
const pathRest = wslMatch[2];
|
|
322
|
+
if (!drive || !pathRest) return null;
|
|
323
|
+
const filePath = drive + ":/" + pathRest;
|
|
324
|
+
if (isTempPath(filePath)) return null;
|
|
325
|
+
const flags = classifyFileExtensions(filePath);
|
|
326
|
+
if (flags === null) return null;
|
|
327
|
+
return { filePath, ...flags };
|
|
328
|
+
}
|
|
329
|
+
function pythonScanText(text) {
|
|
330
|
+
const dashC = /^python3?\s+-c\s*(['"])([\s\S]*)\1\s*$/.exec(text);
|
|
331
|
+
if (dashC) {
|
|
332
|
+
const source = dashC[2] ?? "";
|
|
333
|
+
return { source, masked: maskPythonStrings(source) };
|
|
334
|
+
}
|
|
335
|
+
if (/^python3?\b/.test(text)) return { source: text, masked: text };
|
|
336
|
+
return { source: text, masked: maskPythonStrings(text) };
|
|
337
|
+
}
|
|
338
|
+
function maskPythonStrings(text) {
|
|
339
|
+
const out = text.split("");
|
|
340
|
+
let i = 0;
|
|
341
|
+
while (i < text.length) {
|
|
342
|
+
const ch = text[i];
|
|
343
|
+
if (ch !== "'" && ch !== '"') {
|
|
344
|
+
i += 1;
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
const triple = text.slice(i, i + 3);
|
|
348
|
+
const delim = triple === ch + ch + ch ? triple : ch;
|
|
349
|
+
let j = i + delim.length;
|
|
350
|
+
while (j < text.length) {
|
|
351
|
+
if (text[j] === "\\") {
|
|
352
|
+
j += 2;
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
if (text.slice(j, j + delim.length) === delim) break;
|
|
356
|
+
out[j] = " ";
|
|
357
|
+
j += 1;
|
|
358
|
+
}
|
|
359
|
+
i = j + delim.length;
|
|
360
|
+
}
|
|
361
|
+
return out.join("");
|
|
362
|
+
}
|
|
363
|
+
function pythonOpenCalls(text) {
|
|
364
|
+
const { source, masked } = pythonScanText(text);
|
|
365
|
+
const calls = [];
|
|
366
|
+
for (const opener of masked.matchAll(/\bopen\s*\(/g)) {
|
|
367
|
+
const start = (opener.index ?? 0) + opener[0].length;
|
|
368
|
+
const args = [];
|
|
369
|
+
let current = "";
|
|
370
|
+
let depth = 0;
|
|
371
|
+
let quote = "";
|
|
372
|
+
let closed = false;
|
|
373
|
+
for (let i = start; i < source.length; i++) {
|
|
374
|
+
const ch = source[i] ?? "";
|
|
375
|
+
if (quote !== "") {
|
|
376
|
+
if (ch === "\\") {
|
|
377
|
+
current += ch + (source[i + 1] ?? "");
|
|
378
|
+
i += 1;
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
if (ch === quote) quote = "";
|
|
382
|
+
current += ch;
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
if (ch === "'" || ch === '"') {
|
|
386
|
+
quote = ch;
|
|
387
|
+
current += ch;
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
if (ch === "(" || ch === "[" || ch === "{") {
|
|
391
|
+
depth += 1;
|
|
392
|
+
current += ch;
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
if (ch === ")" && depth === 0) {
|
|
396
|
+
args.push(current);
|
|
397
|
+
closed = true;
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
if (ch === ")" || ch === "]" || ch === "}") {
|
|
401
|
+
depth -= 1;
|
|
402
|
+
current += ch;
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
if (ch === "," && depth === 0) {
|
|
406
|
+
args.push(current);
|
|
407
|
+
current = "";
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
current += ch;
|
|
411
|
+
}
|
|
412
|
+
if (!closed) continue;
|
|
413
|
+
const literalOf = (arg) => {
|
|
414
|
+
const m = /^\s*r?(['"])([^'"]*)\1\s*$/.exec(arg);
|
|
415
|
+
return m?.[2] ?? null;
|
|
416
|
+
};
|
|
417
|
+
const keyword = args.find((a) => /^\s*mode\s*=/.test(a));
|
|
418
|
+
const positional = args[1] !== void 0 && /^\s*[A-Za-z_]\w*\s*=[^=]/.test(args[1]) ? void 0 : args[1];
|
|
419
|
+
const modeArg = keyword !== void 0 ? keyword.replace(/^\s*mode\s*=/, "") : positional;
|
|
420
|
+
calls.push({
|
|
421
|
+
pathLiteral: args.length > 0 ? literalOf(args[0] ?? "") : null,
|
|
422
|
+
hasMode: modeArg !== void 0,
|
|
423
|
+
modeLiteral: modeArg === void 0 ? null : literalOf(modeArg)
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
return calls;
|
|
427
|
+
}
|
|
428
|
+
function pythonOpenWritesAFile(text) {
|
|
429
|
+
return pythonOpenCalls(text).some(
|
|
430
|
+
(call) => call.hasMode && (call.modeLiteral === null || /[wax+]/.test(call.modeLiteral))
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
function pythonWritesThroughFileObject(text) {
|
|
434
|
+
const { masked } = pythonScanText(text);
|
|
435
|
+
for (const call of masked.matchAll(/\.write(?:lines)?\s*\(/g)) {
|
|
436
|
+
const receiver = masked.slice(0, call.index ?? 0);
|
|
437
|
+
if (/(?:^|[^\w.])(?:sys\s*\.\s*)?(?:stdout|stderr|stdin)(?:\s*\.\s*buffer)?$/.test(receiver)) continue;
|
|
438
|
+
return true;
|
|
439
|
+
}
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
function pythonOpenPathsAreAllLiteral(text) {
|
|
443
|
+
const calls = pythonOpenCalls(text);
|
|
444
|
+
return calls.length > 0 && calls.every((call) => call.pathLiteral !== null);
|
|
445
|
+
}
|
|
446
|
+
var KNOWN_PYTHON_EXT_STR = "java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj|css|scss|sass|less|md|mdx|rst|txt|json|yaml|yml|toml|xml|dtsx|ampkg|xaml|html|htm|conf|cfg|ini|properties|sql|ps1|psm1|env";
|
|
447
|
+
var OPEN_EXT = new RegExp(`\\.(?:${KNOWN_PYTHON_EXT_STR})$`, "i");
|
|
448
|
+
function extractPythonFileRead(cmd) {
|
|
449
|
+
let inner = cmd.trim();
|
|
450
|
+
const w = POWERSHELL_WRAP_RE.exec(inner);
|
|
451
|
+
if (w) {
|
|
452
|
+
inner = (w[1] ?? w[2] ?? "").trim();
|
|
453
|
+
}
|
|
454
|
+
const psHereMatch = /^@'([\s\S]*?)'@\s*\|\s*(?:python3?|py)(?:\.exe)?(?:\s+-\S*|\s+-)?\s*$/i.exec(inner) ?? /^@"([\s\S]*?)"@\s*\|\s*(?:python3?|py)(?:\.exe)?(?:\s+-\S*|\s+-)?\s*$/i.exec(inner);
|
|
455
|
+
let pythonBody = null;
|
|
456
|
+
if (psHereMatch) {
|
|
457
|
+
pythonBody = (psHereMatch[1] ?? "").trim();
|
|
458
|
+
} else if (/^(?:python3?|py)(?:\.exe)?\b/i.test(inner)) {
|
|
459
|
+
pythonBody = inner;
|
|
460
|
+
}
|
|
461
|
+
if (pythonBody === null) return null;
|
|
462
|
+
if (pythonOpenWritesAFile(pythonBody) || pythonWritesThroughFileObject(pythonBody)) return null;
|
|
463
|
+
const outputOpen = /open\s*\(\s*r?['"]([^'"]+\.output)['"]/i.exec(pythonBody);
|
|
464
|
+
if (outputOpen?.[1]) {
|
|
465
|
+
const filePath = outputOpen[1];
|
|
466
|
+
if (isOrchestratorStateFile(filePath)) return null;
|
|
467
|
+
return { filePath, isDoc: false, isConfig: false, isEnv: false, isSql: false, isOutputFile: true };
|
|
468
|
+
}
|
|
469
|
+
const classifyResult = (filePath) => {
|
|
470
|
+
const flags = classifyFileExtensions(filePath);
|
|
471
|
+
if (flags !== null) {
|
|
472
|
+
return { filePath, isDoc: flags.isDoc, isConfig: flags.isConfig, isEnv: flags.isEnv, isSql: flags.isSql, isXml: flags.isXml, isOutputFile: false };
|
|
473
|
+
}
|
|
474
|
+
const { isDoc, isConfig, isSql, isXml } = classifyDocConfig(filePath);
|
|
475
|
+
const isEnv = /\.env(\.\w+)?$/i.test(filePath);
|
|
476
|
+
return { filePath, isDoc, isConfig, isEnv, isSql, isXml, isOutputFile: false };
|
|
477
|
+
};
|
|
478
|
+
const heredocMatch = /^python3?\s+-\s+<<\s*'?(\w+)'?\s*\n([\s\S]*?)\n\1\s*$/.exec(pythonBody);
|
|
479
|
+
if (heredocMatch) {
|
|
480
|
+
const body = heredocMatch[2] ?? "";
|
|
481
|
+
if (pythonOpenWritesAFile(body) || pythonWritesThroughFileObject(body)) return null;
|
|
482
|
+
const heredocOpen = new RegExp(`open\\s*\\(\\s*r?['"]([^'"]+\\.(?:${KNOWN_PYTHON_EXT_STR}))['"]`, "i").exec(body);
|
|
483
|
+
if (heredocOpen?.[1]) {
|
|
484
|
+
const filePath = heredocOpen[1];
|
|
485
|
+
if (isOrchestratorStateFile(filePath)) return null;
|
|
486
|
+
return classifyResult(filePath);
|
|
487
|
+
}
|
|
488
|
+
if (/open\s*\(/.test(body) && !pythonOpenPathsAreAllLiteral(body)) {
|
|
489
|
+
const literal = new RegExp(`['"]([^'"]+\\.(?:${KNOWN_PYTHON_EXT_STR}))['"]`, "i").exec(body);
|
|
490
|
+
if (literal?.[1]) {
|
|
491
|
+
const filePath = literal[1];
|
|
492
|
+
if (isOrchestratorStateFile(filePath)) return null;
|
|
493
|
+
if (OPEN_EXT.test(filePath)) {
|
|
494
|
+
return classifyResult(filePath);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
const direct = new RegExp(`open\\s*\\(\\s*r?['"]([^'"]+\\.(?:${KNOWN_PYTHON_EXT_STR}))['"]`, "i").exec(pythonBody);
|
|
501
|
+
if (direct) {
|
|
502
|
+
const filePath = direct[1] ?? "";
|
|
503
|
+
if (!filePath) return null;
|
|
504
|
+
if (isOrchestratorStateFile(filePath)) return null;
|
|
505
|
+
return classifyResult(filePath);
|
|
506
|
+
}
|
|
507
|
+
if (/open\s*\(/.test(pythonBody) && !pythonOpenPathsAreAllLiteral(pythonBody)) {
|
|
508
|
+
const literal = new RegExp(`['"]([^'"]+\\.(?:${KNOWN_PYTHON_EXT_STR}))['"]`, "i").exec(pythonBody);
|
|
509
|
+
if (literal) {
|
|
510
|
+
const filePath = literal[1] ?? "";
|
|
511
|
+
if (filePath) {
|
|
512
|
+
if (isOrchestratorStateFile(filePath)) return null;
|
|
513
|
+
if (OPEN_EXT.test(filePath)) {
|
|
514
|
+
return classifyResult(filePath);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return null;
|
|
520
|
+
}
|
|
521
|
+
function extractHeadFile(cmd) {
|
|
522
|
+
const direct = /^head(?:\s+-n\s+(\d+)|\s+-(\d+))?\s+(?:"([^"]+)"|'([^']+)'|(\S+))\s*$/.exec(cmd);
|
|
523
|
+
const piped = direct === null ? /^cat(?:\s+(?:-[a-zA-Z]+|--[a-zA-Z-]+))*\s+(?:"([^"]+)"|'([^']+)'|(\S+?))(?:\s+2>(?:&1|\/dev\/null))?\s*\|\s*head(?:\s+-n\s+(\d+)|\s+-(\d+))?\s*$/.exec(cmd) : null;
|
|
524
|
+
if (direct === null && piped === null) return null;
|
|
525
|
+
const n = parseInt((direct !== null ? direct[1] ?? direct[2] : piped[4] ?? piped[5]) ?? "0", 10);
|
|
526
|
+
if (n <= 10) return null;
|
|
527
|
+
const filePath = direct !== null ? direct[3] ?? direct[4] ?? direct[5] : piped[1] ?? piped[2] ?? piped[3];
|
|
528
|
+
if (filePath === void 0) return null;
|
|
529
|
+
if (isTempPath(filePath)) return null;
|
|
530
|
+
if (!/\.(?:ts|tsx|js|jsx|py|go|java|rs|rb|cs|md|mdx|rst|txt|json|yaml|yml|toml|sql|sh|xml|dtsx|ampkg|xaml)$/i.test(filePath)) return null;
|
|
531
|
+
const { isDoc, isConfig, isSql, isXml } = classifyDocConfig(filePath);
|
|
532
|
+
return { filePath, isDoc, isConfig, isSql, isXml, n };
|
|
533
|
+
}
|
|
534
|
+
function extractSedRange(cmd) {
|
|
535
|
+
const m = /^sed\s+-n\s+(?:['"]((?:\d+,\d+p)(?:;\d+,\d+p)*)['"]|(\d+,\d+p))\s+(?:"([^"]+)"|'([^']+)'|(\S+))(?:\s+2>(?:\/dev\/null|&1))?\s*$/.exec(cmd);
|
|
536
|
+
if (!m) return null;
|
|
537
|
+
const addressList = m[1] ?? m[2];
|
|
538
|
+
if (addressList === void 0) return null;
|
|
539
|
+
const ranges = [];
|
|
540
|
+
for (const clause of addressList.split(";")) {
|
|
541
|
+
const cm = /^(\d+),(\d+)p$/.exec(clause ?? "");
|
|
542
|
+
if (!cm) return null;
|
|
543
|
+
const start = parseInt(cm[1], 10);
|
|
544
|
+
const end = parseInt(cm[2], 10);
|
|
545
|
+
if (start < 1 || end < start) return null;
|
|
546
|
+
ranges.push([start, end]);
|
|
547
|
+
}
|
|
548
|
+
if (ranges.length === 0) return null;
|
|
549
|
+
const filePath = m[3] ?? m[4] ?? m[5];
|
|
550
|
+
if (filePath === void 0) return null;
|
|
551
|
+
if (isTempPath(filePath)) return null;
|
|
552
|
+
return { filePath, ranges };
|
|
553
|
+
}
|
|
554
|
+
function extractAwkRange(cmd) {
|
|
555
|
+
const m = /^awk\s+(?:'([^']+)'|"([^"]+)")\s+(?:"([^"]+)"|'([^']+)'|(\S+))(?:\s+2>(?:\/dev\/null|&1))?\s*$/.exec(cmd);
|
|
556
|
+
if (!m) return null;
|
|
557
|
+
const program = m[1] ?? m[2];
|
|
558
|
+
if (program === void 0) return null;
|
|
559
|
+
const cmp = /^\s*NR\s*>=\s*(\d+)\s*&&\s*NR\s*<=\s*(\d+)\s*(\{.*\})?\s*$/.exec(program);
|
|
560
|
+
const rng = /^\s*NR\s*==\s*(\d+)\s*,\s*NR\s*==\s*(\d+)\s*(\{.*\})?\s*$/.exec(program);
|
|
561
|
+
const hit = cmp ?? rng;
|
|
562
|
+
const action = hit?.[3];
|
|
563
|
+
if (action !== void 0 && !/^\{\s*(?:print(?:\s+NR\s*"[^"%]*"\s*\$0|\s+\$0)?|printf\s*"%d[^%"]*%s\\n"\s*,\s*NR\s*,\s*\$0)\s*\}$/.test(action)) return null;
|
|
564
|
+
if (!hit) return null;
|
|
565
|
+
const start = parseInt(hit[1], 10);
|
|
566
|
+
const end = parseInt(hit[2], 10);
|
|
567
|
+
if (start < 1 || end < start) return null;
|
|
568
|
+
const filePath = m[3] ?? m[4] ?? m[5];
|
|
569
|
+
if (filePath === void 0) return null;
|
|
570
|
+
if (isTempPath(filePath)) return null;
|
|
571
|
+
return { filePath, ranges: [[start, end]] };
|
|
572
|
+
}
|
|
573
|
+
function normalizeCatSedPipe(cmd) {
|
|
574
|
+
const m = /^cat(?:\s+(?:-[a-zA-Z]+|--[a-zA-Z-]+))*\s+(?:"([^"]+)"|'([^']+)'|(\S+?))(?:\s+2>(?:&1|\/dev\/null))?\s*\|\s*sed\s+-n\s+('[^']+'|"[^"]+"|\d+,\d+p)\s*$/.exec(cmd);
|
|
575
|
+
if (!m) return null;
|
|
576
|
+
const filePath = m[1] ?? m[2] ?? m[3];
|
|
577
|
+
if (filePath === void 0 || filePath.includes('"')) return null;
|
|
578
|
+
return "sed -n " + m[4] + ' "' + filePath + '"';
|
|
579
|
+
}
|
|
580
|
+
function extractLineRangeRead(cmd) {
|
|
581
|
+
const catSed = normalizeCatSedPipe(cmd);
|
|
582
|
+
if (catSed !== null) cmd = catSed;
|
|
583
|
+
const sed = extractSedRange(cmd);
|
|
584
|
+
if (sed !== null) return { ...sed, tool: "sed" };
|
|
585
|
+
const awk = extractAwkRange(cmd);
|
|
586
|
+
if (awk !== null) return { ...awk, tool: "awk" };
|
|
587
|
+
return null;
|
|
588
|
+
}
|
|
589
|
+
var FORMATTING_STAGE_RE = /^(?:fold|cut|cat|nl|head|tail)(?:\s+(?:-\S+|[\d,+-]+))*\s*$/;
|
|
590
|
+
function extractLineRangeReadsCompound(cmd) {
|
|
591
|
+
const cleaned = cmd.replace(/\s2>(?:&1|\/dev\/null)/g, "");
|
|
592
|
+
const segments = splitShellSegments(cleaned);
|
|
593
|
+
if (segments.length < 2) return null;
|
|
594
|
+
const reads = [];
|
|
595
|
+
for (const seg of segments) {
|
|
596
|
+
const r = extractLineRangeRead(seg);
|
|
597
|
+
if (r !== null) {
|
|
598
|
+
reads.push(r);
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
if (/^echo\b[^<>]*$/.test(seg)) continue;
|
|
602
|
+
if (FORMATTING_STAGE_RE.test(seg)) continue;
|
|
603
|
+
return null;
|
|
604
|
+
}
|
|
605
|
+
if (reads.length === 0) return null;
|
|
606
|
+
const merged = /* @__PURE__ */ new Map();
|
|
607
|
+
for (const r of reads) {
|
|
608
|
+
const prev = merged.get(r.filePath);
|
|
609
|
+
if (prev !== void 0) prev.ranges.push(...r.ranges);
|
|
610
|
+
else merged.set(r.filePath, { filePath: r.filePath, ranges: [...r.ranges], tool: r.tool });
|
|
611
|
+
}
|
|
612
|
+
return [...merged.values()];
|
|
613
|
+
}
|
|
614
|
+
function sedRangeHint(filePath, ranges, tool, sub) {
|
|
615
|
+
return "`" + (tool === "awk" ? "awk" : "sed -n") + "` line-range reads bypass read hooks. " + substituteSentence(filePath, ranges, sub);
|
|
616
|
+
}
|
|
617
|
+
function substituteSentence(filePath, ranges, sub) {
|
|
618
|
+
const spans = ranges.map(([s, e]) => s + "-" + e).join(", ");
|
|
619
|
+
const reads = sub.commands.map((c) => "`" + c + "`");
|
|
620
|
+
const allReads = reads.length === 2 ? reads.join(" and ") : reads.length >= 3 ? reads.slice(0, -1).join(", ") + ", and " + reads[reads.length - 1] : reads[0];
|
|
621
|
+
return "Lines " + spans + " of " + filePath + " cost " + sub.requestedBytes + " bytes; " + allReads + " returns the same content as " + sub.replacementBytes + " bytes, resolved to whole regions and robust to line shifts.";
|
|
622
|
+
}
|
|
623
|
+
function leadingLinesHint(lead, hintPath, start, end, preHookCwd, substitute) {
|
|
624
|
+
const key = resolveIndexPath(hintPath, preHookCwd ?? process.cwd());
|
|
625
|
+
const prior = findRangeOverlap(getFileLineRanges(key), start, end);
|
|
626
|
+
if (prior !== null) return sedOverlapHint(hintPath, prior, start, end);
|
|
627
|
+
if (substitute === null) return null;
|
|
628
|
+
return lead + substituteSentence(hintPath, [[start, end]], substitute);
|
|
629
|
+
}
|
|
630
|
+
function findRangeOverlap(prior, start, end) {
|
|
631
|
+
let best = null;
|
|
632
|
+
let bestOverlap = 0;
|
|
633
|
+
for (const range of prior) {
|
|
634
|
+
const overlap = Math.min(range[1], end) - Math.max(range[0], start) + 1;
|
|
635
|
+
if (overlap > bestOverlap) {
|
|
636
|
+
bestOverlap = overlap;
|
|
637
|
+
best = range;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
return best;
|
|
641
|
+
}
|
|
642
|
+
function sedOverlapHint(filePath, prior, start, end) {
|
|
643
|
+
const base = "You already read lines " + prior[0] + "-" + prior[1] + " of " + filePath + " via an earlier line-range read this session; this read (" + start + "-" + end + ") overlaps. ";
|
|
644
|
+
const segments = [];
|
|
645
|
+
if (start < prior[0]) segments.push([start, Math.min(end, prior[0] - 1)]);
|
|
646
|
+
if (end > prior[1]) segments.push([Math.max(start, prior[1] + 1), end]);
|
|
647
|
+
if (segments.length === 0) {
|
|
648
|
+
return base + "These lines were already served - recall them from your earlier output instead of re-reading.";
|
|
649
|
+
}
|
|
650
|
+
const reads = segments.map(([s, e]) => '`token-goat read "' + filePath + "@" + s + "-" + e + '"`').join(" and ");
|
|
651
|
+
return base + "For only the new lines, " + reads + ".";
|
|
652
|
+
}
|
|
653
|
+
function extractNodeFileRead(cmd) {
|
|
654
|
+
if (!/^node\s+-e/.test(cmd)) return null;
|
|
655
|
+
const readSync2 = /readFileSync\(['"]([^'"]+\.(?:ts|tsx|js|jsx|py|go|java|rs|rb|cs|md|mdx|rst|txt|json|yaml|yml|toml|xml|conf|cfg|ini|properties|sql))['"]/i.exec(cmd);
|
|
656
|
+
if (readSync2?.[1]) {
|
|
657
|
+
const filePath = readSync2[1];
|
|
658
|
+
if (isOrchestratorStateFile(filePath)) return null;
|
|
659
|
+
if (isTempPath(filePath)) return null;
|
|
660
|
+
if (writesBackSamePath(cmd, filePath, NODE_WRITE_METHODS)) return null;
|
|
661
|
+
const { isDoc, isConfig, isSql } = classifyDocConfig(filePath);
|
|
662
|
+
return { filePath, isDoc, isConfig, isSql };
|
|
663
|
+
}
|
|
664
|
+
const requireM = /require\(['"]([^'"]+\.json)['"]\)/i.exec(cmd);
|
|
665
|
+
if (requireM?.[1]) {
|
|
666
|
+
const filePath = requireM[1];
|
|
667
|
+
if (filePath.includes("node_modules")) return null;
|
|
668
|
+
if (isOrchestratorStateFile(filePath)) return null;
|
|
669
|
+
if (isTempPath(filePath)) return null;
|
|
670
|
+
if (writesBackSamePath(cmd, filePath, NODE_WRITE_METHODS)) return null;
|
|
671
|
+
return { filePath, isDoc: false, isConfig: true, isSql: false };
|
|
672
|
+
}
|
|
673
|
+
return null;
|
|
674
|
+
}
|
|
675
|
+
function extractTailFile(cmd) {
|
|
676
|
+
if (/-f\b/.test(cmd)) return null;
|
|
677
|
+
if (/-c\b/.test(cmd)) return null;
|
|
678
|
+
if (/-n\s*\+/.test(cmd)) return null;
|
|
679
|
+
const direct = /^tail(?:\s+-n\s+(\d+)|\s+-(\d+))?\s+(?:"([^"]+)"|'([^']+)'|(\S+))\s*$/.exec(cmd);
|
|
680
|
+
const piped = direct === null ? /^cat(?:\s+(?:-[a-zA-Z]+|--[a-zA-Z-]+))*\s+(?:"([^"]+)"|'([^']+)'|(\S+?))(?:\s+2>(?:&1|\/dev\/null))?\s*\|\s*tail(?:\s+-n\s+(\d+)|\s+-(\d+))?\s*$/.exec(cmd) : null;
|
|
681
|
+
if (direct === null && piped === null) return null;
|
|
682
|
+
const n = parseInt((direct !== null ? direct[1] ?? direct[2] : piped[4] ?? piped[5]) ?? "0", 10);
|
|
683
|
+
if (n <= 10) return null;
|
|
684
|
+
const filePath = direct !== null ? direct[3] ?? direct[4] ?? direct[5] : piped[1] ?? piped[2] ?? piped[3];
|
|
685
|
+
if (!filePath) return null;
|
|
686
|
+
if (isTempPath(filePath)) return null;
|
|
687
|
+
if (!/\.(?:ts|tsx|js|jsx|py|go|java|rs|rb|cs|md|mdx|rst|txt|json|yaml|yml|toml|sql|sh|xml|dtsx|ampkg|xaml)$/i.test(filePath)) return null;
|
|
688
|
+
const { isDoc, isConfig, isSql, isXml } = classifyDocConfig(filePath);
|
|
689
|
+
return { filePath, isDoc, isConfig, isSql, isXml };
|
|
690
|
+
}
|
|
691
|
+
function extractGetContentTail(cmd) {
|
|
692
|
+
const tailMatch = /-Tail\s+(\d+)/i.exec(cmd);
|
|
693
|
+
if (!tailMatch) return null;
|
|
694
|
+
const n = parseInt(tailMatch[1], 10);
|
|
695
|
+
if (n <= 10) return null;
|
|
696
|
+
const getnMatch = /^(Get-Content|gc)\s+/i.exec(cmd);
|
|
697
|
+
if (!getnMatch) return null;
|
|
698
|
+
const afterCmd = cmd.slice(getnMatch[0].length).replace(/-Path\s+/i, "");
|
|
699
|
+
const beforeTail = afterCmd.split(/-Tail/i)[0]?.trim() ?? "";
|
|
700
|
+
const afterTail = afterCmd.split(/-Tail\s+\d+/i)[1]?.trim() ?? "";
|
|
701
|
+
const filePath = (beforeTail || afterTail).replace(/^["']|["']$/g, "");
|
|
702
|
+
if (!filePath) return null;
|
|
703
|
+
if (isTempPath(filePath)) return null;
|
|
704
|
+
if (!/\.(?:ts|tsx|js|jsx|py|go|java|rs|rb|cs|md|mdx|rst|txt|json|yaml|yml|toml|sql|sh|ps1|psm1|xml|dtsx|ampkg|xaml)$/i.test(filePath)) return null;
|
|
705
|
+
const { isDoc, isConfig, isSql, isXml } = classifyDocConfig(filePath);
|
|
706
|
+
return { filePath, isDoc, isConfig, isSql, isXml };
|
|
707
|
+
}
|
|
708
|
+
function extractGetContentSelectFirst(cmd) {
|
|
709
|
+
const m = /^(Get-Content|gc)\s+([^|]+)\s*\|\s*(Select-Object|select)\s+(-First\s+(\d+))/i.exec(cmd);
|
|
710
|
+
if (!m) return null;
|
|
711
|
+
const filePath = (m[2]?.trim() ?? "").replace(/^-Path\s+/i, "").replace(/^["']|["']$/g, "");
|
|
712
|
+
const n = parseInt(m[5] ?? "0", 10);
|
|
713
|
+
if (n <= 10) return null;
|
|
714
|
+
if (!filePath) return null;
|
|
715
|
+
if (isTempPath(filePath)) return null;
|
|
716
|
+
if (!/\.(?:ts|tsx|js|jsx|py|go|java|rs|rb|cs|md|mdx|rst|txt|json|yaml|yml|toml|sql|sh|ps1|psm1|xml|dtsx|ampkg|xaml)$/i.test(filePath)) return null;
|
|
717
|
+
const { isDoc, isConfig, isSql, isXml } = classifyDocConfig(filePath);
|
|
718
|
+
return { filePath, isDoc, isConfig, isSql, isXml, n };
|
|
719
|
+
}
|
|
720
|
+
function extractGetContentHead(cmd) {
|
|
721
|
+
const headMatch = /-(?:TotalCount|Head|First)\s+(\d+)/i.exec(cmd);
|
|
722
|
+
if (!headMatch) return null;
|
|
723
|
+
const n = parseInt(headMatch[1], 10);
|
|
724
|
+
if (n <= 10) return null;
|
|
725
|
+
const getnMatch = /^(Get-Content|gc)\s+/i.exec(cmd);
|
|
726
|
+
if (!getnMatch) return null;
|
|
727
|
+
const afterCmd = cmd.slice(getnMatch[0].length).replace(/-Path\s+/i, "");
|
|
728
|
+
const beforeHead = afterCmd.split(/-(?:TotalCount|Head|First)/i)[0]?.trim() ?? "";
|
|
729
|
+
const afterHead = afterCmd.split(/-(?:TotalCount|Head|First)\s+\d+/i)[1]?.trim() ?? "";
|
|
730
|
+
const cleanedBefore = beforeHead.replace(/-(?:Encoding|Delimiter|Wait)\s+\S+/gi, "").replace(/-[a-zA-Z]+/g, "").trim();
|
|
731
|
+
const cleanedAfter = afterHead.replace(/-(?:Encoding|Delimiter|Wait)\s+\S+/gi, "").replace(/-[a-zA-Z]+/g, "").trim();
|
|
732
|
+
const filePath = (cleanedBefore || cleanedAfter).replace(/^["']|["']$/g, "");
|
|
733
|
+
if (!filePath) return null;
|
|
734
|
+
if (isTempPath(filePath)) return null;
|
|
735
|
+
if (!/\.(?:ts|tsx|js|jsx|py|go|java|rs|rb|cs|md|mdx|rst|txt|json|yaml|yml|toml|sql|sh|ps1|psm1|xml|dtsx|ampkg|xaml)$/i.test(filePath)) return null;
|
|
736
|
+
const { isDoc, isConfig, isSql, isXml } = classifyDocConfig(filePath);
|
|
737
|
+
return { filePath, isDoc, isConfig, isSql, isXml, n };
|
|
738
|
+
}
|
|
739
|
+
var JSONL_TRANSCRIPT_HEAD_RE = /^\{\s*"(?:(?:parentUuid|isSidechain)"\s*:|type"\s*:\s*"fork-context-ref")/;
|
|
740
|
+
function taskOutputIsJsonlTranscript(outPath) {
|
|
741
|
+
let fd = null;
|
|
742
|
+
try {
|
|
743
|
+
fd = openSync(normalizePath(outPath), "r");
|
|
744
|
+
const buf = Buffer.alloc(64);
|
|
745
|
+
const read = readSync(fd, buf, 0, buf.length, 0);
|
|
746
|
+
return JSONL_TRANSCRIPT_HEAD_RE.test(buf.subarray(0, read).toString("utf-8").trim());
|
|
747
|
+
} catch {
|
|
748
|
+
return false;
|
|
749
|
+
} finally {
|
|
750
|
+
if (fd !== null) {
|
|
751
|
+
try {
|
|
752
|
+
closeSync(fd);
|
|
753
|
+
} catch {
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
function extractTasksOutput(cmd) {
|
|
759
|
+
const taskOutputRe = /(?:^|[/\\])tasks[/\\]([a-z0-9]+)\.output$/;
|
|
760
|
+
const catM = /^cat(?:\s+(?:-[a-zA-Z]+|--[a-zA-Z-]+))*\s+(?:"([^"]+)"|'([^']+)'|(\S+))\s*$/.exec(cmd);
|
|
761
|
+
if (catM) {
|
|
762
|
+
const fp = catM[1] ?? catM[2] ?? catM[3];
|
|
763
|
+
if (fp) {
|
|
764
|
+
const m = taskOutputRe.exec(fp);
|
|
765
|
+
if (m) return { id: m[1], path: fp };
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
if (!/-f\b/.test(cmd) && !/-n\s*\+/.test(cmd)) {
|
|
769
|
+
const tailM = /^tail(?:\s+-n\s+(\d+)|\s+-(\d+))?\s+(?:"([^"]+)"|'([^']+)'|(\S+))\s*$/.exec(cmd);
|
|
770
|
+
if (tailM) {
|
|
771
|
+
const fp = tailM[3] ?? tailM[4] ?? tailM[5];
|
|
772
|
+
if (fp) {
|
|
773
|
+
const m = taskOutputRe.exec(fp);
|
|
774
|
+
if (m) {
|
|
775
|
+
const nStr = tailM[1] ?? tailM[2];
|
|
776
|
+
const n = nStr !== void 0 ? parseInt(nStr, 10) : void 0;
|
|
777
|
+
return n !== void 0 ? { id: m[1], path: fp, n } : { id: m[1], path: fp };
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
const byteTailM = /^tail\s+-c\s+\d+\s+(?:"([^"]+)"|'([^']+)'|(\S+))\s*$/.exec(cmd);
|
|
782
|
+
if (byteTailM) {
|
|
783
|
+
const fp = byteTailM[1] ?? byteTailM[2] ?? byteTailM[3];
|
|
784
|
+
if (fp) {
|
|
785
|
+
const m = taskOutputRe.exec(fp);
|
|
786
|
+
if (m) return { id: m[1], path: fp };
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
return null;
|
|
791
|
+
}
|
|
792
|
+
function extractToolResultsFile(cmd) {
|
|
793
|
+
const toolResultsRe = new RegExp("[/\\\\]tool-results[/\\\\](" + TOOL_RESULTS_ID_CHARS + ")\\.txt$", "i");
|
|
794
|
+
const catM = /^cat(?:\s+(?:-[a-zA-Z]+|--[a-zA-Z-]+))*\s+(?:"([^"]+)"|'([^']+)'|(\S+))\s*$/.exec(cmd);
|
|
795
|
+
if (catM) {
|
|
796
|
+
const fp = catM[1] ?? catM[2] ?? catM[3];
|
|
797
|
+
if (fp) {
|
|
798
|
+
const m = toolResultsRe.exec(fp);
|
|
799
|
+
if (m) return { path: fp };
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
if (!/-f\b/.test(cmd) && !/-n\s*\+/.test(cmd)) {
|
|
803
|
+
const tailM = /^tail(?:\s+-n\s+(\d+)|\s+-(\d+))?\s+(?:"([^"]+)"|'([^']+)'|(\S+))\s*$/.exec(cmd);
|
|
804
|
+
if (tailM) {
|
|
805
|
+
const fp = tailM[3] ?? tailM[4] ?? tailM[5];
|
|
806
|
+
if (fp) {
|
|
807
|
+
const m = toolResultsRe.exec(fp);
|
|
808
|
+
if (m) return { path: fp };
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
const byteTailM = /^tail\s+-c\s+\d+\s+(?:"([^"]+)"|'([^']+)'|(\S+))\s*$/.exec(cmd);
|
|
812
|
+
if (byteTailM) {
|
|
813
|
+
const fp = byteTailM[1] ?? byteTailM[2] ?? byteTailM[3];
|
|
814
|
+
if (fp) {
|
|
815
|
+
const m = toolResultsRe.exec(fp);
|
|
816
|
+
if (m) return { path: fp };
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
return null;
|
|
821
|
+
}
|
|
822
|
+
function extractDirectoryListing(cmd) {
|
|
823
|
+
return /^eza\s+.*--long\s+\S+/.test(cmd) || /^eza\s+.*--tree/.test(cmd) || /^tree(\s|$)/.test(cmd) || /^ls\s+(?:\S+\s+)*-[a-zA-Z]*R[a-zA-Z]*(?:\s|$)/.test(cmd) || /^ls\s+(?:-[la]+\s+)?(\S+)\s*[|]\s*head/.test(cmd) || /^ls\s+(?:-[la]+\s+)?(\S+)\s*[|]\s*grep/.test(cmd) || /^ls\s+(?:-[la]+\s+)?(\S+)\s*[|]\s*wc/.test(cmd) || /^(?:Get-ChildItem|gci|dir)\b.*(?:-Recurse|-r\b|\/s\b)/i.test(cmd);
|
|
824
|
+
}
|
|
825
|
+
function extractForLoopWcL(cmd) {
|
|
826
|
+
return /^for\s+\w+\s+in\s+.*;\s*do\s+wc\s+-l/.test(cmd);
|
|
827
|
+
}
|
|
828
|
+
function extractFindCommand(cmd) {
|
|
829
|
+
if (!/^find\b/.test(cmd)) return null;
|
|
830
|
+
const isXargsGrepL = /[|]\s*xargs\s+(?:grep|rg)\s+.*-l\b/.test(cmd);
|
|
831
|
+
const nameMatch = /-name\s+['"]([^'"]+)['"]/i.exec(cmd);
|
|
832
|
+
const extGlob = nameMatch ? nameMatch[1] ?? null : null;
|
|
833
|
+
return { extGlob, isXargsGrepL };
|
|
834
|
+
}
|
|
835
|
+
function extractMarkdownHeadingGrep(cmd) {
|
|
836
|
+
if (!/^(?:rg|grep)\s+/.test(cmd)) return null;
|
|
837
|
+
if (!/-n\b/.test(cmd)) return null;
|
|
838
|
+
const hasHeadingPattern = /["']?\^#{1,6}["']?/.test(cmd) || /["']?\^#\+["']?/.test(cmd) || /["']?\^#\\+["']?/.test(cmd);
|
|
839
|
+
if (!hasHeadingPattern) return null;
|
|
840
|
+
const fileMatch = /(?:^|\s)(?:"([^"]+\.(?:md|markdown))"|'([^']+\.(?:md|markdown))'|([^\s"'|<>]+\.(?:md|markdown)))\s*(?:\||$)/.exec(cmd);
|
|
841
|
+
if (!fileMatch) return null;
|
|
842
|
+
const filePath = fileMatch[1] ?? fileMatch[2] ?? fileMatch[3];
|
|
843
|
+
if (!filePath) return null;
|
|
844
|
+
return { filePath };
|
|
845
|
+
}
|
|
846
|
+
function extractRgStructuralSearch(cmd) {
|
|
847
|
+
if (!/^(?:rg|grep)\s+/.test(cmd)) return null;
|
|
848
|
+
const hasStructural = /["']?\^?(?:def\s|class\s|function\s|func\s|fn\s|pub fn\s|import\s|from\s)/.test(cmd) || /["']\^(?:def|class|function|func|import|from)["']/.test(cmd) || /\\bdef\\b|\\bclass\\b/.test(cmd) || /["']?\^[ \t]+def\b/.test(cmd);
|
|
849
|
+
if (!hasStructural) return null;
|
|
850
|
+
const fileMatch = /(?:^|\s)(?:"([^"]+\.(?:py|ts|tsx|js|jsx|go|rs|rb|cs|java|cpp|cc|cxx|c|h|sh|bash))"|('([^']+\.(?:py|ts|tsx|js|jsx|go|rs|rb|cs|java|cpp|cc|cxx|c|h|sh|bash))')|([^\s"']+\.(?:py|ts|tsx|js|jsx|go|rs|rb|cs|java|cpp|cc|cxx|c|h|sh|bash)))\s*$/.exec(cmd);
|
|
851
|
+
if (!fileMatch) return null;
|
|
852
|
+
const filePath = fileMatch[1] ?? fileMatch[3] ?? fileMatch[4];
|
|
853
|
+
if (!filePath) return null;
|
|
854
|
+
if (isTempPath(filePath)) return null;
|
|
855
|
+
return { filePath };
|
|
856
|
+
}
|
|
857
|
+
function extractGrepPipeChain(cmd) {
|
|
858
|
+
return /^(?:rg|grep)\b.*\|\s*(?:rg|grep)\b/.test(cmd);
|
|
859
|
+
}
|
|
860
|
+
function splitCommandWords(cmd) {
|
|
861
|
+
return splitCommandWordsAt(cmd).words;
|
|
862
|
+
}
|
|
863
|
+
function splitCommandWordsAt(cmd) {
|
|
864
|
+
const words = [];
|
|
865
|
+
const starts = [];
|
|
866
|
+
let cur = "";
|
|
867
|
+
let quote = null;
|
|
868
|
+
let started = false;
|
|
869
|
+
let start = 0;
|
|
870
|
+
for (let i = 0; i < cmd.length; i++) {
|
|
871
|
+
const ch = cmd[i];
|
|
872
|
+
if (quote !== null) {
|
|
873
|
+
if (ch === "\\" && quote === '"' && i + 1 < cmd.length) {
|
|
874
|
+
cur += cmd[i + 1];
|
|
875
|
+
i++;
|
|
876
|
+
continue;
|
|
877
|
+
}
|
|
878
|
+
if (ch === quote) quote = null;
|
|
879
|
+
else cur += ch;
|
|
880
|
+
continue;
|
|
881
|
+
}
|
|
882
|
+
if (!started) start = i;
|
|
883
|
+
if (ch === "\\" && i + 1 < cmd.length) {
|
|
884
|
+
cur += cmd[i + 1];
|
|
885
|
+
i++;
|
|
886
|
+
started = true;
|
|
887
|
+
continue;
|
|
888
|
+
}
|
|
889
|
+
if (ch === '"' || ch === "'") {
|
|
890
|
+
quote = ch;
|
|
891
|
+
started = true;
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
894
|
+
if (/\s/.test(ch)) {
|
|
895
|
+
if (started) {
|
|
896
|
+
words.push(cur);
|
|
897
|
+
starts.push(start);
|
|
898
|
+
}
|
|
899
|
+
cur = "";
|
|
900
|
+
started = false;
|
|
901
|
+
continue;
|
|
902
|
+
}
|
|
903
|
+
cur += ch;
|
|
904
|
+
started = true;
|
|
905
|
+
}
|
|
906
|
+
if (started) {
|
|
907
|
+
words.push(cur);
|
|
908
|
+
starts.push(start);
|
|
909
|
+
}
|
|
910
|
+
return { words, starts };
|
|
911
|
+
}
|
|
912
|
+
var COMPRESS_OPT_OUT_VAR = "TOKEN_GOAT_BASH_COMPRESS";
|
|
913
|
+
var HEREDOC_OPENER_RE = /(?<!<)<<(?!<)-?[\t ]*(['"]?)([A-Za-z_]\w*)\1/g;
|
|
914
|
+
function stripHeredocBodies(cmd) {
|
|
915
|
+
const kept = [];
|
|
916
|
+
const pending = [];
|
|
917
|
+
for (const line of cmd.split("\n")) {
|
|
918
|
+
if (pending.length > 0) {
|
|
919
|
+
if (line.trim() === pending[0]) pending.shift();
|
|
920
|
+
continue;
|
|
921
|
+
}
|
|
922
|
+
kept.push(line);
|
|
923
|
+
for (const m of line.matchAll(HEREDOC_OPENER_RE)) pending.push(m[2]);
|
|
924
|
+
}
|
|
925
|
+
return kept.join("\n");
|
|
926
|
+
}
|
|
927
|
+
var ASSIGNMENT_PREFIX_WORDS = /* @__PURE__ */ new Set(["env", "export", "time", "command", "nohup", "exec", "!", "{", "do", "then", "else"]);
|
|
928
|
+
var ENV_VALUE_FLAGS = /* @__PURE__ */ new Set(["-u", "--unset", "-C", "--chdir"]);
|
|
929
|
+
var ENV_PREFIX_WORDS = /* @__PURE__ */ new Set(["env"]);
|
|
930
|
+
function walkCommandPrefix(words, prefixWords) {
|
|
931
|
+
const assignments = [];
|
|
932
|
+
let prefixed = false;
|
|
933
|
+
for (let i = 0; i < words.length; i++) {
|
|
934
|
+
const word = words[i];
|
|
935
|
+
const pwsh = /^\$env:([A-Za-z_]\w*)(?:=(.*))?$/i.exec(word);
|
|
936
|
+
if (pwsh !== null) {
|
|
937
|
+
assignments.push({ name: pwsh[1].toUpperCase(), value: pwsh[2] ?? (words[i + 1] === "=" ? words[i + 2] : void 0) });
|
|
938
|
+
return { command: i, assignments };
|
|
939
|
+
}
|
|
940
|
+
const posix = /^([A-Za-z_]\w*)=(.*)$/.exec(word);
|
|
941
|
+
if (posix !== null) {
|
|
942
|
+
assignments.push({ name: posix[1], value: posix[2] });
|
|
943
|
+
continue;
|
|
944
|
+
}
|
|
945
|
+
if (prefixWords.has(word)) {
|
|
946
|
+
prefixed = true;
|
|
947
|
+
continue;
|
|
948
|
+
}
|
|
949
|
+
if (prefixed && word.startsWith("-")) {
|
|
950
|
+
if (ENV_VALUE_FLAGS.has(word)) i++;
|
|
951
|
+
continue;
|
|
952
|
+
}
|
|
953
|
+
return { command: i, assignments };
|
|
954
|
+
}
|
|
955
|
+
return { command: words.length, assignments };
|
|
956
|
+
}
|
|
957
|
+
function stripLeadingAssignments(cmd) {
|
|
958
|
+
const { words, starts } = splitCommandWordsAt(cmd);
|
|
959
|
+
const at = starts[walkCommandPrefix(words, ENV_PREFIX_WORDS).command];
|
|
960
|
+
if (at === void 0 || at === 0) return cmd;
|
|
961
|
+
return isCompressibleSingleCommand(cmd.slice(0, at + 1)) ? cmd.slice(at) : cmd;
|
|
962
|
+
}
|
|
963
|
+
function commandOptsOutOfCompression(cmd) {
|
|
964
|
+
if (!cmd.toUpperCase().includes(COMPRESS_OPT_OUT_VAR)) return false;
|
|
965
|
+
const optsOut = (value) => value !== void 0 && FALSY_ENV_VALUES.has(value.trim().toLowerCase());
|
|
966
|
+
const commands = cmd.includes("<<") ? stripHeredocBodies(cmd) : cmd;
|
|
967
|
+
for (const segment of splitShellSegments(commands)) {
|
|
968
|
+
const words = splitCommandWords(segment).map((word) => word.replace(/^\(+/, ""));
|
|
969
|
+
if (walkCommandPrefix(words, ASSIGNMENT_PREFIX_WORDS).assignments.some(({ name, value }) => name === COMPRESS_OPT_OUT_VAR && optsOut(value))) return true;
|
|
970
|
+
}
|
|
971
|
+
return false;
|
|
972
|
+
}
|
|
973
|
+
var CURL_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
974
|
+
"-H",
|
|
975
|
+
"--header",
|
|
976
|
+
"-A",
|
|
977
|
+
"--user-agent",
|
|
978
|
+
"-e",
|
|
979
|
+
"--referer",
|
|
980
|
+
"-b",
|
|
981
|
+
"--cookie",
|
|
982
|
+
"-c",
|
|
983
|
+
"--cookie-jar",
|
|
984
|
+
"-o",
|
|
985
|
+
"--output",
|
|
986
|
+
"--output-dir",
|
|
987
|
+
"-u",
|
|
988
|
+
"--user",
|
|
989
|
+
"-U",
|
|
990
|
+
"--proxy-user",
|
|
991
|
+
"-x",
|
|
992
|
+
"--proxy",
|
|
993
|
+
"-X",
|
|
994
|
+
"--request",
|
|
995
|
+
"-d",
|
|
996
|
+
"--data",
|
|
997
|
+
"--data-raw",
|
|
998
|
+
"--data-binary",
|
|
999
|
+
"--data-urlencode",
|
|
1000
|
+
"-F",
|
|
1001
|
+
"--form",
|
|
1002
|
+
"-T",
|
|
1003
|
+
"--upload-file",
|
|
1004
|
+
"-K",
|
|
1005
|
+
"--config",
|
|
1006
|
+
"-w",
|
|
1007
|
+
"--write-out",
|
|
1008
|
+
"-m",
|
|
1009
|
+
"--max-time",
|
|
1010
|
+
"--connect-to",
|
|
1011
|
+
"--resolve",
|
|
1012
|
+
"--retry",
|
|
1013
|
+
"--cacert",
|
|
1014
|
+
"-E",
|
|
1015
|
+
"--cert",
|
|
1016
|
+
"--key",
|
|
1017
|
+
"--range",
|
|
1018
|
+
"-r",
|
|
1019
|
+
"--interface",
|
|
1020
|
+
// Options whose own value is a URL or a host. Leaving these out was the same defect as reading the first URL in the string: `curl --doh-url https://doh/ -o f https://target` answered with the DoH resolver rather than the file being fetched. `--url-query` belongs here too -- it appends query data to the request, it does not name the request target.
|
|
1021
|
+
"--url-query",
|
|
1022
|
+
"--doh-url",
|
|
1023
|
+
"--preproxy",
|
|
1024
|
+
"--proxy1.0",
|
|
1025
|
+
"--socks4",
|
|
1026
|
+
"--socks4a",
|
|
1027
|
+
"--socks5",
|
|
1028
|
+
"--socks5-hostname",
|
|
1029
|
+
"--proxy-header",
|
|
1030
|
+
"--noproxy",
|
|
1031
|
+
"--dns-servers",
|
|
1032
|
+
"--aws-sigv4",
|
|
1033
|
+
"--proxy-cacert",
|
|
1034
|
+
"--proxy-cert",
|
|
1035
|
+
"--proxy-key",
|
|
1036
|
+
"--oauth2-bearer",
|
|
1037
|
+
"--hsts",
|
|
1038
|
+
"--alt-svc",
|
|
1039
|
+
"--etag-save",
|
|
1040
|
+
"--etag-compare",
|
|
1041
|
+
"--trace",
|
|
1042
|
+
"--trace-ascii",
|
|
1043
|
+
"--dump-header",
|
|
1044
|
+
"-D"
|
|
1045
|
+
]);
|
|
1046
|
+
function extractCurlUrl(cmd) {
|
|
1047
|
+
const words = splitCommandWords(cmd);
|
|
1048
|
+
let positional = null;
|
|
1049
|
+
for (let i = 0; i < words.length; i++) {
|
|
1050
|
+
const w = words[i];
|
|
1051
|
+
if (w === "--url") {
|
|
1052
|
+
const next = words[i + 1];
|
|
1053
|
+
if (next !== void 0 && /^https?:\/\//.test(next)) return next;
|
|
1054
|
+
i++;
|
|
1055
|
+
continue;
|
|
1056
|
+
}
|
|
1057
|
+
const eq = w.indexOf("=");
|
|
1058
|
+
if (w.startsWith("--") && eq > 0) {
|
|
1059
|
+
if (w.slice(0, eq) === "--url" && /^https?:\/\//.test(w.slice(eq + 1))) return w.slice(eq + 1);
|
|
1060
|
+
continue;
|
|
1061
|
+
}
|
|
1062
|
+
if (CURL_VALUE_FLAGS.has(w)) {
|
|
1063
|
+
i++;
|
|
1064
|
+
continue;
|
|
1065
|
+
}
|
|
1066
|
+
if (w.startsWith("-") && w !== "-") continue;
|
|
1067
|
+
if (/^https?:\/\//.test(w)) return w;
|
|
1068
|
+
positional ??= /https?:\/\/[^\s'")}`]+/.exec(w)?.[0] ?? null;
|
|
1069
|
+
}
|
|
1070
|
+
if (positional !== null) return positional;
|
|
1071
|
+
return null;
|
|
1072
|
+
}
|
|
1073
|
+
function extractTgSurgicalRead(cmd, cwd) {
|
|
1074
|
+
const m = /^(?:token-goat|tg)\s+(symbol|read|section|skill-body|skill-compact|map)(?:\s+(.*))?$/.exec(cmd);
|
|
1075
|
+
if (!m) return null;
|
|
1076
|
+
const sub = m[1];
|
|
1077
|
+
const rest = (m[2] ?? "").trim();
|
|
1078
|
+
if (sub === "map") {
|
|
1079
|
+
return { sub, spec: rest, filePath: null };
|
|
1080
|
+
}
|
|
1081
|
+
if (sub === "skill-body" || sub === "skill-compact") {
|
|
1082
|
+
const pathFlagMatch = /--path\s+(?:"([^"]+)"|'([^']+)'|(\S+))/.exec(rest);
|
|
1083
|
+
if (pathFlagMatch !== null) {
|
|
1084
|
+
const rawPathArg = pathFlagMatch[1] ?? pathFlagMatch[2] ?? pathFlagMatch[3] ?? "";
|
|
1085
|
+
const resolvedPath = resolveIndexPath(rawPathArg, cwd ?? process.cwd());
|
|
1086
|
+
const spec2 = rest.slice(0, pathFlagMatch.index) + "--path " + resolvedPath + rest.slice(pathFlagMatch.index + pathFlagMatch[0].length);
|
|
1087
|
+
return { sub, spec: spec2, filePath: null };
|
|
1088
|
+
}
|
|
1089
|
+
return { sub, spec: rest, filePath: null };
|
|
1090
|
+
}
|
|
1091
|
+
const specMatch = /^(?:"([^"]+)"|'([^']+)'|(\S+))/.exec(rest);
|
|
1092
|
+
const rawSpec = specMatch?.[1] ?? specMatch?.[2] ?? specMatch?.[3] ?? null;
|
|
1093
|
+
if (rawSpec === null) return null;
|
|
1094
|
+
let filePath = null;
|
|
1095
|
+
let spec = rawSpec;
|
|
1096
|
+
if (sub === "read" || sub === "section") {
|
|
1097
|
+
const rangeMatch = /^(.+)@(\d+)(?:-(\d+))?$/.exec(rawSpec);
|
|
1098
|
+
const rangeSuffix = rangeMatch !== null ? "@" + rangeMatch[2] + (rangeMatch[3] !== void 0 ? "-" + rangeMatch[3] : "") : "";
|
|
1099
|
+
const specWithoutRange = rangeMatch !== null ? rangeMatch[1] : rawSpec;
|
|
1100
|
+
const colonIdx = specWithoutRange.indexOf("::");
|
|
1101
|
+
const rawFilePath = colonIdx === -1 ? specWithoutRange : specWithoutRange.slice(0, colonIdx);
|
|
1102
|
+
filePath = resolveIndexPath(rawFilePath, cwd ?? process.cwd());
|
|
1103
|
+
spec = (colonIdx === -1 ? filePath : filePath + specWithoutRange.slice(colonIdx)) + rangeSuffix;
|
|
1104
|
+
}
|
|
1105
|
+
return { sub, spec, filePath };
|
|
1106
|
+
}
|
|
1107
|
+
function curlHasUnsafeFlags(cmd) {
|
|
1108
|
+
if (/-X\s+(?:POST|PUT|PATCH|DELETE|HEAD|OPTIONS)/i.test(cmd)) return true;
|
|
1109
|
+
if (/--request(?:\s+|=)(?:POST|PUT|PATCH|DELETE|HEAD|OPTIONS)/i.test(cmd)) return true;
|
|
1110
|
+
if (/(?:^|\s)(?:-d|--data(?:-raw|-binary|-urlencode)?|-F|--form)\b/.test(cmd)) return true;
|
|
1111
|
+
if (/(?:^|\s)(?:-u|--user)\b/.test(cmd)) return true;
|
|
1112
|
+
if (/(?:-H|--header)(?:\s+|=)['"]?Authorization/i.test(cmd)) return true;
|
|
1113
|
+
return false;
|
|
1114
|
+
}
|
|
1115
|
+
function isCurlGetCommand(cmd) {
|
|
1116
|
+
if (!/^curl\b/.test(cmd)) return false;
|
|
1117
|
+
return !curlHasUnsafeFlags(cmd);
|
|
1118
|
+
}
|
|
1119
|
+
function isReadOnlyGhApi(cmd) {
|
|
1120
|
+
if (!/^gh\s+api\b/.test(cmd)) return false;
|
|
1121
|
+
if (/\bgraphql\b/.test(cmd)) return false;
|
|
1122
|
+
if (/(?:-H|--header)(?:\s+|=)['"]?Authorization/i.test(cmd)) return false;
|
|
1123
|
+
if (/(?:-X|--method)(?:\s+|=)GET\b/i.test(cmd)) return true;
|
|
1124
|
+
if (/(?:-X|--method)(?:\s+|=)(?:POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\b/i.test(cmd)) return false;
|
|
1125
|
+
if (/\s(?:-f|-F|--field|--raw-field|--input)\b/.test(cmd)) return false;
|
|
1126
|
+
return true;
|
|
1127
|
+
}
|
|
1128
|
+
var GH_VIEW_BATCH_HINT_KEY = "gh-view-field-batch";
|
|
1129
|
+
var GH_VIEW_RE = /^gh\s+(pr|issue)\s+view\b(.*)$/i;
|
|
1130
|
+
function extractGhViewForBatchAdvisory(cmd) {
|
|
1131
|
+
const m = GH_VIEW_RE.exec(cmd);
|
|
1132
|
+
if (!m) return null;
|
|
1133
|
+
const sub = (m[1] ?? "").toLowerCase() === "issue" ? "issue" : "pr";
|
|
1134
|
+
const rest = m[2] ?? "";
|
|
1135
|
+
if (/--json\s+\S*,/.test(rest)) return null;
|
|
1136
|
+
const refMatch = /^\s+(?!-)(\S+)/.exec(rest);
|
|
1137
|
+
const ref = refMatch?.[1];
|
|
1138
|
+
return { sub, ref };
|
|
1139
|
+
}
|
|
1140
|
+
function buildGhViewBatchAdvisory(sub, ref) {
|
|
1141
|
+
const target = ref ? ref + " " : "";
|
|
1142
|
+
const fields = sub === "pr" ? "number,title,state,body,labels,reviews,files" : "number,title,state,body,labels,comments";
|
|
1143
|
+
const example = "gh " + sub + " view " + target + "--json " + fields;
|
|
1144
|
+
return "`gh " + sub + " view` field queries can be batched: fetch every field you need in one round-trip with `" + example + "` (slice it with `--jq`) instead of querying field-by-field across multiple calls.";
|
|
1145
|
+
}
|
|
1146
|
+
function extractCurlDownload(cmd) {
|
|
1147
|
+
if (!/^curl\b/.test(cmd)) return null;
|
|
1148
|
+
const outputMatch = /(?:^|\s)(?:-o|--output)\s+(?:"([^"]+)"|'([^']+)'|(\S+))/.exec(cmd);
|
|
1149
|
+
if (!outputMatch) return null;
|
|
1150
|
+
const outputPath = outputMatch[1] ?? outputMatch[2] ?? outputMatch[3];
|
|
1151
|
+
if (!outputPath) return null;
|
|
1152
|
+
if (curlHasUnsafeFlags(cmd)) return null;
|
|
1153
|
+
const url = extractCurlUrl(cmd);
|
|
1154
|
+
if (url === null) return null;
|
|
1155
|
+
return { url, outputPath };
|
|
1156
|
+
}
|
|
1157
|
+
function isTscCommand(cmd) {
|
|
1158
|
+
return /^\s*tsc(\s|$)/i.test(cmd);
|
|
1159
|
+
}
|
|
1160
|
+
function isDevServerCommand(cmd) {
|
|
1161
|
+
return /^\s*(vite\s+dev|next\s+dev|nuxt\s+dev)\b/i.test(cmd);
|
|
1162
|
+
}
|
|
1163
|
+
function buildRecallHint(cmd, outputId) {
|
|
1164
|
+
const cmdPreview = cmd.length > 60 ? cmd.slice(0, 57) + "..." : cmd;
|
|
1165
|
+
if (isTscCommand(cmd)) {
|
|
1166
|
+
return "Output from a prior `" + cmdPreview + "` run is cached. Use `token-goat bash-output " + outputId + ' --grep "error TS"` to filter TypeScript errors, or `--grep "Cannot find"` for missing module errors.';
|
|
1167
|
+
}
|
|
1168
|
+
if (isDevServerCommand(cmd)) {
|
|
1169
|
+
return "Dev server output cached (`" + cmdPreview + "`). Use `token-goat bash-output " + outputId + ' --tail 20` to see the latest output, or `--grep "error\\|warn"` to filter issues.';
|
|
1170
|
+
}
|
|
1171
|
+
return "Output from a prior `" + cmdPreview + "` run is cached. Use `token-goat bash-output " + outputId + "` (or `--tail 50`, `--grep ERROR`) to re-inspect it without re-running.";
|
|
1172
|
+
}
|
|
1173
|
+
function shellQuoteSingle(s) {
|
|
1174
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
1175
|
+
}
|
|
1176
|
+
function isCompressibleSingleCommand(cmd) {
|
|
1177
|
+
if (!cmd || cmd.length > 65536) return false;
|
|
1178
|
+
if (["$(", "`"].some((op) => cmd.includes(op))) return false;
|
|
1179
|
+
if (hasUnquotedOperator(cmd, ["&&", "||", "|", ";", "<", ">"])) return false;
|
|
1180
|
+
if (hasBareBackgroundOrNewline(cmd)) return false;
|
|
1181
|
+
return true;
|
|
1182
|
+
}
|
|
1183
|
+
function extractTerminalXmlParsing(cmd) {
|
|
1184
|
+
const trimmed = cmd.trim();
|
|
1185
|
+
if (!trimmed) return null;
|
|
1186
|
+
const selectXmlM = /\bSelect-Xml\b(?:\s+(?:-Path\s+)?["']?([^"'\s|;]+(?:\.xml|\.dtsx|\.ampkg|\.xaml))["']?)?/i.exec(trimmed);
|
|
1187
|
+
if (selectXmlM) {
|
|
1188
|
+
const rawPath = selectXmlM[1];
|
|
1189
|
+
const filePath = rawPath && !rawPath.startsWith("-") ? rawPath : void 0;
|
|
1190
|
+
return { filePath, toolOrScript: "Select-Xml" };
|
|
1191
|
+
}
|
|
1192
|
+
const psXmlCastM = /\[(?:xml|System\.Xml\.XmlDocument)\]/i.exec(trimmed);
|
|
1193
|
+
if (psXmlCastM) {
|
|
1194
|
+
const fileM = /["']([^"'\r\n]+\.(?:xml|dtsx|ampkg|xaml))["']/i.exec(trimmed) || /(?:Get-Content|gc|ReadAllText)\s+["']?([^"'\s|;)]+\.(?:xml|dtsx|ampkg|xaml))["']?/i.exec(trimmed);
|
|
1195
|
+
const filePath = fileM?.[1];
|
|
1196
|
+
return { filePath, toolOrScript: psXmlCastM[0] };
|
|
1197
|
+
}
|
|
1198
|
+
const inspectScriptM = /(?:powershell|pwsh|&|\.)?\s*["']?(\.?[/\\]?inspect_[^"'\s|;]+\.ps1)["']/i.exec(trimmed) || /\b(inspect_[^"'\s|;]+\.ps1)\b/i.exec(trimmed);
|
|
1199
|
+
if (inspectScriptM) {
|
|
1200
|
+
const fileM = /["']([^"'\r\n]+\.(?:xml|dtsx|ampkg|xaml))["']/i.exec(trimmed);
|
|
1201
|
+
const filePath = fileM?.[1];
|
|
1202
|
+
return { filePath, toolOrScript: inspectScriptM[1] ?? "inspect script" };
|
|
1203
|
+
}
|
|
1204
|
+
const pyXmlM = /\bpython(?:\d+(?:\.\d+)?)?(?:\.exe)?\b.*-c\s+["'].*?\b(xml\.etree|BeautifulSoup|minidom|lxml)\b/i.exec(trimmed);
|
|
1205
|
+
if (pyXmlM) {
|
|
1206
|
+
const fileM = /["']([^"'\r\n]+\.(?:xml|dtsx|ampkg|xaml))["']/i.exec(trimmed);
|
|
1207
|
+
const filePath = fileM?.[1];
|
|
1208
|
+
return { filePath, toolOrScript: `python ${pyXmlM[1]}` };
|
|
1209
|
+
}
|
|
1210
|
+
const cliXmlM = /\b(xmllint|xmlstarlet|xidel)\b/i.exec(trimmed);
|
|
1211
|
+
if (cliXmlM) {
|
|
1212
|
+
const fileM = /["']?([^"'\s|;]+\.(?:xml|dtsx|ampkg|xaml))["']?/i.exec(trimmed);
|
|
1213
|
+
const rawPath = fileM?.[1];
|
|
1214
|
+
const filePath = rawPath && !rawPath.startsWith("-") ? rawPath : void 0;
|
|
1215
|
+
return { filePath, toolOrScript: cliXmlM[1] };
|
|
1216
|
+
}
|
|
1217
|
+
return null;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
// src/tool_error_class.ts
|
|
1221
|
+
var UNKNOWN_REASON = "unclassified";
|
|
1222
|
+
var DENIAL_KIND_REASONS = {
|
|
1223
|
+
"permission-rule": "hook_deny",
|
|
1224
|
+
// 73 in the census: the user declined an approval prompt.
|
|
1225
|
+
"user-rejected": "user_rejected",
|
|
1226
|
+
// 1 in the census; kept because it is an enumerated harness value, not a text guess.
|
|
1227
|
+
"automode-blocked": "auto_mode_blocked"
|
|
1228
|
+
};
|
|
1229
|
+
var TG_DENY_RE = /\[tg\]|token-goat/;
|
|
1230
|
+
var CONTENT_DELIVERED_REASON = "tg_content_delivered";
|
|
1231
|
+
function tgDenyReason(text) {
|
|
1232
|
+
return DELIVERS_CONTENT_RE.test(text) ? CONTENT_DELIVERED_REASON : "tg_deny";
|
|
1233
|
+
}
|
|
1234
|
+
var HOOK_DENY_TEXT_RE = /^PreToolUse:\S+ hook error: /;
|
|
1235
|
+
var TEST_OR_BUILD_COMMAND_RE = /^(?:npm\s+(?:test|run\s+(?:test|build|lint|typecheck))|npx\s+(?:vitest|tsc|eslint|jest|playwright)|vitest|jest|pytest|tsc|eslint|cargo\s+(?:test|build|check|clippy)|go\s+(?:test|build|vet)|make|gradle|mvn|dotnet\s+(?:test|build)|ruff|mypy|python3?\s+-m\s+(?:pytest|unittest))\b/;
|
|
1236
|
+
var SEARCH_COMMAND_RE = /^(?:grep|egrep|fgrep|rg|findstr)\b/;
|
|
1237
|
+
function runsCommand(command, head) {
|
|
1238
|
+
return splitShellSegments(command).some((segment) => head.test(stripLeadingAssignments(segment)));
|
|
1239
|
+
}
|
|
1240
|
+
var EXPECTED_PATTERNS = [
|
|
1241
|
+
// 176: the Bash tool's own timeout, reported as SIGTERM's 143 plus this line.
|
|
1242
|
+
{ reason: "command_timeout", tools: ["Bash"], re: /^Exit code \d+\s+Command timed out after / },
|
|
1243
|
+
// 289: exit 1 with no output from a search command, which is how grep and rg report zero hits.
|
|
1244
|
+
{ reason: "search_no_match", tools: ["Bash"], re: /^Exit code 1\s*$/, command: SEARCH_COMMAND_RE },
|
|
1245
|
+
// 252: a test, build, lint or typecheck run reporting failures, which is the tool doing its job.
|
|
1246
|
+
{ reason: "test_or_build_failed", tools: ["Bash"], re: /^Exit code \d+/, command: TEST_OR_BUILD_COMMAND_RE },
|
|
1247
|
+
// 302: an unbalanced quote or bracket in the command itself.
|
|
1248
|
+
{ reason: "shell_syntax_error", tools: ["Bash"], re: /unexpected EOF while looking for matching|syntax error near unexpected token/ },
|
|
1249
|
+
// 29: a binary missing from PATH.
|
|
1250
|
+
{ reason: "command_not_found", tools: ["Bash"], re: /command not found/ },
|
|
1251
|
+
// 272: the harness refusing a bare or leading `sleep` and pointing at Monitor instead.
|
|
1252
|
+
{ reason: "sleep_blocked", tools: ["Bash"], re: /^Blocked: (?:sleep \d+ followed by|standalone sleep)/ },
|
|
1253
|
+
// 114: old_string absent from the file.
|
|
1254
|
+
{ reason: "edit_string_not_found", tools: ["Edit", "MultiEdit"], re: /^String to replace not found in file/ },
|
|
1255
|
+
// 37: old_string present more than once with replace_all false.
|
|
1256
|
+
{ reason: "edit_multiple_matches", tools: ["Edit", "MultiEdit"], re: /^Found \d+ matches of the string to replace/ },
|
|
1257
|
+
// 37: the harness's read-before-write guard.
|
|
1258
|
+
{ reason: "file_not_read_yet", tools: ["Edit", "MultiEdit", "Write"], re: /^File has not been read yet/ },
|
|
1259
|
+
// 16: the file changed on disk after the last read.
|
|
1260
|
+
{ reason: "file_modified_since_read", tools: ["Edit", "MultiEdit", "Write"], re: /^File has been modified since read/ },
|
|
1261
|
+
// 643 (578 Bash, 53 Read, 11 Grep, 1 Edit): a path that does not exist, in each tool's own wording.
|
|
1262
|
+
{ reason: "path_not_found", re: /No such file or directory|^File does not exist\.|^Path does not exist:/ },
|
|
1263
|
+
// 1,171: an interpreter raising: a Python traceback header, the `Node.js vN.N.N` line Node prints under every uncaught error, or a named error line (`SyntaxError: ...`). After path_not_found, so a missing file stays a missing file whichever language reported it.
|
|
1264
|
+
{ reason: "script_exception", tools: ["Bash"], re: /^(?:Traceback \(most recent call last\):|Node\.js v\d+\.\d+\.\d+\b|[A-Z]\w*(?:Error|Exception)(?: \[\w+\])?: )/m },
|
|
1265
|
+
// 14: a Read over the harness's size ceiling.
|
|
1266
|
+
{ reason: "file_too_large", tools: ["Read"], re: /^File content \([^)]*\) exceeds maximum allowed/ },
|
|
1267
|
+
// 85: arguments the tool's schema rejected before it ran.
|
|
1268
|
+
{ reason: "input_validation", re: /^InputValidationError/ },
|
|
1269
|
+
// 68: a StructuredOutput call that did not match the requested schema.
|
|
1270
|
+
{ reason: "schema_mismatch", tools: ["StructuredOutput"], re: /^Output does not match required schema/ },
|
|
1271
|
+
// 35: the harness's concurrent-subagent ceiling.
|
|
1272
|
+
{ reason: "subagent_limit", tools: ["Agent", "Task"], re: /^Concurrent subagent limit reached/ },
|
|
1273
|
+
// 37: a tool disabled for this session or a misspelled tool name.
|
|
1274
|
+
{ reason: "tool_unavailable", re: /^Error: No such tool available/ }
|
|
1275
|
+
];
|
|
1276
|
+
var EXPECTED_REASONS = [
|
|
1277
|
+
"interrupted",
|
|
1278
|
+
"tg_deny",
|
|
1279
|
+
CONTENT_DELIVERED_REASON,
|
|
1280
|
+
...new Set(Object.values(DENIAL_KIND_REASONS)),
|
|
1281
|
+
...EXPECTED_PATTERNS.map((p) => p.reason)
|
|
1282
|
+
];
|
|
1283
|
+
var TOOL_USE_ERROR_OPEN = "<tool_use_error>";
|
|
1284
|
+
var TOOL_USE_ERROR_CLOSE = "</tool_use_error>";
|
|
1285
|
+
function unwrapToolError(errorText) {
|
|
1286
|
+
let text = errorText.trim();
|
|
1287
|
+
if (text.startsWith(TOOL_USE_ERROR_OPEN)) text = text.slice(TOOL_USE_ERROR_OPEN.length);
|
|
1288
|
+
if (text.endsWith(TOOL_USE_ERROR_CLOSE)) text = text.slice(0, text.length - TOOL_USE_ERROR_CLOSE.length);
|
|
1289
|
+
return text.trim();
|
|
1290
|
+
}
|
|
1291
|
+
function expected(reason) {
|
|
1292
|
+
return { kind: "expected", reason };
|
|
1293
|
+
}
|
|
1294
|
+
function classifyToolError(tool, errorText, flags = {}) {
|
|
1295
|
+
if (flags.isInterrupt === true) return expected("interrupted");
|
|
1296
|
+
const text = unwrapToolError(errorText);
|
|
1297
|
+
const denial = flags.denialKind;
|
|
1298
|
+
if (denial !== void 0 && denial !== "") {
|
|
1299
|
+
const reason = DENIAL_KIND_REASONS[denial] ?? "hook_deny";
|
|
1300
|
+
return expected(reason === "hook_deny" && TG_DENY_RE.test(text) ? tgDenyReason(text) : reason);
|
|
1301
|
+
}
|
|
1302
|
+
if (HOOK_DENY_TEXT_RE.test(text)) return expected(TG_DENY_RE.test(text) ? tgDenyReason(text) : "hook_deny");
|
|
1303
|
+
for (const p of EXPECTED_PATTERNS) {
|
|
1304
|
+
if (p.tools !== void 0 && (tool === void 0 || !p.tools.includes(tool))) continue;
|
|
1305
|
+
if (!p.re.test(text)) continue;
|
|
1306
|
+
if (p.command !== void 0 && (flags.command === void 0 || !runsCommand(flags.command, p.command))) continue;
|
|
1307
|
+
return expected(p.reason);
|
|
1308
|
+
}
|
|
1309
|
+
return { kind: "unknown", reason: UNKNOWN_REASON };
|
|
1310
|
+
}
|
|
1311
|
+
var ERROR_PREFIX_CHARS = 60;
|
|
1312
|
+
var EXIT_CODE_HEADER_RE = /^Exit code (\d+)\s*/;
|
|
1313
|
+
function errorPrefix(errorText) {
|
|
1314
|
+
let text = unwrapToolError(errorText);
|
|
1315
|
+
let lead = "";
|
|
1316
|
+
const exit = EXIT_CODE_HEADER_RE.exec(text);
|
|
1317
|
+
if (exit !== null) {
|
|
1318
|
+
lead = `exit ${exit[1] ?? ""}: `;
|
|
1319
|
+
text = text.slice(exit[0].length);
|
|
1320
|
+
}
|
|
1321
|
+
const line = text.split("\n").find((l) => l.trim() !== "") ?? "";
|
|
1322
|
+
const masked = line.trim().replace(/\b[A-Za-z]:[\\/]\S*|~?\/[\w.~-]+(?:\/[\w.~-]*)+/g, "<path>").replace(/'[^'\n]*'|"[^"\n]*"|`[^`\n]*`/g, "<q>").replace(/\d+/g, "N");
|
|
1323
|
+
return safeSlice(`${lead}${masked === "" ? "(empty)" : masked}`, ERROR_PREFIX_CHARS);
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
export {
|
|
1327
|
+
sqlTableHint,
|
|
1328
|
+
surgicalHintFor,
|
|
1329
|
+
surgicalHintForConfigDoc,
|
|
1330
|
+
splitShellSegments,
|
|
1331
|
+
extractCatSourceFile,
|
|
1332
|
+
classifyCatPath,
|
|
1333
|
+
extractCatFile,
|
|
1334
|
+
extractCatFilesMulti,
|
|
1335
|
+
extractPowerShellWrappedGetContent,
|
|
1336
|
+
extractPowerShellFileMethodRead,
|
|
1337
|
+
extractRgSymbolSearch,
|
|
1338
|
+
extractCatJsonPipe,
|
|
1339
|
+
extractPowerShellJsonPipeline,
|
|
1340
|
+
extractWslCatFile,
|
|
1341
|
+
extractPythonFileRead,
|
|
1342
|
+
extractHeadFile,
|
|
1343
|
+
extractLineRangeRead,
|
|
1344
|
+
extractLineRangeReadsCompound,
|
|
1345
|
+
sedRangeHint,
|
|
1346
|
+
leadingLinesHint,
|
|
1347
|
+
findRangeOverlap,
|
|
1348
|
+
sedOverlapHint,
|
|
1349
|
+
extractNodeFileRead,
|
|
1350
|
+
extractTailFile,
|
|
1351
|
+
extractGetContentTail,
|
|
1352
|
+
extractGetContentSelectFirst,
|
|
1353
|
+
extractGetContentHead,
|
|
1354
|
+
taskOutputIsJsonlTranscript,
|
|
1355
|
+
extractTasksOutput,
|
|
1356
|
+
extractToolResultsFile,
|
|
1357
|
+
extractDirectoryListing,
|
|
1358
|
+
extractForLoopWcL,
|
|
1359
|
+
extractFindCommand,
|
|
1360
|
+
extractMarkdownHeadingGrep,
|
|
1361
|
+
extractRgStructuralSearch,
|
|
1362
|
+
extractGrepPipeChain,
|
|
1363
|
+
stripLeadingAssignments,
|
|
1364
|
+
commandOptsOutOfCompression,
|
|
1365
|
+
extractCurlUrl,
|
|
1366
|
+
extractTgSurgicalRead,
|
|
1367
|
+
isCurlGetCommand,
|
|
1368
|
+
isReadOnlyGhApi,
|
|
1369
|
+
GH_VIEW_BATCH_HINT_KEY,
|
|
1370
|
+
extractGhViewForBatchAdvisory,
|
|
1371
|
+
buildGhViewBatchAdvisory,
|
|
1372
|
+
extractCurlDownload,
|
|
1373
|
+
isTscCommand,
|
|
1374
|
+
buildRecallHint,
|
|
1375
|
+
shellQuoteSingle,
|
|
1376
|
+
isCompressibleSingleCommand,
|
|
1377
|
+
extractTerminalXmlParsing,
|
|
1378
|
+
CONTENT_DELIVERED_REASON,
|
|
1379
|
+
SEARCH_COMMAND_RE,
|
|
1380
|
+
classifyToolError,
|
|
1381
|
+
errorPrefix
|
|
1382
|
+
};
|