token-goat 2.6.33 → 2.6.35
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 +4 -0
- package/SECURITY.md +11 -10
- package/dist/{token-goat-chunk-FN6WLFIX.mjs → token-goat-chunk-D5IZEGN2.mjs} +817 -53
- package/dist/{token-goat-hook-chunk-HXHFLKEL.mjs → token-goat-chunk-DQIT5JIF.mjs} +7 -2
- package/dist/{token-goat-chunk-72R7M5H7.mjs → token-goat-chunk-DRAIYVUI.mjs} +16 -14
- package/dist/{token-goat-chunk-RPOXYJWK.mjs → token-goat-chunk-HW4VUKJ5.mjs} +77 -26
- package/dist/{token-goat-chunk-TDYTBCT4.mjs → token-goat-chunk-I2KCEGIP.mjs} +704 -138
- package/dist/{token-goat-chunk-WFRR2DBG.mjs → token-goat-chunk-K7W3P2TJ.mjs} +583 -343
- package/dist/{token-goat-chunk-OHQ7RBLL.mjs → token-goat-chunk-SIHYQCTM.mjs} +2 -2
- package/dist/{token-goat-chunk-77CX3MCN.mjs → token-goat-chunk-UIZVX3HN.mjs} +2 -2
- package/dist/{token-goat-chunk-L6FVTDPD.mjs → token-goat-chunk-Y2NH6DGZ.mjs} +564 -31
- package/dist/{token-goat-chunk-HPWPUBAK.mjs → token-goat-chunk-YF67EWRH.mjs} +5 -5
- package/dist/{token-goat-hook-chunk-E6AIPKNF.mjs → token-goat-hook-chunk-2O7P4Z6Q.mjs} +579 -339
- package/dist/{token-goat-hook-chunk-WBBHV6TZ.mjs → token-goat-hook-chunk-3AAH72SO.mjs} +77 -26
- package/dist/{token-goat-hook-chunk-QD2DX2U7.mjs → token-goat-hook-chunk-4POU6OHK.mjs} +564 -31
- package/dist/{token-goat-hook-chunk-ZT47M3IA.mjs → token-goat-hook-chunk-CRBMPT74.mjs} +2 -2
- package/dist/{token-goat-hook-chunk-26DLRODR.mjs → token-goat-hook-chunk-D6UJEFHX.mjs} +2 -2
- package/dist/{token-goat-hook-chunk-XBOWDNJB.mjs → token-goat-hook-chunk-J4HKWUWZ.mjs} +5 -5
- package/dist/{token-goat-chunk-RQQ4SY2P.mjs → token-goat-hook-chunk-NIPXIAPA.mjs} +7 -2
- package/dist/{token-goat-hook-chunk-VDXQXYJK.mjs → token-goat-hook-chunk-NXVT4F3S.mjs} +817 -53
- package/dist/{token-goat-hook-chunk-EKGDLI4D.mjs → token-goat-hook-chunk-VU6VGZBM.mjs} +16 -14
- package/dist/{token-goat-hook-chunk-UWV4MTYI.mjs → token-goat-hook-chunk-XM4W3WCO.mjs} +704 -138
- package/dist/token-goat-hook.mjs +5 -5
- package/dist/token-goat.core.mjs +5 -5
- package/package.json +11 -10
|
@@ -2,13 +2,15 @@ import { createRequire as __cjsRequire } from 'node:module';
|
|
|
2
2
|
const require = __cjsRequire(import.meta.url);
|
|
3
3
|
import {
|
|
4
4
|
canRunWrappedShell,
|
|
5
|
+
compressOutput,
|
|
5
6
|
dedupeConsecutive,
|
|
6
7
|
detectFromCommand,
|
|
8
|
+
filterByName,
|
|
7
9
|
hasBareBackgroundOrNewline,
|
|
8
10
|
isRewriteWorthwhile,
|
|
9
11
|
resolveMinNetSavingsBytes,
|
|
10
12
|
shlexSplit
|
|
11
|
-
} from "./token-goat-chunk-
|
|
13
|
+
} from "./token-goat-chunk-DQIT5JIF.mjs";
|
|
12
14
|
import {
|
|
13
15
|
BASH_OUTPUT_SUBDIR,
|
|
14
16
|
HOOK_EVENTS,
|
|
@@ -27,7 +29,7 @@ import {
|
|
|
27
29
|
storeBashOutput,
|
|
28
30
|
storeWebOutput,
|
|
29
31
|
summarizeOutputDelta
|
|
30
|
-
} from "./token-goat-chunk-
|
|
32
|
+
} from "./token-goat-chunk-SIHYQCTM.mjs";
|
|
31
33
|
import {
|
|
32
34
|
BODY_FIRST_TOOL_RESPONSE_KEYS,
|
|
33
35
|
OUTPUT_FIRST_TOOL_RESPONSE_KEYS,
|
|
@@ -117,11 +119,12 @@ import {
|
|
|
117
119
|
wasCliReadThisSession,
|
|
118
120
|
wasFileReadThisSession,
|
|
119
121
|
wasHintShown
|
|
120
|
-
} from "./token-goat-chunk-
|
|
122
|
+
} from "./token-goat-chunk-I2KCEGIP.mjs";
|
|
121
123
|
import {
|
|
122
124
|
VERSION,
|
|
123
125
|
detectHarness,
|
|
124
126
|
detectLanguage,
|
|
127
|
+
displaySafePath,
|
|
125
128
|
extractErrorMessage,
|
|
126
129
|
foldPath,
|
|
127
130
|
getDb,
|
|
@@ -135,7 +138,7 @@ import {
|
|
|
135
138
|
runGit,
|
|
136
139
|
shortFingerprint,
|
|
137
140
|
toKB
|
|
138
|
-
} from "./token-goat-chunk-
|
|
141
|
+
} from "./token-goat-chunk-K7W3P2TJ.mjs";
|
|
139
142
|
import "./token-goat-chunk-AO2QD2AG.mjs";
|
|
140
143
|
import "./token-goat-chunk-AEX54RUZ.mjs";
|
|
141
144
|
|
|
@@ -314,9 +317,9 @@ function postEditHandlerInner(event) {
|
|
|
314
317
|
} catch {
|
|
315
318
|
}
|
|
316
319
|
if (meetsSavingsFloor(editedSize)) {
|
|
317
|
-
const escapedPath = normalized.replace(/`/g, "\\`").replace(/"/g, '\\"');
|
|
320
|
+
const escapedPath = displaySafePath(normalized).replace(/`/g, "\\`").replace(/"/g, '\\"');
|
|
318
321
|
return contextOutput(
|
|
319
|
-
editedBasename + ' was edited. Use `token-goat section "' + escapedPath + '::HeadingName"` to re-read a specific section rather than the full file.'
|
|
322
|
+
displaySafePath(editedBasename) + ' was edited. Use `token-goat section "' + escapedPath + '::HeadingName"` to re-read a specific section rather than the full file.'
|
|
320
323
|
);
|
|
321
324
|
}
|
|
322
325
|
}
|
|
@@ -575,7 +578,7 @@ import crypto from "node:crypto";
|
|
|
575
578
|
var TRACKED_SKILL = "token-goat";
|
|
576
579
|
var MAX_COMMANDS_SHOWN = 8;
|
|
577
580
|
async function currentCommandNames() {
|
|
578
|
-
const { buildProgram } = await import("./token-goat-chunk-
|
|
581
|
+
const { buildProgram } = await import("./token-goat-chunk-YF67EWRH.mjs");
|
|
579
582
|
return flattenCommandNames(buildCommandManifest(buildProgram()));
|
|
580
583
|
}
|
|
581
584
|
async function recordSkillVersionSnapshot(sessionId, skillName) {
|
|
@@ -7054,6 +7057,10 @@ function stripCdPrefix(cmd) {
|
|
|
7054
7057
|
const stripped = cmd.replace(/^(?:cd\s+(?:"[^"]*"|'[^']*'|\S+)\s*&&\s*)+/, "");
|
|
7055
7058
|
return stripped.trim() || cmd;
|
|
7056
7059
|
}
|
|
7060
|
+
function stripTrailingStderrRedirect(cmd) {
|
|
7061
|
+
const stripped = cmd.replace(/\s*2>&1\s*$/, "");
|
|
7062
|
+
return stripped.trim() || cmd;
|
|
7063
|
+
}
|
|
7057
7064
|
function hasTestRunScopeOrBudget(cmd) {
|
|
7058
7065
|
const hasTimeout = /(?:^|\s)(?:timeout(?:\.exe)?\s+\S+|--(?:test)?timeout(?:=|\s)\S+)/i.test(cmd);
|
|
7059
7066
|
const hasSelector = /(?:\s::\S+|\s(?:-k|-t|-run|--(?:testNamePattern|last-failed|test|project))(?:=|\s)|\b(?:tests?|spec)\S*\.(?:[cm]?[jt]sx?|py|go|rs)\b|(?:^|\s)\.\/\S+)/i.test(cmd);
|
|
@@ -7145,6 +7152,182 @@ function isHeadMovingGitCommand(cmd) {
|
|
|
7145
7152
|
}
|
|
7146
7153
|
var ORIG_HEAD_ELIGIBLE_GIT_RE = /^\s*git\s+(?:pull|merge|rebase)\b/i;
|
|
7147
7154
|
var ORIG_HEAD_REFLOG_MSG_RE = /^(merge\s|rebase\s\(|pull\s)/i;
|
|
7155
|
+
var GIT_RESTORE_RE = /^\s*git\s+restore\b/i;
|
|
7156
|
+
var GIT_RESTORE_STAGED_ONLY_RE = /(?:^|\s)(?:--staged|-S)(?:\s|$)/;
|
|
7157
|
+
var GIT_STASH_APPLY_RE = /^\s*git\s+stash\s+(?:pop|apply)\b/i;
|
|
7158
|
+
var PATCH_APPLY_SEGMENT_RE = /^\s*(?:\S*[/\\])?(?:git\s+apply|patch)\b/i;
|
|
7159
|
+
var PATCH_APPLY_INSPECT_ONLY_RE = /(?:^|\s)--(?:check|stat|numstat|summary|dry-run)(?:\s|=|$)/i;
|
|
7160
|
+
function isFormatterWriteSegment(segment) {
|
|
7161
|
+
return /(?:^|\s|[/\\])(?:prettier|eslint)\b/i.test(segment) && /(?:^|\s)--(?:write|fix)(?:\s|$)/i.test(segment);
|
|
7162
|
+
}
|
|
7163
|
+
function splitShellSegments(cmd) {
|
|
7164
|
+
const segments = [];
|
|
7165
|
+
let cur = "";
|
|
7166
|
+
let quote = null;
|
|
7167
|
+
for (let i = 0; i < cmd.length; i++) {
|
|
7168
|
+
const ch = cmd[i];
|
|
7169
|
+
if (quote !== null) {
|
|
7170
|
+
cur += ch;
|
|
7171
|
+
if (ch === quote) quote = null;
|
|
7172
|
+
continue;
|
|
7173
|
+
}
|
|
7174
|
+
if (ch === '"' || ch === "'") {
|
|
7175
|
+
quote = ch;
|
|
7176
|
+
cur += ch;
|
|
7177
|
+
continue;
|
|
7178
|
+
}
|
|
7179
|
+
if (ch === "\\" && i + 1 < cmd.length) {
|
|
7180
|
+
cur += ch + cmd[i + 1];
|
|
7181
|
+
i++;
|
|
7182
|
+
continue;
|
|
7183
|
+
}
|
|
7184
|
+
if (ch === "|" || ch === ";" || ch === "\n" || ch === "&") {
|
|
7185
|
+
segments.push(cur);
|
|
7186
|
+
cur = "";
|
|
7187
|
+
if ((ch === "|" || ch === "&") && cmd[i + 1] === ch) i++;
|
|
7188
|
+
continue;
|
|
7189
|
+
}
|
|
7190
|
+
cur += ch;
|
|
7191
|
+
}
|
|
7192
|
+
segments.push(cur);
|
|
7193
|
+
return segments.map((s) => s.trim()).filter((s) => s.length > 0);
|
|
7194
|
+
}
|
|
7195
|
+
function safeShlexSplit(segment) {
|
|
7196
|
+
try {
|
|
7197
|
+
return shlexSplit(segment);
|
|
7198
|
+
} catch {
|
|
7199
|
+
return null;
|
|
7200
|
+
}
|
|
7201
|
+
}
|
|
7202
|
+
function segmentCommandIs(segment, name2) {
|
|
7203
|
+
return new RegExp("^\\s*(?:\\S*[/\\\\])?" + name2 + "(?:\\.exe)?\\b", "i").test(segment);
|
|
7204
|
+
}
|
|
7205
|
+
function extractSedInPlaceFiles(segment) {
|
|
7206
|
+
if (!segmentCommandIs(segment, "sed")) return [];
|
|
7207
|
+
const tokens = safeShlexSplit(segment);
|
|
7208
|
+
if (tokens === null) return [];
|
|
7209
|
+
const rest = tokens.slice(1);
|
|
7210
|
+
const inPlaceIdx = rest.findIndex((t) => t === "--in-place" || t.startsWith("--in-place=") || /^-[a-zA-Z]*i/.test(t));
|
|
7211
|
+
if (inPlaceIdx === -1) return [];
|
|
7212
|
+
const files = [];
|
|
7213
|
+
let scriptTaken = rest.some((t) => t === "-e" || t === "-f" || t.startsWith("--expression") || t.startsWith("--file"));
|
|
7214
|
+
for (let i = 0; i < rest.length; i++) {
|
|
7215
|
+
const t = rest[i];
|
|
7216
|
+
if (i === inPlaceIdx) {
|
|
7217
|
+
if (t === "-i" && rest[i + 1] === "") i++;
|
|
7218
|
+
continue;
|
|
7219
|
+
}
|
|
7220
|
+
if (t === "-e" || t === "-f" || t === "--expression" || t === "--file") {
|
|
7221
|
+
i++;
|
|
7222
|
+
continue;
|
|
7223
|
+
}
|
|
7224
|
+
if (t.length > 1 && t.startsWith("-")) continue;
|
|
7225
|
+
if (!scriptTaken) {
|
|
7226
|
+
scriptTaken = true;
|
|
7227
|
+
continue;
|
|
7228
|
+
}
|
|
7229
|
+
files.push(t);
|
|
7230
|
+
}
|
|
7231
|
+
return files;
|
|
7232
|
+
}
|
|
7233
|
+
function extractTeeFiles(segment) {
|
|
7234
|
+
if (!segmentCommandIs(segment, "tee")) return [];
|
|
7235
|
+
const tokens = safeShlexSplit(segment);
|
|
7236
|
+
if (tokens === null) return [];
|
|
7237
|
+
return tokens.slice(1).filter((t) => t.length > 0 && !t.startsWith("-"));
|
|
7238
|
+
}
|
|
7239
|
+
function extractRedirectTargets(segment) {
|
|
7240
|
+
const targets = [];
|
|
7241
|
+
const re = /(?:^|\s)[0-9]?>>?\s*("[^"]*"|'[^']*'|[^\s|&<>]+)/g;
|
|
7242
|
+
let m;
|
|
7243
|
+
while ((m = re.exec(segment)) !== null) {
|
|
7244
|
+
const raw = m[1];
|
|
7245
|
+
targets.push(raw.length >= 2 && (raw[0] === '"' || raw[0] === "'") ? raw.slice(1, -1) : raw);
|
|
7246
|
+
}
|
|
7247
|
+
return targets;
|
|
7248
|
+
}
|
|
7249
|
+
function extractGitRestorePathspecs(cmd) {
|
|
7250
|
+
const m = /^\s*git\s+restore\b(.*)$/i.exec(cmd);
|
|
7251
|
+
if (m === null) return [];
|
|
7252
|
+
const tokens = safeShlexSplit(m[1] ?? "");
|
|
7253
|
+
if (tokens === null) return [];
|
|
7254
|
+
const dashDash = tokens.indexOf("--");
|
|
7255
|
+
if (dashDash !== -1) return tokens.slice(dashDash + 1);
|
|
7256
|
+
const specs = [];
|
|
7257
|
+
let skipNext = false;
|
|
7258
|
+
for (const t of tokens) {
|
|
7259
|
+
if (skipNext) {
|
|
7260
|
+
skipNext = false;
|
|
7261
|
+
continue;
|
|
7262
|
+
}
|
|
7263
|
+
if (t === "-s" || t === "--source") {
|
|
7264
|
+
skipNext = true;
|
|
7265
|
+
continue;
|
|
7266
|
+
}
|
|
7267
|
+
if (t.startsWith("-")) continue;
|
|
7268
|
+
specs.push(t);
|
|
7269
|
+
}
|
|
7270
|
+
return specs;
|
|
7271
|
+
}
|
|
7272
|
+
function gitRepoRoot(gitDir) {
|
|
7273
|
+
const toplevel = runGit(["rev-parse", "--show-toplevel"], { cwd: gitDir, timeoutMs: 5e3 });
|
|
7274
|
+
return toplevel.exitCode === 0 && toplevel.stdout.trim() !== "" ? toplevel.stdout.trim() : gitDir;
|
|
7275
|
+
}
|
|
7276
|
+
function expandGitPathspecs(specs, gitDir) {
|
|
7277
|
+
if (specs.length === 0) return [];
|
|
7278
|
+
const listed = runGit(["ls-files", "--full-name", "--", ...specs], { cwd: gitDir, timeoutMs: 5e3 });
|
|
7279
|
+
if (listed.exitCode !== 0) return [];
|
|
7280
|
+
const repoRoot = gitRepoRoot(gitDir);
|
|
7281
|
+
return listed.stdout.split("\n").map((l) => l.trim()).filter((l) => l.length > 0).map((rel) => resolveIndexPath(rel, repoRoot));
|
|
7282
|
+
}
|
|
7283
|
+
function workingTreeStatusPaths(gitDir) {
|
|
7284
|
+
const status = runGit(["status", "--porcelain"], { cwd: gitDir, timeoutMs: 5e3 });
|
|
7285
|
+
if (status.exitCode !== 0) return [];
|
|
7286
|
+
const repoRoot = gitRepoRoot(gitDir);
|
|
7287
|
+
const out = [];
|
|
7288
|
+
for (const line of status.stdout.split("\n")) {
|
|
7289
|
+
if (line.length < 4) continue;
|
|
7290
|
+
let rel = line.slice(3).trim();
|
|
7291
|
+
if (rel.length === 0) continue;
|
|
7292
|
+
const arrow = rel.lastIndexOf(" -> ");
|
|
7293
|
+
if (arrow !== -1) rel = rel.slice(arrow + 4);
|
|
7294
|
+
if (rel.startsWith('"')) {
|
|
7295
|
+
try {
|
|
7296
|
+
rel = JSON.parse(rel);
|
|
7297
|
+
} catch {
|
|
7298
|
+
continue;
|
|
7299
|
+
}
|
|
7300
|
+
}
|
|
7301
|
+
out.push(resolveIndexPath(rel, repoRoot));
|
|
7302
|
+
}
|
|
7303
|
+
return out;
|
|
7304
|
+
}
|
|
7305
|
+
function enqueueRewrittenPath(absPath) {
|
|
7306
|
+
if (/(?:^|[/\\])(?:NUL|nul)$/.test(absPath) || absPath.replace(/\\/g, "/").endsWith("/dev/null")) return;
|
|
7307
|
+
try {
|
|
7308
|
+
if (!statSync3(absPath).isFile()) return;
|
|
7309
|
+
} catch {
|
|
7310
|
+
return;
|
|
7311
|
+
}
|
|
7312
|
+
enqueueDirtyPathSafe(absPath, { alreadyResolved: true });
|
|
7313
|
+
}
|
|
7314
|
+
function enqueueNonHeadMovingRewrites(cmd, rawCmd, cwd) {
|
|
7315
|
+
const atCwd = (f) => resolveIndexPath(resolveCdHintPath(rawCmd, f, cwd), cwd);
|
|
7316
|
+
const paths = [];
|
|
7317
|
+
let needsStatusSweep = GIT_STASH_APPLY_RE.test(cmd);
|
|
7318
|
+
if (GIT_RESTORE_RE.test(cmd) && !GIT_RESTORE_STAGED_ONLY_RE.test(cmd)) {
|
|
7319
|
+
paths.push(...expandGitPathspecs(extractGitRestorePathspecs(cmd), cwd));
|
|
7320
|
+
}
|
|
7321
|
+
for (const segment of splitShellSegments(cmd)) {
|
|
7322
|
+
if (PATCH_APPLY_SEGMENT_RE.test(segment) && !PATCH_APPLY_INSPECT_ONLY_RE.test(segment)) needsStatusSweep = true;
|
|
7323
|
+
if (isFormatterWriteSegment(segment)) needsStatusSweep = true;
|
|
7324
|
+
for (const f of extractSedInPlaceFiles(segment)) paths.push(atCwd(f));
|
|
7325
|
+
for (const f of extractTeeFiles(segment)) paths.push(atCwd(f));
|
|
7326
|
+
for (const f of extractRedirectTargets(segment)) paths.push(atCwd(f));
|
|
7327
|
+
}
|
|
7328
|
+
if (needsStatusSweep) paths.push(...workingTreeStatusPaths(cwd));
|
|
7329
|
+
for (const p of paths) enqueueRewrittenPath(p);
|
|
7330
|
+
}
|
|
7148
7331
|
function isTempPath(fp) {
|
|
7149
7332
|
const norm = fp.replace(/\\/g, "/");
|
|
7150
7333
|
return /^\/tmp\//i.test(norm) || /\/var\/folders\//i.test(norm) || /AppData\/Local\/Temp\//i.test(norm) || norm.startsWith("/c/Users/") && norm.includes("/AppData/Local/Temp/") || isUnderSystemTemp(fp);
|
|
@@ -7259,9 +7442,126 @@ function extractWslCatFile(cmd) {
|
|
|
7259
7442
|
if (flags === null) return null;
|
|
7260
7443
|
return { filePath, ...flags };
|
|
7261
7444
|
}
|
|
7445
|
+
function pythonScanText(text) {
|
|
7446
|
+
const dashC = /^python3?\s+-c\s*(['"])([\s\S]*)\1\s*$/.exec(text);
|
|
7447
|
+
if (dashC) {
|
|
7448
|
+
const source = dashC[2] ?? "";
|
|
7449
|
+
return { source, masked: maskPythonStrings(source) };
|
|
7450
|
+
}
|
|
7451
|
+
if (/^python3?\b/.test(text)) return { source: text, masked: text };
|
|
7452
|
+
return { source: text, masked: maskPythonStrings(text) };
|
|
7453
|
+
}
|
|
7454
|
+
function maskPythonStrings(text) {
|
|
7455
|
+
const out = text.split("");
|
|
7456
|
+
let i = 0;
|
|
7457
|
+
while (i < text.length) {
|
|
7458
|
+
const ch = text[i];
|
|
7459
|
+
if (ch !== "'" && ch !== '"') {
|
|
7460
|
+
i += 1;
|
|
7461
|
+
continue;
|
|
7462
|
+
}
|
|
7463
|
+
const triple = text.slice(i, i + 3);
|
|
7464
|
+
const delim = triple === ch + ch + ch ? triple : ch;
|
|
7465
|
+
let j = i + delim.length;
|
|
7466
|
+
while (j < text.length) {
|
|
7467
|
+
if (text[j] === "\\") {
|
|
7468
|
+
j += 2;
|
|
7469
|
+
continue;
|
|
7470
|
+
}
|
|
7471
|
+
if (text.slice(j, j + delim.length) === delim) break;
|
|
7472
|
+
out[j] = " ";
|
|
7473
|
+
j += 1;
|
|
7474
|
+
}
|
|
7475
|
+
i = j + delim.length;
|
|
7476
|
+
}
|
|
7477
|
+
return out.join("");
|
|
7478
|
+
}
|
|
7479
|
+
function pythonOpenCalls(text) {
|
|
7480
|
+
const { source, masked } = pythonScanText(text);
|
|
7481
|
+
const calls = [];
|
|
7482
|
+
for (const opener of masked.matchAll(/\bopen\s*\(/g)) {
|
|
7483
|
+
const start = (opener.index ?? 0) + opener[0].length;
|
|
7484
|
+
const args = [];
|
|
7485
|
+
let current = "";
|
|
7486
|
+
let depth = 0;
|
|
7487
|
+
let quote = "";
|
|
7488
|
+
let closed = false;
|
|
7489
|
+
for (let i = start; i < source.length; i++) {
|
|
7490
|
+
const ch = source[i] ?? "";
|
|
7491
|
+
if (quote !== "") {
|
|
7492
|
+
if (ch === "\\") {
|
|
7493
|
+
current += ch + (source[i + 1] ?? "");
|
|
7494
|
+
i += 1;
|
|
7495
|
+
continue;
|
|
7496
|
+
}
|
|
7497
|
+
if (ch === quote) quote = "";
|
|
7498
|
+
current += ch;
|
|
7499
|
+
continue;
|
|
7500
|
+
}
|
|
7501
|
+
if (ch === "'" || ch === '"') {
|
|
7502
|
+
quote = ch;
|
|
7503
|
+
current += ch;
|
|
7504
|
+
continue;
|
|
7505
|
+
}
|
|
7506
|
+
if (ch === "(" || ch === "[" || ch === "{") {
|
|
7507
|
+
depth += 1;
|
|
7508
|
+
current += ch;
|
|
7509
|
+
continue;
|
|
7510
|
+
}
|
|
7511
|
+
if (ch === ")" && depth === 0) {
|
|
7512
|
+
args.push(current);
|
|
7513
|
+
closed = true;
|
|
7514
|
+
break;
|
|
7515
|
+
}
|
|
7516
|
+
if (ch === ")" || ch === "]" || ch === "}") {
|
|
7517
|
+
depth -= 1;
|
|
7518
|
+
current += ch;
|
|
7519
|
+
continue;
|
|
7520
|
+
}
|
|
7521
|
+
if (ch === "," && depth === 0) {
|
|
7522
|
+
args.push(current);
|
|
7523
|
+
current = "";
|
|
7524
|
+
continue;
|
|
7525
|
+
}
|
|
7526
|
+
current += ch;
|
|
7527
|
+
}
|
|
7528
|
+
if (!closed) continue;
|
|
7529
|
+
const literalOf = (arg) => {
|
|
7530
|
+
const m = /^\s*r?(['"])([^'"]*)\1\s*$/.exec(arg);
|
|
7531
|
+
return m?.[2] ?? null;
|
|
7532
|
+
};
|
|
7533
|
+
const keyword = args.find((a) => /^\s*mode\s*=/.test(a));
|
|
7534
|
+
const positional = args[1] !== void 0 && /^\s*[A-Za-z_]\w*\s*=[^=]/.test(args[1]) ? void 0 : args[1];
|
|
7535
|
+
const modeArg = keyword !== void 0 ? keyword.replace(/^\s*mode\s*=/, "") : positional;
|
|
7536
|
+
calls.push({
|
|
7537
|
+
pathLiteral: args.length > 0 ? literalOf(args[0] ?? "") : null,
|
|
7538
|
+
hasMode: modeArg !== void 0,
|
|
7539
|
+
modeLiteral: modeArg === void 0 ? null : literalOf(modeArg)
|
|
7540
|
+
});
|
|
7541
|
+
}
|
|
7542
|
+
return calls;
|
|
7543
|
+
}
|
|
7544
|
+
function pythonOpenWritesAFile(text) {
|
|
7545
|
+
return pythonOpenCalls(text).some(
|
|
7546
|
+
(call) => call.hasMode && (call.modeLiteral === null || /[wax+]/.test(call.modeLiteral))
|
|
7547
|
+
);
|
|
7548
|
+
}
|
|
7549
|
+
function pythonWritesThroughFileObject(text) {
|
|
7550
|
+
const { masked } = pythonScanText(text);
|
|
7551
|
+
for (const call of masked.matchAll(/\.write(?:lines)?\s*\(/g)) {
|
|
7552
|
+
const receiver = masked.slice(0, call.index ?? 0);
|
|
7553
|
+
if (/(?:^|[^\w.])(?:sys\s*\.\s*)?(?:stdout|stderr|stdin)(?:\s*\.\s*buffer)?$/.test(receiver)) continue;
|
|
7554
|
+
return true;
|
|
7555
|
+
}
|
|
7556
|
+
return false;
|
|
7557
|
+
}
|
|
7558
|
+
function pythonOpenPathsAreAllLiteral(text) {
|
|
7559
|
+
const calls = pythonOpenCalls(text);
|
|
7560
|
+
return calls.length > 0 && calls.every((call) => call.pathLiteral !== null);
|
|
7561
|
+
}
|
|
7262
7562
|
function extractPythonFileRead(cmd) {
|
|
7263
7563
|
if (!/^python3?\b/.test(cmd)) return null;
|
|
7264
|
-
if (
|
|
7564
|
+
if (pythonOpenWritesAFile(cmd) || pythonWritesThroughFileObject(cmd)) return null;
|
|
7265
7565
|
const outputOpen = /open\s*\(\s*r?['"]([^'"]+\.output)['"]/i.exec(cmd);
|
|
7266
7566
|
if (outputOpen?.[1]) {
|
|
7267
7567
|
const filePath = outputOpen[1];
|
|
@@ -7272,7 +7572,7 @@ function extractPythonFileRead(cmd) {
|
|
|
7272
7572
|
const heredocMatch = /^python3?\s+-\s+<<\s*'?(\w+)'?\s*\n([\s\S]*?)\n\1\s*$/.exec(cmd);
|
|
7273
7573
|
if (heredocMatch) {
|
|
7274
7574
|
const body = heredocMatch[2] ?? "";
|
|
7275
|
-
if (
|
|
7575
|
+
if (pythonOpenWritesAFile(body) || pythonWritesThroughFileObject(body)) return null;
|
|
7276
7576
|
const heredocOpen = /open\s*\(\s*r?['"]([^'"]+\.(?:java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj|md|mdx|rst|txt|json|yaml|yml|toml|xml|conf|cfg|ini|properties))['"]/i.exec(body);
|
|
7277
7577
|
if (heredocOpen?.[1]) {
|
|
7278
7578
|
const filePath = heredocOpen[1];
|
|
@@ -7280,7 +7580,7 @@ function extractPythonFileRead(cmd) {
|
|
|
7280
7580
|
const isDoc = /\.(?:md|mdx|rst|txt)$/i.test(filePath);
|
|
7281
7581
|
return { filePath, isDoc, isTranscript: false };
|
|
7282
7582
|
}
|
|
7283
|
-
if (/open\s*\(/.test(body)) {
|
|
7583
|
+
if (/open\s*\(/.test(body) && !pythonOpenPathsAreAllLiteral(body)) {
|
|
7284
7584
|
const literal2 = /['"]([^'"]+\.(?:java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj|md|mdx|rst|txt|json|yaml|yml|toml|xml|conf|cfg|ini|properties))['"]/i.exec(body);
|
|
7285
7585
|
if (literal2?.[1]) {
|
|
7286
7586
|
const filePath = literal2[1];
|
|
@@ -7301,7 +7601,7 @@ function extractPythonFileRead(cmd) {
|
|
|
7301
7601
|
const isDoc = /\.(?:md|mdx|rst|txt)$/i.test(filePath);
|
|
7302
7602
|
return { filePath, isDoc, isTranscript: false };
|
|
7303
7603
|
}
|
|
7304
|
-
if (/open\s*\(/.test(cmd)) {
|
|
7604
|
+
if (/open\s*\(/.test(cmd) && !pythonOpenPathsAreAllLiteral(cmd)) {
|
|
7305
7605
|
const literal2 = /['"]([^'"]+\.(?:java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj|md|mdx|rst|txt|json|yaml|yml|toml|xml|conf|cfg|ini|properties))['"]/i.exec(cmd);
|
|
7306
7606
|
if (literal2) {
|
|
7307
7607
|
const filePath = literal2[1] ?? "";
|
|
@@ -7348,6 +7648,30 @@ function extractSedRange(cmd) {
|
|
|
7348
7648
|
if (isTempPath(filePath)) return null;
|
|
7349
7649
|
return { filePath, ranges };
|
|
7350
7650
|
}
|
|
7651
|
+
function extractAwkRange(cmd) {
|
|
7652
|
+
const m = /^awk\s+(?:'([^']+)'|"([^"]+)")\s+(?:"([^"]+)"|'([^']+)'|(\S+))(?:\s+2>\/dev\/null)?\s*$/.exec(cmd);
|
|
7653
|
+
if (!m) return null;
|
|
7654
|
+
const program = m[1] ?? m[2];
|
|
7655
|
+
if (program === void 0) return null;
|
|
7656
|
+
const cmp = /^\s*NR\s*>=\s*(\d+)\s*&&\s*NR\s*<=\s*(\d+)\s*$/.exec(program);
|
|
7657
|
+
const rng = /^\s*NR\s*==\s*(\d+)\s*,\s*NR\s*==\s*(\d+)\s*$/.exec(program);
|
|
7658
|
+
const hit = cmp ?? rng;
|
|
7659
|
+
if (!hit) return null;
|
|
7660
|
+
const start = parseInt(hit[1], 10);
|
|
7661
|
+
const end = parseInt(hit[2], 10);
|
|
7662
|
+
if (start < 1 || end < start) return null;
|
|
7663
|
+
const filePath = m[3] ?? m[4] ?? m[5];
|
|
7664
|
+
if (filePath === void 0) return null;
|
|
7665
|
+
if (isTempPath(filePath)) return null;
|
|
7666
|
+
return { filePath, ranges: [[start, end]] };
|
|
7667
|
+
}
|
|
7668
|
+
function extractLineRangeRead(cmd) {
|
|
7669
|
+
const sed = extractSedRange(cmd);
|
|
7670
|
+
if (sed !== null) return { ...sed, tool: "sed" };
|
|
7671
|
+
const awk = extractAwkRange(cmd);
|
|
7672
|
+
if (awk !== null) return { ...awk, tool: "awk" };
|
|
7673
|
+
return null;
|
|
7674
|
+
}
|
|
7351
7675
|
var SYMBOL_BEARING_LANGUAGES = /* @__PURE__ */ new Set([
|
|
7352
7676
|
"python",
|
|
7353
7677
|
"typescript",
|
|
@@ -7378,11 +7702,11 @@ var SYMBOL_BEARING_LANGUAGES = /* @__PURE__ */ new Set([
|
|
|
7378
7702
|
"salesforce_metadata",
|
|
7379
7703
|
"salesforce_markup"
|
|
7380
7704
|
]);
|
|
7381
|
-
function sedRangeHint(filePath, ranges) {
|
|
7705
|
+
function sedRangeHint(filePath, ranges, tool) {
|
|
7382
7706
|
const lang = detectLanguage(filePath);
|
|
7383
7707
|
const rangeReads = ranges.map(([s, e]) => '`token-goat read "' + filePath + "@" + s + "-" + e + '"`');
|
|
7384
7708
|
const allReads = rangeReads.length === 2 ? rangeReads.join(" and ") : rangeReads.length >= 3 ? rangeReads.slice(0, -1).join(", ") + ", and " + rangeReads[rangeReads.length - 1] : rangeReads[0];
|
|
7385
|
-
const prefix = "`sed -n` line-range reads bypass read hooks. ";
|
|
7709
|
+
const prefix = "`" + (tool === "awk" ? "awk" : "sed -n") + "` line-range reads bypass read hooks. ";
|
|
7386
7710
|
if (lang === "markdown") {
|
|
7387
7711
|
return prefix + 'For Markdown, `token-goat section "' + filePath + '::<heading>"` extracts a whole section by name (robust to line shifts); or ' + allReads + " for exactly those lines.";
|
|
7388
7712
|
}
|
|
@@ -7407,7 +7731,7 @@ function findRangeOverlap(prior, start, end) {
|
|
|
7407
7731
|
return best;
|
|
7408
7732
|
}
|
|
7409
7733
|
function sedOverlapHint(filePath, prior, start, end) {
|
|
7410
|
-
const base = "You already read lines " + prior[0] + "-" + prior[1] + " of " + filePath + " via an earlier
|
|
7734
|
+
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. ";
|
|
7411
7735
|
const segments = [];
|
|
7412
7736
|
if (start < prior[0]) segments.push([start, Math.min(end, prior[0] - 1)]);
|
|
7413
7737
|
if (end > prior[1]) segments.push([Math.max(start, prior[1] + 1), end]);
|
|
@@ -7583,12 +7907,151 @@ function extractRgStructuralSearch(cmd) {
|
|
|
7583
7907
|
function extractGrepPipeChain(cmd) {
|
|
7584
7908
|
return /^(?:rg|grep)\b.*\|\s*(?:rg|grep)\b/.test(cmd);
|
|
7585
7909
|
}
|
|
7910
|
+
function splitCommandWords(cmd) {
|
|
7911
|
+
const words = [];
|
|
7912
|
+
let cur = "";
|
|
7913
|
+
let quote = null;
|
|
7914
|
+
let started = false;
|
|
7915
|
+
for (let i = 0; i < cmd.length; i++) {
|
|
7916
|
+
const ch = cmd[i];
|
|
7917
|
+
if (quote !== null) {
|
|
7918
|
+
if (ch === "\\" && quote === '"' && i + 1 < cmd.length) {
|
|
7919
|
+
cur += cmd[i + 1];
|
|
7920
|
+
i++;
|
|
7921
|
+
continue;
|
|
7922
|
+
}
|
|
7923
|
+
if (ch === quote) quote = null;
|
|
7924
|
+
else cur += ch;
|
|
7925
|
+
continue;
|
|
7926
|
+
}
|
|
7927
|
+
if (ch === "\\" && i + 1 < cmd.length) {
|
|
7928
|
+
cur += cmd[i + 1];
|
|
7929
|
+
i++;
|
|
7930
|
+
started = true;
|
|
7931
|
+
continue;
|
|
7932
|
+
}
|
|
7933
|
+
if (ch === '"' || ch === "'") {
|
|
7934
|
+
quote = ch;
|
|
7935
|
+
started = true;
|
|
7936
|
+
continue;
|
|
7937
|
+
}
|
|
7938
|
+
if (/\s/.test(ch)) {
|
|
7939
|
+
if (started) words.push(cur);
|
|
7940
|
+
cur = "";
|
|
7941
|
+
started = false;
|
|
7942
|
+
continue;
|
|
7943
|
+
}
|
|
7944
|
+
cur += ch;
|
|
7945
|
+
started = true;
|
|
7946
|
+
}
|
|
7947
|
+
if (started) words.push(cur);
|
|
7948
|
+
return words;
|
|
7949
|
+
}
|
|
7950
|
+
var CURL_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
7951
|
+
"-H",
|
|
7952
|
+
"--header",
|
|
7953
|
+
"-A",
|
|
7954
|
+
"--user-agent",
|
|
7955
|
+
"-e",
|
|
7956
|
+
"--referer",
|
|
7957
|
+
"-b",
|
|
7958
|
+
"--cookie",
|
|
7959
|
+
"-c",
|
|
7960
|
+
"--cookie-jar",
|
|
7961
|
+
"-o",
|
|
7962
|
+
"--output",
|
|
7963
|
+
"--output-dir",
|
|
7964
|
+
"-u",
|
|
7965
|
+
"--user",
|
|
7966
|
+
"-U",
|
|
7967
|
+
"--proxy-user",
|
|
7968
|
+
"-x",
|
|
7969
|
+
"--proxy",
|
|
7970
|
+
"-X",
|
|
7971
|
+
"--request",
|
|
7972
|
+
"-d",
|
|
7973
|
+
"--data",
|
|
7974
|
+
"--data-raw",
|
|
7975
|
+
"--data-binary",
|
|
7976
|
+
"--data-urlencode",
|
|
7977
|
+
"-F",
|
|
7978
|
+
"--form",
|
|
7979
|
+
"-T",
|
|
7980
|
+
"--upload-file",
|
|
7981
|
+
"-K",
|
|
7982
|
+
"--config",
|
|
7983
|
+
"-w",
|
|
7984
|
+
"--write-out",
|
|
7985
|
+
"-m",
|
|
7986
|
+
"--max-time",
|
|
7987
|
+
"--connect-to",
|
|
7988
|
+
"--resolve",
|
|
7989
|
+
"--retry",
|
|
7990
|
+
"--cacert",
|
|
7991
|
+
"-E",
|
|
7992
|
+
"--cert",
|
|
7993
|
+
"--key",
|
|
7994
|
+
"--range",
|
|
7995
|
+
"-r",
|
|
7996
|
+
"--interface",
|
|
7997
|
+
// Options whose own value is a URL or a host. Leaving these out was the same defect as reading
|
|
7998
|
+
// the first URL in the string: `curl --doh-url https://doh/ -o f https://target` answered with
|
|
7999
|
+
// the DoH resolver rather than the file being fetched. `--url-query` belongs here too -- it
|
|
8000
|
+
// appends query data to the request, it does not name the request target.
|
|
8001
|
+
"--url-query",
|
|
8002
|
+
"--doh-url",
|
|
8003
|
+
"--preproxy",
|
|
8004
|
+
"--proxy1.0",
|
|
8005
|
+
"--socks4",
|
|
8006
|
+
"--socks4a",
|
|
8007
|
+
"--socks5",
|
|
8008
|
+
"--socks5-hostname",
|
|
8009
|
+
"--proxy-header",
|
|
8010
|
+
"--noproxy",
|
|
8011
|
+
"--dns-servers",
|
|
8012
|
+
"--aws-sigv4",
|
|
8013
|
+
"--proxy-cacert",
|
|
8014
|
+
"--proxy-cert",
|
|
8015
|
+
"--proxy-key",
|
|
8016
|
+
"--oauth2-bearer",
|
|
8017
|
+
"--hsts",
|
|
8018
|
+
"--alt-svc",
|
|
8019
|
+
"--etag-save",
|
|
8020
|
+
"--etag-compare",
|
|
8021
|
+
"--trace",
|
|
8022
|
+
"--trace-ascii",
|
|
8023
|
+
"--dump-header",
|
|
8024
|
+
"-D"
|
|
8025
|
+
]);
|
|
7586
8026
|
function extractCurlUrl(cmd) {
|
|
7587
|
-
const
|
|
7588
|
-
|
|
8027
|
+
const words = splitCommandWords(cmd);
|
|
8028
|
+
let positional = null;
|
|
8029
|
+
for (let i = 0; i < words.length; i++) {
|
|
8030
|
+
const w = words[i];
|
|
8031
|
+
if (w === "--url") {
|
|
8032
|
+
const next = words[i + 1];
|
|
8033
|
+
if (next !== void 0 && /^https?:\/\//.test(next)) return next;
|
|
8034
|
+
i++;
|
|
8035
|
+
continue;
|
|
8036
|
+
}
|
|
8037
|
+
const eq = w.indexOf("=");
|
|
8038
|
+
if (w.startsWith("--") && eq > 0) {
|
|
8039
|
+
if (w.slice(0, eq) === "--url" && /^https?:\/\//.test(w.slice(eq + 1))) return w.slice(eq + 1);
|
|
8040
|
+
continue;
|
|
8041
|
+
}
|
|
8042
|
+
if (CURL_VALUE_FLAGS.has(w)) {
|
|
8043
|
+
i++;
|
|
8044
|
+
continue;
|
|
8045
|
+
}
|
|
8046
|
+
if (w.startsWith("-") && w !== "-") continue;
|
|
8047
|
+
if (/^https?:\/\//.test(w)) return w;
|
|
8048
|
+
positional ??= /https?:\/\/[^\s'")}`]+/.exec(w)?.[0] ?? null;
|
|
8049
|
+
}
|
|
8050
|
+
if (positional !== null) return positional;
|
|
8051
|
+
return null;
|
|
7589
8052
|
}
|
|
7590
8053
|
function extractTgSurgicalRead(cmd, cwd) {
|
|
7591
|
-
const m = /^token-goat\s+(symbol|read|section|skill-body|skill-compact|map)(?:\s+(.*))?$/.exec(cmd);
|
|
8054
|
+
const m = /^(?:token-goat|tg)\s+(symbol|read|section|skill-body|skill-compact|map)(?:\s+(.*))?$/.exec(cmd);
|
|
7592
8055
|
if (!m) return null;
|
|
7593
8056
|
const sub = m[1];
|
|
7594
8057
|
const rest = (m[2] ?? "").trim();
|
|
@@ -7667,9 +8130,9 @@ function extractCurlDownload(cmd) {
|
|
|
7667
8130
|
const outputPath = outputMatch[1] ?? outputMatch[2] ?? outputMatch[3];
|
|
7668
8131
|
if (!outputPath) return null;
|
|
7669
8132
|
if (curlHasUnsafeFlags(cmd)) return null;
|
|
7670
|
-
const
|
|
7671
|
-
if (
|
|
7672
|
-
return { url
|
|
8133
|
+
const url = extractCurlUrl(cmd);
|
|
8134
|
+
if (url === null) return null;
|
|
8135
|
+
return { url, outputPath };
|
|
7673
8136
|
}
|
|
7674
8137
|
function isTscCommand(cmd) {
|
|
7675
8138
|
return /^\s*tsc(\s|$)/i.test(cmd);
|
|
@@ -7708,11 +8171,12 @@ function maybeCompressRewrite(event, rawCmd, cmd) {
|
|
|
7708
8171
|
}
|
|
7709
8172
|
if (!cfg.enabled) return null;
|
|
7710
8173
|
if (!canRunWrappedShell()) return null;
|
|
7711
|
-
const
|
|
8174
|
+
const gateCmd = stripTrailingStderrRedirect(cmd);
|
|
8175
|
+
const detected = detectFromCommand(gateCmd, getCwd(event));
|
|
7712
8176
|
let filterName;
|
|
7713
8177
|
if (detected !== null) {
|
|
7714
8178
|
filterName = detected.filter.name;
|
|
7715
|
-
} else if (isCompressibleSingleCommand(
|
|
8179
|
+
} else if (isCompressibleSingleCommand(gateCmd)) {
|
|
7716
8180
|
filterName = "generic";
|
|
7717
8181
|
} else {
|
|
7718
8182
|
return null;
|
|
@@ -7721,9 +8185,34 @@ function maybeCompressRewrite(event, rawCmd, cmd) {
|
|
|
7721
8185
|
const wrapped = `token-goat compress -f ${filterName} --timeout ${cfg.timeout_seconds} -c ${shellQuoteSingle(rawCmd)}`;
|
|
7722
8186
|
return { hookType: "rewriteInput", updatedInput: { ...event.toolInput, command: wrapped } };
|
|
7723
8187
|
}
|
|
8188
|
+
async function maybeCompressCompoundOutput(cmd, output, exitCode, cwd, cacheMinBytes) {
|
|
8189
|
+
if (process.env["TOKEN_GOAT_BASH_COMPRESS"] === "0") return null;
|
|
8190
|
+
if (isCompressibleSingleCommand(cmd)) return null;
|
|
8191
|
+
if (exitCode !== null && exitCode !== 0) return null;
|
|
8192
|
+
let cfg;
|
|
8193
|
+
try {
|
|
8194
|
+
cfg = loadConfig().bash_compress;
|
|
8195
|
+
} catch {
|
|
8196
|
+
return null;
|
|
8197
|
+
}
|
|
8198
|
+
if (!cfg.enabled || cfg.disabled_filters.includes("generic")) return null;
|
|
8199
|
+
if (Buffer.byteLength(output, "utf-8") < cacheMinBytes) return null;
|
|
8200
|
+
const filter3 = filterByName("generic");
|
|
8201
|
+
if (filter3 === null) return null;
|
|
8202
|
+
const compressed = compressOutput(filter3, output, "", exitCode ?? 0, [], {
|
|
8203
|
+
maxLines: cfg.max_lines,
|
|
8204
|
+
maxBytes: cfg.max_bytes
|
|
8205
|
+
});
|
|
8206
|
+
const minNet = resolveMinNetSavingsBytes();
|
|
8207
|
+
if (!compressed.worthApplying(minNet)) return null;
|
|
8208
|
+
const id = await storeBashOutput(cmd, output, exitCode ?? 0, cwd);
|
|
8209
|
+
recordStat("bash_compress:generic", compressed.bytesSaved, compressed.tokensSaved);
|
|
8210
|
+
const body = compressed.withMarker(minNet) + "\n[token-goat] full output: bash-output " + id + " --full";
|
|
8211
|
+
return { hookType: "rewriteOutput", updatedOutput: body };
|
|
8212
|
+
}
|
|
7724
8213
|
function unwrapCompressCommand(executed) {
|
|
7725
8214
|
const t = executed.trim();
|
|
7726
|
-
if (!/^token-goat\s+compress\b/.test(t)) return null;
|
|
8215
|
+
if (!/^(?:token-goat|tg)\s+compress\b/.test(t)) return null;
|
|
7727
8216
|
let argv;
|
|
7728
8217
|
try {
|
|
7729
8218
|
argv = shlexSplit(t);
|
|
@@ -7775,6 +8264,28 @@ function detectUnbalancedShellSyntax(cmd) {
|
|
|
7775
8264
|
i = j;
|
|
7776
8265
|
continue;
|
|
7777
8266
|
}
|
|
8267
|
+
if (ch === "$" && cmd[i + 1] === "'") {
|
|
8268
|
+
let j = i + 2;
|
|
8269
|
+
let closed = false;
|
|
8270
|
+
while (j < cmd.length) {
|
|
8271
|
+
if (cmd[j] === "\\") {
|
|
8272
|
+
j += 2;
|
|
8273
|
+
continue;
|
|
8274
|
+
}
|
|
8275
|
+
if (cmd[j] === "'") {
|
|
8276
|
+
j++;
|
|
8277
|
+
closed = true;
|
|
8278
|
+
break;
|
|
8279
|
+
}
|
|
8280
|
+
j++;
|
|
8281
|
+
}
|
|
8282
|
+
if (!closed) {
|
|
8283
|
+
inSingle = true;
|
|
8284
|
+
break;
|
|
8285
|
+
}
|
|
8286
|
+
i = j;
|
|
8287
|
+
continue;
|
|
8288
|
+
}
|
|
7778
8289
|
if (ch === "#" && (i === 0 || /\s/.test(cmd[i - 1] ?? ""))) {
|
|
7779
8290
|
const nl2 = cmd.indexOf("\n", i);
|
|
7780
8291
|
i = nl2 === -1 ? cmd.length : nl2 + 1;
|
|
@@ -7929,9 +8440,9 @@ function preBashHandlerInner(event) {
|
|
|
7929
8440
|
"Use `token-goat outline <file>` to see symbol names and line counts without loading files."
|
|
7930
8441
|
);
|
|
7931
8442
|
}
|
|
7932
|
-
const sedRange =
|
|
8443
|
+
const sedRange = extractLineRangeRead(cmd);
|
|
7933
8444
|
if (sedRange !== null) {
|
|
7934
|
-
const { filePath, ranges } = sedRange;
|
|
8445
|
+
const { filePath, ranges, tool } = sedRange;
|
|
7935
8446
|
const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
|
|
7936
8447
|
recordStat("session_hint", 0, 0);
|
|
7937
8448
|
const sedDedupKey = resolveIndexPath(hintPath, preHookCwd ?? process.cwd());
|
|
@@ -7947,7 +8458,7 @@ function preBashHandlerInner(event) {
|
|
|
7947
8458
|
}
|
|
7948
8459
|
}
|
|
7949
8460
|
const hints = [...overlapHints];
|
|
7950
|
-
if (freshRanges.length > 0) hints.push(sedRangeHint(hintPath, freshRanges));
|
|
8461
|
+
if (freshRanges.length > 0) hints.push(sedRangeHint(hintPath, freshRanges, tool));
|
|
7951
8462
|
return contextOutput(hints.join(" "));
|
|
7952
8463
|
}
|
|
7953
8464
|
const catJsonPipe = extractCatJsonPipe(cmd);
|
|
@@ -8200,7 +8711,7 @@ function preBashHandlerInner(event) {
|
|
|
8200
8711
|
);
|
|
8201
8712
|
}
|
|
8202
8713
|
}
|
|
8203
|
-
if (!isBuildCommand(cmd) && detectFromCommand(cmd, preHookCwd ?? void 0) === null) return passOutput();
|
|
8714
|
+
if (!isBuildCommand(cmd) && detectFromCommand(stripTrailingStderrRedirect(cmd), preHookCwd ?? void 0) === null) return passOutput();
|
|
8204
8715
|
const cmdHash = shortFingerprint(stripOutputPipeline(cmd));
|
|
8205
8716
|
const outputId = getBashOutputId(cmdHash);
|
|
8206
8717
|
const entryRaw = outputId !== null ? getBashOutput(outputId) : null;
|
|
@@ -8315,8 +8826,7 @@ async function postBashHandler(event) {
|
|
|
8315
8826
|
}
|
|
8316
8827
|
const mutationDiff = runGit(["diff", "--name-only", diffBase, "HEAD"], { cwd: gitDir, timeoutMs: 5e3 });
|
|
8317
8828
|
if (mutationDiff.exitCode === 0) {
|
|
8318
|
-
const
|
|
8319
|
-
const repoRoot = toplevel.exitCode === 0 && toplevel.stdout.trim() !== "" ? toplevel.stdout.trim() : gitDir;
|
|
8829
|
+
const repoRoot = gitRepoRoot(gitDir);
|
|
8320
8830
|
for (const rel of mutationDiff.stdout.split("\n")) {
|
|
8321
8831
|
const trimmed = rel.trim();
|
|
8322
8832
|
if (trimmed.length === 0) continue;
|
|
@@ -8324,6 +8834,9 @@ async function postBashHandler(event) {
|
|
|
8324
8834
|
}
|
|
8325
8835
|
}
|
|
8326
8836
|
}
|
|
8837
|
+
if (exitCode === null || exitCode === 0) {
|
|
8838
|
+
enqueueNonHeadMovingRewrites(cmd, rawCmd, cwd ?? process.cwd());
|
|
8839
|
+
}
|
|
8327
8840
|
const curlDl = extractCurlDownload(cmd);
|
|
8328
8841
|
if (curlDl !== null && (exitCode === null || exitCode === 0)) {
|
|
8329
8842
|
const resolvedOutputPath = resolveIndexPath(curlDl.outputPath, cwd ?? process.cwd());
|
|
@@ -8379,7 +8892,11 @@ async function postBashHandler(event) {
|
|
|
8379
8892
|
recordBashOutput(gitScopedCacheHash, gitScopedCacheId, Buffer.byteLength(output, "utf-8"));
|
|
8380
8893
|
}
|
|
8381
8894
|
const isMonitoring = getMonitoringRecallHint(cmd) !== null;
|
|
8382
|
-
if (!isMonitoring && !isBuildCommand(cmd) && !isCurlGetCommand(cmd))
|
|
8895
|
+
if (!isMonitoring && !isBuildCommand(cmd) && !isCurlGetCommand(cmd)) {
|
|
8896
|
+
const compound = await maybeCompressCompoundOutput(cmd, output, exitCode, cwd, cacheMinBytes);
|
|
8897
|
+
if (compound !== null) return compound;
|
|
8898
|
+
return passOutput();
|
|
8899
|
+
}
|
|
8383
8900
|
if (Buffer.byteLength(output, "utf-8") < cacheMinBytes) return passOutput();
|
|
8384
8901
|
const cacheKey = isCurlGetCommand(cmd) ? extractCurlUrl(cmd) ?? cmd : stripOutputPipeline(cmd);
|
|
8385
8902
|
const simpleHash = shortFingerprint(cacheKey);
|
|
@@ -8971,8 +9488,21 @@ function compressGWorkspaceMcpResult(toolName, resultText) {
|
|
|
8971
9488
|
const summarized = summarizeAttachments(trimmed);
|
|
8972
9489
|
return finishWithGenericPass(resultText, summarized);
|
|
8973
9490
|
}
|
|
9491
|
+
var DATA_URL_BASE64_RE = /data:([a-zA-Z0-9.+-]+\/[a-zA-Z0-9.+-]+);base64,([A-Za-z0-9+/=]+)/g;
|
|
9492
|
+
var MIN_BASE64_PAYLOAD_LEN = 100;
|
|
9493
|
+
function stripBase64DataUrls(resultText) {
|
|
9494
|
+
let changed = false;
|
|
9495
|
+
const out = resultText.replace(DATA_URL_BASE64_RE, (match, mime, payload) => {
|
|
9496
|
+
if (payload.length < MIN_BASE64_PAYLOAD_LEN) return match;
|
|
9497
|
+
changed = true;
|
|
9498
|
+
return `data:${mime};base64,<stripped ${payload.length} bytes>`;
|
|
9499
|
+
});
|
|
9500
|
+
return changed ? out : null;
|
|
9501
|
+
}
|
|
8974
9502
|
function compressMcpResultWithPacks(toolName, resultText) {
|
|
8975
|
-
|
|
9503
|
+
const base64Stripped = stripBase64DataUrls(resultText);
|
|
9504
|
+
const text = base64Stripped ?? resultText;
|
|
9505
|
+
return compressGithubMcpResult(toolName, text) ?? compressBrowserMcpResult(toolName, text) ?? compressGWorkspaceMcpResult(toolName, text) ?? base64Stripped;
|
|
8976
9506
|
}
|
|
8977
9507
|
|
|
8978
9508
|
// src/hooks_mcp.ts
|
|
@@ -9492,6 +10022,9 @@ async function relayInProcess(eventName, rawPayload) {
|
|
|
9492
10022
|
async function relay(eventName) {
|
|
9493
10023
|
try {
|
|
9494
10024
|
if (!isHookEventName(eventName)) {
|
|
10025
|
+
console.error(
|
|
10026
|
+
`[relay] unknown hook event '${eventName}'; nothing ran. Valid events: ${HOOK_EVENTS.join(", ")}`
|
|
10027
|
+
);
|
|
9495
10028
|
process.stdout.write("{}");
|
|
9496
10029
|
return;
|
|
9497
10030
|
}
|