@wrongstack/tools 0.1.4 → 0.1.8
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 +127 -0
- package/dist/audit.js.map +1 -1
- package/dist/bash.js +103 -5
- package/dist/bash.js.map +1 -1
- package/dist/builtin.js +550 -258
- package/dist/builtin.js.map +1 -1
- package/dist/diff.js +5 -9
- package/dist/diff.js.map +1 -1
- package/dist/document.js +0 -1
- package/dist/document.js.map +1 -1
- package/dist/edit.js +2 -2
- package/dist/edit.js.map +1 -1
- package/dist/exec.d.ts +0 -1
- package/dist/exec.js +105 -44
- package/dist/exec.js.map +1 -1
- package/dist/fetch.js +110 -25
- package/dist/fetch.js.map +1 -1
- package/dist/format.js.map +1 -1
- package/dist/git.d.ts +0 -1
- package/dist/git.js +9 -9
- package/dist/git.js.map +1 -1
- package/dist/glob.js +0 -1
- package/dist/glob.js.map +1 -1
- package/dist/grep.js +58 -3
- package/dist/grep.js.map +1 -1
- package/dist/index.js +549 -257
- package/dist/index.js.map +1 -1
- package/dist/install.js.map +1 -1
- package/dist/lint.js.map +1 -1
- package/dist/logs.js +61 -6
- package/dist/logs.js.map +1 -1
- package/dist/outdated.js.map +1 -1
- package/dist/patch.js +68 -29
- package/dist/patch.js.map +1 -1
- package/dist/read.js +0 -1
- package/dist/read.js.map +1 -1
- package/dist/replace.js +59 -9
- package/dist/replace.js.map +1 -1
- package/dist/scaffold.js +5 -6
- package/dist/scaffold.js.map +1 -1
- package/dist/test.js.map +1 -1
- package/dist/todo.js +1 -1
- package/dist/todo.js.map +1 -1
- package/dist/tool-use.js +0 -8
- package/dist/tool-use.js.map +1 -1
- package/dist/tree.js +9 -5
- package/dist/tree.js.map +1 -1
- package/dist/typecheck.js.map +1 -1
- package/dist/write.js +0 -1
- package/dist/write.js.map +1 -1
- package/package.json +7 -4
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import * as fs4 from 'fs/promises';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { dirname } from 'path';
|
|
4
|
-
import { spawn } from 'child_process';
|
|
5
4
|
import { atomicWrite, unifiedDiff, detectNewlineStyle, normalizeToLf, toStyle, compileGlob, stripAnsi } from '@wrongstack/core';
|
|
5
|
+
import { spawn } from 'child_process';
|
|
6
6
|
import * as os from 'os';
|
|
7
7
|
import * as dns from 'dns/promises';
|
|
8
|
-
import * as
|
|
8
|
+
import * as net from 'net';
|
|
9
9
|
import { statSync } from 'fs';
|
|
10
10
|
|
|
11
11
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
@@ -43,83 +43,6 @@ function isBinaryBuffer(buf) {
|
|
|
43
43
|
}
|
|
44
44
|
return false;
|
|
45
45
|
}
|
|
46
|
-
async function* spawnStream(opts) {
|
|
47
|
-
const max = opts.maxBytes ?? 2e5;
|
|
48
|
-
const flushAt = opts.flushBytes ?? 4 * 1024;
|
|
49
|
-
let stdout = "";
|
|
50
|
-
let stderr = "";
|
|
51
|
-
let pending = "";
|
|
52
|
-
let error;
|
|
53
|
-
const child = spawn(opts.cmd, opts.args, {
|
|
54
|
-
cwd: opts.cwd,
|
|
55
|
-
signal: opts.signal,
|
|
56
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
57
|
-
});
|
|
58
|
-
const queue = [];
|
|
59
|
-
let waiter;
|
|
60
|
-
const wake = () => {
|
|
61
|
-
if (waiter) {
|
|
62
|
-
const w = waiter;
|
|
63
|
-
waiter = void 0;
|
|
64
|
-
w();
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
child.stdout?.on("data", (c) => {
|
|
68
|
-
const s = c.toString();
|
|
69
|
-
if (stdout.length < max) stdout += s;
|
|
70
|
-
queue.push({ kind: "out", data: s });
|
|
71
|
-
wake();
|
|
72
|
-
});
|
|
73
|
-
child.stderr?.on("data", (c) => {
|
|
74
|
-
const s = c.toString();
|
|
75
|
-
if (stderr.length < max) stderr += s;
|
|
76
|
-
queue.push({ kind: "err", data: s });
|
|
77
|
-
wake();
|
|
78
|
-
});
|
|
79
|
-
child.on("error", (e) => {
|
|
80
|
-
error = e.message;
|
|
81
|
-
queue.push({ kind: "error", data: e.message });
|
|
82
|
-
wake();
|
|
83
|
-
});
|
|
84
|
-
child.on("close", (code) => {
|
|
85
|
-
queue.push({ kind: "close", data: "", code: code ?? 0 });
|
|
86
|
-
wake();
|
|
87
|
-
});
|
|
88
|
-
let exitCode = 0;
|
|
89
|
-
let spawnFailed = false;
|
|
90
|
-
for (; ; ) {
|
|
91
|
-
while (queue.length === 0) {
|
|
92
|
-
await new Promise((resolve2) => {
|
|
93
|
-
waiter = resolve2;
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
const chunk = queue.shift();
|
|
97
|
-
if (chunk.kind === "close") {
|
|
98
|
-
if (!spawnFailed) exitCode = chunk.code ?? 0;
|
|
99
|
-
break;
|
|
100
|
-
}
|
|
101
|
-
if (chunk.kind === "error") {
|
|
102
|
-
spawnFailed = true;
|
|
103
|
-
exitCode = 1;
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
pending += chunk.data;
|
|
107
|
-
if (pending.length >= flushAt) {
|
|
108
|
-
yield { type: "partial_output", text: pending };
|
|
109
|
-
pending = "";
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
if (pending.length > 0) {
|
|
113
|
-
yield { type: "partial_output", text: pending };
|
|
114
|
-
}
|
|
115
|
-
return {
|
|
116
|
-
stdout,
|
|
117
|
-
stderr,
|
|
118
|
-
exitCode,
|
|
119
|
-
truncated: stdout.length >= max || stderr.length >= max,
|
|
120
|
-
error
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
46
|
|
|
124
47
|
// src/read.ts
|
|
125
48
|
var MAX_BYTES = 5 * 1024 * 1024;
|
|
@@ -256,7 +179,8 @@ var editTool = {
|
|
|
256
179
|
);
|
|
257
180
|
}
|
|
258
181
|
const lastReadMtime = ctx.lastReadMtime(absPath);
|
|
259
|
-
|
|
182
|
+
const mtimeTolerance = process.platform === "win32" ? 2e3 : 1;
|
|
183
|
+
if (lastReadMtime !== void 0 && stat9.mtimeMs > lastReadMtime + mtimeTolerance) {
|
|
260
184
|
throw new Error(`edit: file "${input.path}" was modified externally. Re-read it first.`);
|
|
261
185
|
}
|
|
262
186
|
const original = await fs4.readFile(absPath, "utf8");
|
|
@@ -331,6 +255,48 @@ function findSimilarity(haystack, needle) {
|
|
|
331
255
|
}
|
|
332
256
|
return line;
|
|
333
257
|
}
|
|
258
|
+
|
|
259
|
+
// src/_regex.ts
|
|
260
|
+
var MAX_PATTERN_LEN = 512;
|
|
261
|
+
var DANGEROUS_PATTERNS = [
|
|
262
|
+
/(\([^)]*[+*][^)]*\))[+*]/,
|
|
263
|
+
// (a+)+, (.*)+, etc — nested quantifier on a group with internal quantifier
|
|
264
|
+
/(\(\?:[^)]*[+*][^)]*\))[+*]/
|
|
265
|
+
// same, with non-capturing group
|
|
266
|
+
];
|
|
267
|
+
function compileUserRegex(pattern, flags) {
|
|
268
|
+
if (typeof pattern !== "string") {
|
|
269
|
+
return { ok: false, reason: "pattern must be a string" };
|
|
270
|
+
}
|
|
271
|
+
if (pattern.length === 0) {
|
|
272
|
+
return { ok: false, reason: "pattern is empty" };
|
|
273
|
+
}
|
|
274
|
+
if (pattern.length > MAX_PATTERN_LEN) {
|
|
275
|
+
return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };
|
|
276
|
+
}
|
|
277
|
+
for (const rx of DANGEROUS_PATTERNS) {
|
|
278
|
+
if (rx.test(pattern)) {
|
|
279
|
+
return {
|
|
280
|
+
ok: false,
|
|
281
|
+
reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
try {
|
|
286
|
+
return { ok: true, regex: new RegExp(pattern, flags) };
|
|
287
|
+
} catch (err) {
|
|
288
|
+
return {
|
|
289
|
+
ok: false,
|
|
290
|
+
reason: err instanceof Error ? err.message : "invalid regex"
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
var MAX_SUBJECT_LEN = 64 * 1024;
|
|
295
|
+
function capSubject(line) {
|
|
296
|
+
return line.length > MAX_SUBJECT_LEN ? line.slice(0, MAX_SUBJECT_LEN) : line;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// src/replace.ts
|
|
334
300
|
var DEFAULT_IGNORE = ["node_modules", ".git", "dist", "build", ".next", "coverage"];
|
|
335
301
|
var replaceTool = {
|
|
336
302
|
name: "replace",
|
|
@@ -358,23 +324,38 @@ var replaceTool = {
|
|
|
358
324
|
if (!input?.pattern) throw new Error("replace: pattern is required");
|
|
359
325
|
if (input.replacement === void 0) throw new Error("replace: replacement is required");
|
|
360
326
|
if (!input?.files) throw new Error("replace: files is required");
|
|
361
|
-
const
|
|
327
|
+
const replaceAll = input.replace_all ?? true;
|
|
328
|
+
const compiled = compileUserRegex(input.pattern, replaceAll ? "g" : "");
|
|
329
|
+
if (!compiled.ok) {
|
|
330
|
+
throw new Error(`replace: ${compiled.reason}`);
|
|
331
|
+
}
|
|
332
|
+
const re = compiled.regex;
|
|
362
333
|
const globRe = input.glob ? compileGlob(input.glob) : null;
|
|
363
334
|
const dryRun = input.dry_run ?? false;
|
|
364
|
-
const replaceAll = input.replace_all ?? true;
|
|
365
335
|
const filesInput = Array.isArray(input.files) ? input.files.join(",") : input.files;
|
|
366
336
|
const fileList = await resolveFiles(filesInput, ctx, globRe);
|
|
367
337
|
const results = [];
|
|
368
338
|
let totalReplacements = 0;
|
|
369
339
|
for (const absPath of fileList) {
|
|
370
|
-
const
|
|
340
|
+
const lstat2 = await fs4.lstat(absPath).catch((err) => {
|
|
371
341
|
if (err.code === "ENOENT") return null;
|
|
372
342
|
throw err;
|
|
373
343
|
});
|
|
344
|
+
if (!lstat2 || !lstat2.isFile()) continue;
|
|
345
|
+
if (lstat2.isSymbolicLink()) continue;
|
|
346
|
+
let realPath;
|
|
347
|
+
try {
|
|
348
|
+
realPath = await fs4.realpath(absPath);
|
|
349
|
+
} catch {
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
const rel = path.relative(ctx.projectRoot, realPath);
|
|
353
|
+
if (rel.startsWith("..") || path.isAbsolute(rel)) continue;
|
|
354
|
+
const stat9 = await fs4.stat(realPath).catch(() => null);
|
|
374
355
|
if (!stat9 || !stat9.isFile()) continue;
|
|
375
356
|
let content;
|
|
376
357
|
try {
|
|
377
|
-
const buf = await fs4.readFile(
|
|
358
|
+
const buf = await fs4.readFile(realPath);
|
|
378
359
|
if (isBinaryBuffer(buf)) continue;
|
|
379
360
|
content = buf.toString("utf8");
|
|
380
361
|
} catch {
|
|
@@ -385,13 +366,12 @@ var replaceTool = {
|
|
|
385
366
|
re.lastIndex = 0;
|
|
386
367
|
const matches = [...contentLf.matchAll(re)];
|
|
387
368
|
if (matches.length === 0) continue;
|
|
388
|
-
const newContentLf =
|
|
369
|
+
const newContentLf = contentLf.replace(re, input.replacement);
|
|
389
370
|
re.lastIndex = 0;
|
|
390
|
-
|
|
391
|
-
totalReplacements += actualCount;
|
|
371
|
+
totalReplacements += matches.length;
|
|
392
372
|
if (!dryRun) {
|
|
393
373
|
const newContent = toStyle(newContentLf, style);
|
|
394
|
-
await atomicWrite(
|
|
374
|
+
await atomicWrite(realPath, newContent, { mode: stat9.mode & 511 });
|
|
395
375
|
}
|
|
396
376
|
const diff = dryRun || matches.length > 0 ? unifiedDiff(content, toStyle(newContentLf, style), { fromFile: absPath, toFile: absPath }) : void 0;
|
|
397
377
|
results.push({
|
|
@@ -438,13 +418,13 @@ async function globFiles(pattern, base, extraGlob) {
|
|
|
438
418
|
return await globNative(pattern, base, extraGlob);
|
|
439
419
|
}
|
|
440
420
|
function checkRg() {
|
|
441
|
-
return new Promise((
|
|
421
|
+
return new Promise((resolve4) => {
|
|
442
422
|
try {
|
|
443
423
|
const p = spawn("rg", ["--version"], { stdio: "ignore" });
|
|
444
|
-
p.on("error", () =>
|
|
445
|
-
p.on("close", (code) =>
|
|
424
|
+
p.on("error", () => resolve4(false));
|
|
425
|
+
p.on("close", (code) => resolve4(code === 0));
|
|
446
426
|
} catch {
|
|
447
|
-
|
|
427
|
+
resolve4(false);
|
|
448
428
|
}
|
|
449
429
|
});
|
|
450
430
|
}
|
|
@@ -456,10 +436,10 @@ function spawnRgFind(pattern, base) {
|
|
|
456
436
|
buf += chunk.toString();
|
|
457
437
|
});
|
|
458
438
|
return {
|
|
459
|
-
promise: new Promise((
|
|
439
|
+
promise: new Promise((resolve4, reject) => {
|
|
460
440
|
child.on("error", reject);
|
|
461
441
|
child.on("close", () => {
|
|
462
|
-
|
|
442
|
+
resolve4(buf.split("\n").filter(Boolean));
|
|
463
443
|
});
|
|
464
444
|
})
|
|
465
445
|
};
|
|
@@ -616,13 +596,13 @@ var grepTool = {
|
|
|
616
596
|
}
|
|
617
597
|
};
|
|
618
598
|
async function detectRg(signal) {
|
|
619
|
-
return new Promise((
|
|
599
|
+
return new Promise((resolve4) => {
|
|
620
600
|
try {
|
|
621
601
|
const p = spawn("rg", ["--version"], { stdio: "ignore", signal });
|
|
622
|
-
p.on("error", () =>
|
|
623
|
-
p.on("close", (code) =>
|
|
602
|
+
p.on("error", () => resolve4(false));
|
|
603
|
+
p.on("close", (code) => resolve4(code === 0));
|
|
624
604
|
} catch {
|
|
625
|
-
|
|
605
|
+
resolve4(false);
|
|
626
606
|
}
|
|
627
607
|
});
|
|
628
608
|
}
|
|
@@ -642,6 +622,8 @@ async function* runRgStream(input, base, mode, limit, signal) {
|
|
|
642
622
|
let totalLines = 0;
|
|
643
623
|
let batchSinceFlush = 0;
|
|
644
624
|
const FLUSH_AT = 16;
|
|
625
|
+
const MAX_BUF_BYTES = 1e6;
|
|
626
|
+
let bufOverflow = false;
|
|
645
627
|
const child = spawn("rg", args, { signal, stdio: ["ignore", "pipe", "pipe"] });
|
|
646
628
|
const queue = [];
|
|
647
629
|
let waiter;
|
|
@@ -679,6 +661,14 @@ async function* runRgStream(input, base, mode, limit, signal) {
|
|
|
679
661
|
}
|
|
680
662
|
if (c.kind === "close") break;
|
|
681
663
|
buf += c.data;
|
|
664
|
+
if (buf.length > MAX_BUF_BYTES && !bufOverflow) {
|
|
665
|
+
bufOverflow = true;
|
|
666
|
+
buf = buf.slice(-MAX_BUF_BYTES);
|
|
667
|
+
try {
|
|
668
|
+
child.kill("SIGTERM");
|
|
669
|
+
} catch {
|
|
670
|
+
}
|
|
671
|
+
}
|
|
682
672
|
const idx = buf.lastIndexOf("\n");
|
|
683
673
|
if (idx === -1) continue;
|
|
684
674
|
const ready = buf.slice(0, idx);
|
|
@@ -725,14 +715,18 @@ async function* runRgStream(input, base, mode, limit, signal) {
|
|
|
725
715
|
output: {
|
|
726
716
|
matches,
|
|
727
717
|
count: totalLines,
|
|
728
|
-
truncated: totalLines > limit,
|
|
718
|
+
truncated: totalLines > limit || bufOverflow,
|
|
729
719
|
used: "rg"
|
|
730
720
|
}
|
|
731
721
|
};
|
|
732
722
|
}
|
|
733
723
|
async function runNative(input, base, mode, limit, signal) {
|
|
734
724
|
const flags = input.case_insensitive ? "i" : "";
|
|
735
|
-
const
|
|
725
|
+
const compiled = compileUserRegex(input.pattern, flags);
|
|
726
|
+
if (!compiled.ok) {
|
|
727
|
+
throw new Error(`grep: ${compiled.reason}`);
|
|
728
|
+
}
|
|
729
|
+
const re = compiled.regex;
|
|
736
730
|
const globRe = input.glob ? compileGlob(input.glob) : null;
|
|
737
731
|
const matches = [];
|
|
738
732
|
const fileMatches = /* @__PURE__ */ new Map();
|
|
@@ -749,6 +743,7 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
749
743
|
for (const e of entries) {
|
|
750
744
|
if (stopped) return;
|
|
751
745
|
if (DEFAULT_IGNORE3.includes(e.name)) continue;
|
|
746
|
+
if (e.isSymbolicLink()) continue;
|
|
752
747
|
const full = path.join(dir, e.name);
|
|
753
748
|
if (e.isDirectory()) {
|
|
754
749
|
await walk(full);
|
|
@@ -764,7 +759,7 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
764
759
|
const lines = text.split(/\r?\n/);
|
|
765
760
|
let fileHits = 0;
|
|
766
761
|
for (let i = 0; i < lines.length; i++) {
|
|
767
|
-
const ln = lines[i] ?? "";
|
|
762
|
+
const ln = capSubject(lines[i] ?? "");
|
|
768
763
|
re.lastIndex = 0;
|
|
769
764
|
if (re.test(ln)) {
|
|
770
765
|
fileHits++;
|
|
@@ -797,6 +792,86 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
797
792
|
used: "native"
|
|
798
793
|
};
|
|
799
794
|
}
|
|
795
|
+
|
|
796
|
+
// src/_env.ts
|
|
797
|
+
var ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
798
|
+
"PATH",
|
|
799
|
+
"HOME",
|
|
800
|
+
"USER",
|
|
801
|
+
"USERNAME",
|
|
802
|
+
"LOGNAME",
|
|
803
|
+
"SHELL",
|
|
804
|
+
"LANG",
|
|
805
|
+
"LC_ALL",
|
|
806
|
+
"LC_CTYPE",
|
|
807
|
+
"TERM",
|
|
808
|
+
"TZ",
|
|
809
|
+
"TMPDIR",
|
|
810
|
+
"TEMP",
|
|
811
|
+
"TMP",
|
|
812
|
+
"PWD",
|
|
813
|
+
"OLDPWD",
|
|
814
|
+
"COMSPEC",
|
|
815
|
+
"SYSTEMROOT",
|
|
816
|
+
"SYSTEMDRIVE",
|
|
817
|
+
"WINDIR",
|
|
818
|
+
"PROGRAMFILES",
|
|
819
|
+
"PROGRAMFILES(X86)",
|
|
820
|
+
"PROGRAMDATA",
|
|
821
|
+
"APPDATA",
|
|
822
|
+
"LOCALAPPDATA",
|
|
823
|
+
"USERPROFILE",
|
|
824
|
+
"PUBLIC",
|
|
825
|
+
"PATHEXT"
|
|
826
|
+
]);
|
|
827
|
+
var SECRET_NAME_PARTS = [
|
|
828
|
+
"TOKEN",
|
|
829
|
+
"SECRET",
|
|
830
|
+
"PASSWORD",
|
|
831
|
+
"PASSWD",
|
|
832
|
+
"AUTH",
|
|
833
|
+
"CRED",
|
|
834
|
+
"BEARER",
|
|
835
|
+
"COOKIE",
|
|
836
|
+
"PRIVATE"
|
|
837
|
+
];
|
|
838
|
+
function looksSecret(name) {
|
|
839
|
+
const upper = name.toUpperCase();
|
|
840
|
+
for (const p of SECRET_NAME_PARTS) {
|
|
841
|
+
if (upper.includes(p)) return true;
|
|
842
|
+
}
|
|
843
|
+
if (/(?:^|_)KEY(?:$|_|S$)/i.test(upper)) return true;
|
|
844
|
+
if (/API[_-]?KEY/i.test(upper)) return true;
|
|
845
|
+
if (/ACCESS[_-]?KEY/i.test(upper)) return true;
|
|
846
|
+
if (/SESSION[_-]?ID/i.test(upper) === false && /SESSION/i.test(upper)) {
|
|
847
|
+
return true;
|
|
848
|
+
}
|
|
849
|
+
return false;
|
|
850
|
+
}
|
|
851
|
+
function buildChildEnv(sessionId) {
|
|
852
|
+
const passthrough = process.env["WRONGSTACK_BASH_ENV_PASSTHROUGH"] === "1";
|
|
853
|
+
const out = {};
|
|
854
|
+
for (const [k, v] of Object.entries(process.env)) {
|
|
855
|
+
if (v === void 0) continue;
|
|
856
|
+
if (passthrough) {
|
|
857
|
+
out[k] = v;
|
|
858
|
+
continue;
|
|
859
|
+
}
|
|
860
|
+
const upper = k.toUpperCase();
|
|
861
|
+
if (ALLOWED_KEYS.has(upper)) {
|
|
862
|
+
out[k] = v;
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
if (looksSecret(upper)) continue;
|
|
866
|
+
if (upper.startsWith("NODE_") || upper.startsWith("NPM_") || upper.startsWith("PNPM_") || upper.startsWith("YARN_") || upper.startsWith("GIT_") || upper.startsWith("CI") || upper.startsWith("XDG_") || upper === "EDITOR" || upper === "VISUAL" || upper === "PAGER") {
|
|
867
|
+
out[k] = v;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
if (sessionId) out["WRONGSTACK_SESSION_ID"] = sessionId;
|
|
871
|
+
return out;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// src/bash.ts
|
|
800
875
|
var MAX_OUTPUT = 32768;
|
|
801
876
|
var DEFAULT_TIMEOUT = 3e4;
|
|
802
877
|
var STREAM_FLUSH_INTERVAL_MS = 200;
|
|
@@ -807,6 +882,10 @@ var bashTool = {
|
|
|
807
882
|
usageHint: "Runs via `bash -c` (or `cmd /c` on Windows). Cwd is the project root. Default timeout 30s. Output truncated from the middle if oversized. Use for git, npm, builds, tests.",
|
|
808
883
|
permission: "confirm",
|
|
809
884
|
mutating: true,
|
|
885
|
+
// Trust rules match on the literal `command` string. Without subjectKey
|
|
886
|
+
// the policy heuristic would have done the same here, but declaring it
|
|
887
|
+
// explicitly removes the implicit cross-tool aliasing.
|
|
888
|
+
subjectKey: "command",
|
|
810
889
|
timeoutMs: 3e4,
|
|
811
890
|
maxOutputBytes: MAX_OUTPUT,
|
|
812
891
|
estimatedDurationMs: 3e3,
|
|
@@ -833,13 +912,13 @@ var bashTool = {
|
|
|
833
912
|
const isWin = os.platform() === "win32";
|
|
834
913
|
const shell = isWin ? process.env["COMSPEC"] ?? "cmd.exe" : process.env["SHELL"] ?? "/bin/bash";
|
|
835
914
|
const args = isWin ? ["/c", input.command] : ["-c", input.command];
|
|
836
|
-
const env =
|
|
837
|
-
|
|
915
|
+
const env = buildChildEnv(ctx.session?.id);
|
|
916
|
+
const detached = isWin ? !!input.background : true;
|
|
838
917
|
const child = spawn(shell, args, {
|
|
839
918
|
cwd: ctx.projectRoot,
|
|
840
919
|
env,
|
|
841
920
|
stdio: input.background ? "ignore" : ["ignore", "pipe", "pipe"],
|
|
842
|
-
detached
|
|
921
|
+
detached,
|
|
843
922
|
signal: opts.signal
|
|
844
923
|
});
|
|
845
924
|
if (input.background) {
|
|
@@ -869,10 +948,26 @@ var bashTool = {
|
|
|
869
948
|
}
|
|
870
949
|
} else {
|
|
871
950
|
try {
|
|
872
|
-
child.
|
|
951
|
+
if (typeof child.pid === "number") {
|
|
952
|
+
try {
|
|
953
|
+
process.kill(-child.pid, "SIGTERM");
|
|
954
|
+
} catch {
|
|
955
|
+
child.kill("SIGTERM");
|
|
956
|
+
}
|
|
957
|
+
} else {
|
|
958
|
+
child.kill("SIGTERM");
|
|
959
|
+
}
|
|
873
960
|
const killTimer = setTimeout(() => {
|
|
874
961
|
try {
|
|
875
|
-
child.
|
|
962
|
+
if (typeof child.pid === "number") {
|
|
963
|
+
try {
|
|
964
|
+
process.kill(-child.pid, "SIGKILL");
|
|
965
|
+
} catch {
|
|
966
|
+
child.kill("SIGKILL");
|
|
967
|
+
}
|
|
968
|
+
} else {
|
|
969
|
+
child.kill("SIGKILL");
|
|
970
|
+
}
|
|
876
971
|
} catch {
|
|
877
972
|
}
|
|
878
973
|
}, 2e3);
|
|
@@ -894,10 +989,10 @@ var bashTool = {
|
|
|
894
989
|
queue.push(c);
|
|
895
990
|
}
|
|
896
991
|
};
|
|
897
|
-
const next = () => new Promise((
|
|
992
|
+
const next = () => new Promise((resolve4) => {
|
|
898
993
|
const c = queue.shift();
|
|
899
|
-
if (c)
|
|
900
|
-
else resolveNext =
|
|
994
|
+
if (c) resolve4(c);
|
|
995
|
+
else resolveNext = resolve4;
|
|
901
996
|
});
|
|
902
997
|
let lastFlush = Date.now();
|
|
903
998
|
const flush = () => {
|
|
@@ -989,32 +1084,13 @@ var ALLOWED_COMMANDS = {
|
|
|
989
1084
|
docker: ["--version", "ps", "images", "build"],
|
|
990
1085
|
kubectl: ["version", "get", "describe", "logs"]
|
|
991
1086
|
};
|
|
992
|
-
var FORBIDDEN_PATTERNS = [
|
|
993
|
-
/;\s*rm\s+-rf/i,
|
|
994
|
-
/\|\s*rm\s/i,
|
|
995
|
-
/\&\&\s*rm/i,
|
|
996
|
-
/\$\(.*rm/s,
|
|
997
|
-
/`.*rm/s,
|
|
998
|
-
/eval\s*\(/i,
|
|
999
|
-
/exec\s+/i,
|
|
1000
|
-
/nc\s+-e/i,
|
|
1001
|
-
/bash\s+-i/i,
|
|
1002
|
-
/\/dev\/tcp\//i,
|
|
1003
|
-
/curl\s+.*\|/i,
|
|
1004
|
-
/wget\s+.*\|/i,
|
|
1005
|
-
/chmod\s+777/i,
|
|
1006
|
-
/chmod\s+4755/i,
|
|
1007
|
-
/>\s*\/dev\//i,
|
|
1008
|
-
/2>\s*\/dev\//i,
|
|
1009
|
-
/tee\s+/i
|
|
1010
|
-
];
|
|
1011
1087
|
var MAX_ARGS = 20;
|
|
1012
1088
|
var MAX_OUTPUT2 = 2e5;
|
|
1013
1089
|
var TIMEOUT_MS = 3e4;
|
|
1014
1090
|
var execTool = {
|
|
1015
1091
|
name: "exec",
|
|
1016
1092
|
description: "Restricted shell that only runs pre-approved commands with constrained arguments. Safer alternative to `bash`.",
|
|
1017
|
-
usageHint: "Set `command` (must be in allowlist). `args` passed through.
|
|
1093
|
+
usageHint: "Set `command` (must be in allowlist). `args` passed through. For arbitrary shell access use the `bash` tool instead.",
|
|
1018
1094
|
permission: "confirm",
|
|
1019
1095
|
mutating: false,
|
|
1020
1096
|
timeoutMs: TIMEOUT_MS,
|
|
@@ -1023,57 +1099,56 @@ var execTool = {
|
|
|
1023
1099
|
properties: {
|
|
1024
1100
|
command: { type: "string", description: "Command to run (must be in allowlist)" },
|
|
1025
1101
|
args: { type: "array", items: { type: "string" }, description: "Arguments" },
|
|
1026
|
-
cwd: { type: "string", description: "Working directory" },
|
|
1027
|
-
timeout: { type: "integer", description: "Timeout in ms (default: 30000)" }
|
|
1028
|
-
allow_unknown: {
|
|
1029
|
-
type: "boolean",
|
|
1030
|
-
description: "Allow commands not in allowlist (DANGEROUS, use with caution)"
|
|
1031
|
-
}
|
|
1102
|
+
cwd: { type: "string", description: "Working directory (must resolve inside project root)" },
|
|
1103
|
+
timeout: { type: "integer", description: "Timeout in ms (default: 30000)" }
|
|
1032
1104
|
},
|
|
1033
1105
|
required: ["command"]
|
|
1034
1106
|
},
|
|
1035
1107
|
async execute(input, ctx, opts) {
|
|
1036
1108
|
const cmd = input.command.trim();
|
|
1037
1109
|
if (!cmd) return { command: cmd, args: [], stdout: "", stderr: "Empty command", exitCode: 1, truncated: false, allowed: false };
|
|
1038
|
-
if (
|
|
1110
|
+
if (!(cmd in ALLOWED_COMMANDS)) {
|
|
1039
1111
|
return {
|
|
1040
1112
|
command: cmd,
|
|
1041
1113
|
args: input.args ?? [],
|
|
1042
1114
|
stdout: "",
|
|
1043
|
-
stderr: `Command
|
|
1115
|
+
stderr: `Command "${cmd}" not in allowlist. Use the bash tool for arbitrary commands.`,
|
|
1044
1116
|
exitCode: 1,
|
|
1045
1117
|
truncated: false,
|
|
1046
1118
|
allowed: false
|
|
1047
1119
|
};
|
|
1048
1120
|
}
|
|
1049
|
-
const
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
if (
|
|
1121
|
+
const args = (input.args ?? []).slice(0, MAX_ARGS);
|
|
1122
|
+
const timeout = Math.min(input.timeout ?? TIMEOUT_MS, TIMEOUT_MS);
|
|
1123
|
+
const requestedCwd = input.cwd ? path.resolve(ctx.projectRoot, input.cwd) : ctx.cwd;
|
|
1124
|
+
const rel = path.relative(ctx.projectRoot, requestedCwd);
|
|
1125
|
+
if (rel.startsWith("..") || path.isAbsolute(rel)) {
|
|
1054
1126
|
return {
|
|
1055
1127
|
command: cmd,
|
|
1056
|
-
args
|
|
1128
|
+
args,
|
|
1057
1129
|
stdout: "",
|
|
1058
|
-
stderr: `
|
|
1130
|
+
stderr: `cwd "${input.cwd}" resolves outside project root`,
|
|
1059
1131
|
exitCode: 1,
|
|
1060
1132
|
truncated: false,
|
|
1061
1133
|
allowed: false
|
|
1062
1134
|
};
|
|
1063
1135
|
}
|
|
1064
|
-
const
|
|
1065
|
-
const timeout = Math.min(input.timeout ?? TIMEOUT_MS, TIMEOUT_MS);
|
|
1066
|
-
const cwd = input.cwd ?? ctx.cwd;
|
|
1136
|
+
const cwd = requestedCwd;
|
|
1067
1137
|
const signal = opts.signal;
|
|
1068
|
-
return runCommand(cmd, args, cwd, timeout, signal);
|
|
1138
|
+
return runCommand(cmd, args, cwd, timeout, signal, ctx.session?.id);
|
|
1069
1139
|
}
|
|
1070
1140
|
};
|
|
1071
|
-
function runCommand(cmd, args, cwd, timeout, signal) {
|
|
1072
|
-
return new Promise((
|
|
1141
|
+
function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
1142
|
+
return new Promise((resolve4) => {
|
|
1073
1143
|
let stdout = "";
|
|
1074
1144
|
let stderr = "";
|
|
1075
1145
|
let killed = false;
|
|
1076
|
-
const child = spawn(cmd, args, {
|
|
1146
|
+
const child = spawn(cmd, args, {
|
|
1147
|
+
cwd,
|
|
1148
|
+
signal,
|
|
1149
|
+
env: buildChildEnv(sessionId),
|
|
1150
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1151
|
+
});
|
|
1077
1152
|
const timer = setTimeout(() => {
|
|
1078
1153
|
killed = true;
|
|
1079
1154
|
child.kill("SIGTERM");
|
|
@@ -1086,7 +1161,7 @@ function runCommand(cmd, args, cwd, timeout, signal) {
|
|
|
1086
1161
|
});
|
|
1087
1162
|
child.on("close", (code) => {
|
|
1088
1163
|
clearTimeout(timer);
|
|
1089
|
-
|
|
1164
|
+
resolve4({
|
|
1090
1165
|
command: cmd,
|
|
1091
1166
|
args,
|
|
1092
1167
|
stdout: stdout.slice(0, MAX_OUTPUT2),
|
|
@@ -1098,7 +1173,7 @@ function runCommand(cmd, args, cwd, timeout, signal) {
|
|
|
1098
1173
|
});
|
|
1099
1174
|
child.on("error", (err) => {
|
|
1100
1175
|
clearTimeout(timer);
|
|
1101
|
-
|
|
1176
|
+
resolve4({
|
|
1102
1177
|
command: cmd,
|
|
1103
1178
|
args,
|
|
1104
1179
|
stdout: stdout.slice(0, MAX_OUTPUT2),
|
|
@@ -1112,17 +1187,6 @@ function runCommand(cmd, args, cwd, timeout, signal) {
|
|
|
1112
1187
|
}
|
|
1113
1188
|
var MAX_BYTES2 = 131072;
|
|
1114
1189
|
var TIMEOUT_MS2 = 2e4;
|
|
1115
|
-
var PRIVATE_RANGES = [
|
|
1116
|
-
/^10\./,
|
|
1117
|
-
/^192\.168\./,
|
|
1118
|
-
/^172\.(1[6-9]|2[0-9]|3[01])\./,
|
|
1119
|
-
/^127\./,
|
|
1120
|
-
/^0\./,
|
|
1121
|
-
/^169\.254\./,
|
|
1122
|
-
/^::1$/,
|
|
1123
|
-
/^fc/i,
|
|
1124
|
-
/^fe80:/i
|
|
1125
|
-
];
|
|
1126
1190
|
var ALLOW_PRIVATE = process.env["WRONGSTACK_FETCH_ALLOW_PRIVATE"] === "1";
|
|
1127
1191
|
async function fetchWithRedirectLimit(url, maxRedirects, signal) {
|
|
1128
1192
|
const headers = {
|
|
@@ -1132,6 +1196,14 @@ async function fetchWithRedirectLimit(url, maxRedirects, signal) {
|
|
|
1132
1196
|
let redirectCount = 0;
|
|
1133
1197
|
let currentUrl = url;
|
|
1134
1198
|
for (; ; ) {
|
|
1199
|
+
const parsed = new URL(currentUrl);
|
|
1200
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
1201
|
+
throw new Error(`fetch: redirect to unsupported protocol "${parsed.protocol}"`);
|
|
1202
|
+
}
|
|
1203
|
+
if (parsed.protocol === "http:" && !ALLOW_PRIVATE) {
|
|
1204
|
+
throw new Error("fetch: redirect to http:// blocked (HTTPS required by default)");
|
|
1205
|
+
}
|
|
1206
|
+
await assertNotPrivate(parsed.hostname);
|
|
1135
1207
|
const res = await fetch(currentUrl, {
|
|
1136
1208
|
redirect: "manual",
|
|
1137
1209
|
signal,
|
|
@@ -1157,6 +1229,11 @@ var fetchTool = {
|
|
|
1157
1229
|
usageHint: "HTTPS only by default. Localhost and RFC1918 ranges blocked unless WRONGSTACK_FETCH_ALLOW_PRIVATE=1. Max 5 redirects, 20s timeout, 128KB cap.",
|
|
1158
1230
|
permission: "confirm",
|
|
1159
1231
|
mutating: false,
|
|
1232
|
+
// Trust rules for fetch match on the literal URL — declare it explicitly
|
|
1233
|
+
// so a user can trust `https://api.example.com/*` without accidentally
|
|
1234
|
+
// matching that pattern on any other tool that happens to have a `url`
|
|
1235
|
+
// input field.
|
|
1236
|
+
subjectKey: "url",
|
|
1160
1237
|
timeoutMs: TIMEOUT_MS2,
|
|
1161
1238
|
maxOutputBytes: MAX_BYTES2,
|
|
1162
1239
|
inputSchema: {
|
|
@@ -1244,35 +1321,118 @@ var fetchTool = {
|
|
|
1244
1321
|
};
|
|
1245
1322
|
async function assertNotPrivate(hostname) {
|
|
1246
1323
|
if (ALLOW_PRIVATE) return;
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
}
|
|
1250
|
-
if (hostname === "localhost" || hostname.endsWith(".localhost")) {
|
|
1324
|
+
const host = hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
|
|
1325
|
+
if (host === "localhost" || host.endsWith(".localhost")) {
|
|
1251
1326
|
throw new Error("fetch: blocked localhost target");
|
|
1252
1327
|
}
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1328
|
+
const ipVersion = net.isIP(host);
|
|
1329
|
+
if (ipVersion === 4) {
|
|
1330
|
+
if (isPrivateIPv4(host)) {
|
|
1331
|
+
throw new Error(`fetch: blocked private/loopback address "${host}"`);
|
|
1332
|
+
}
|
|
1333
|
+
} else if (ipVersion === 6) {
|
|
1334
|
+
if (isPrivateIPv6(host)) {
|
|
1335
|
+
throw new Error(`fetch: blocked private/loopback address "${host}"`);
|
|
1336
|
+
}
|
|
1337
|
+
} else {
|
|
1338
|
+
try {
|
|
1339
|
+
const records = await dns.lookup(host, { all: true });
|
|
1340
|
+
for (const r of records) {
|
|
1341
|
+
const bad = r.family === 4 ? isPrivateIPv4(r.address) : isPrivateIPv6(r.address);
|
|
1342
|
+
if (bad) {
|
|
1343
|
+
throw new Error(`fetch: resolved to private address ${r.address}`);
|
|
1344
|
+
}
|
|
1258
1345
|
}
|
|
1346
|
+
} catch (err) {
|
|
1347
|
+
if (err instanceof Error && err.message.startsWith("fetch:")) throw err;
|
|
1259
1348
|
}
|
|
1260
|
-
} catch (err) {
|
|
1261
|
-
if (err instanceof Error && err.message.startsWith("fetch:")) throw err;
|
|
1262
1349
|
}
|
|
1263
1350
|
}
|
|
1351
|
+
function isPrivateIPv4(addr) {
|
|
1352
|
+
const parts = addr.split(".").map((p) => Number.parseInt(p, 10));
|
|
1353
|
+
if (parts.length !== 4 || parts.some((n) => Number.isNaN(n) || n < 0 || n > 255)) {
|
|
1354
|
+
return true;
|
|
1355
|
+
}
|
|
1356
|
+
const [a, b, c] = parts;
|
|
1357
|
+
if (a === 0) return true;
|
|
1358
|
+
if (a === 10) return true;
|
|
1359
|
+
if (a === 127) return true;
|
|
1360
|
+
if (a === 169 && b === 254) return true;
|
|
1361
|
+
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
1362
|
+
if (a === 192 && b === 168) return true;
|
|
1363
|
+
if (a === 192 && b === 0 && c === 0) return true;
|
|
1364
|
+
if (a === 100 && b >= 64 && b <= 127) return true;
|
|
1365
|
+
if (a >= 224) return true;
|
|
1366
|
+
return false;
|
|
1367
|
+
}
|
|
1368
|
+
function isPrivateIPv6(addr) {
|
|
1369
|
+
const lower = addr.toLowerCase();
|
|
1370
|
+
if (lower === "::" || lower === "::1") return true;
|
|
1371
|
+
const groups = expandIPv6(lower);
|
|
1372
|
+
if (!groups) return true;
|
|
1373
|
+
if (groups[0] === 0 && groups[1] === 0 && groups[2] === 0 && groups[3] === 0 && groups[4] === 0 && groups[5] === 65535) {
|
|
1374
|
+
const a = (groups[6] ?? 0) >> 8;
|
|
1375
|
+
const b = (groups[6] ?? 0) & 255;
|
|
1376
|
+
const c = (groups[7] ?? 0) >> 8;
|
|
1377
|
+
const d = (groups[7] ?? 0) & 255;
|
|
1378
|
+
return isPrivateIPv4(`${a}.${b}.${c}.${d}`);
|
|
1379
|
+
}
|
|
1380
|
+
const high = groups[0] ?? 0;
|
|
1381
|
+
if ((high & 65024) === 64512) return true;
|
|
1382
|
+
if ((high & 65472) === 65152) return true;
|
|
1383
|
+
if ((high & 65280) === 65280) return true;
|
|
1384
|
+
return false;
|
|
1385
|
+
}
|
|
1386
|
+
function expandIPv6(addr) {
|
|
1387
|
+
const parts = addr.split("::");
|
|
1388
|
+
if (parts.length > 2) return null;
|
|
1389
|
+
const parseGroups = (s) => {
|
|
1390
|
+
if (s === "") return [];
|
|
1391
|
+
const out = [];
|
|
1392
|
+
for (const g of s.split(":")) {
|
|
1393
|
+
if (g.length === 0 || g.length > 4) return null;
|
|
1394
|
+
const n = Number.parseInt(g, 16);
|
|
1395
|
+
if (Number.isNaN(n) || n < 0 || n > 65535) return null;
|
|
1396
|
+
out.push(n);
|
|
1397
|
+
}
|
|
1398
|
+
return out;
|
|
1399
|
+
};
|
|
1400
|
+
if (parts.length === 1) {
|
|
1401
|
+
const groups = parseGroups(parts[0] ?? "");
|
|
1402
|
+
if (!groups || groups.length !== 8) return null;
|
|
1403
|
+
return groups;
|
|
1404
|
+
}
|
|
1405
|
+
const head = parseGroups(parts[0] ?? "");
|
|
1406
|
+
const tail = parseGroups(parts[1] ?? "");
|
|
1407
|
+
if (!head || !tail) return null;
|
|
1408
|
+
const fill = 8 - head.length - tail.length;
|
|
1409
|
+
if (fill < 0) return null;
|
|
1410
|
+
return [...head, ...new Array(fill).fill(0), ...tail];
|
|
1411
|
+
}
|
|
1264
1412
|
function combineSignals(...sigs) {
|
|
1265
1413
|
if (typeof AbortSignal.any === "function") {
|
|
1266
1414
|
return AbortSignal.any(sigs);
|
|
1267
1415
|
}
|
|
1268
1416
|
const ctrl = new AbortController();
|
|
1417
|
+
const cleanups = [];
|
|
1418
|
+
const detach = () => {
|
|
1419
|
+
for (const fn of cleanups) fn();
|
|
1420
|
+
cleanups.length = 0;
|
|
1421
|
+
};
|
|
1269
1422
|
for (const s of sigs) {
|
|
1270
1423
|
if (s.aborted) {
|
|
1424
|
+
detach();
|
|
1271
1425
|
ctrl.abort(s.reason);
|
|
1272
|
-
|
|
1426
|
+
return ctrl.signal;
|
|
1273
1427
|
}
|
|
1274
|
-
|
|
1428
|
+
const onAbort = () => {
|
|
1429
|
+
detach();
|
|
1430
|
+
ctrl.abort(s.reason);
|
|
1431
|
+
};
|
|
1432
|
+
s.addEventListener("abort", onAbort, { once: true });
|
|
1433
|
+
cleanups.push(() => s.removeEventListener("abort", onAbort));
|
|
1275
1434
|
}
|
|
1435
|
+
ctrl.signal.addEventListener("abort", detach, { once: true });
|
|
1276
1436
|
return ctrl.signal;
|
|
1277
1437
|
}
|
|
1278
1438
|
function prettyJson(s) {
|
|
@@ -1557,7 +1717,7 @@ var todoTool = {
|
|
|
1557
1717
|
}
|
|
1558
1718
|
}
|
|
1559
1719
|
}
|
|
1560
|
-
ctx.
|
|
1720
|
+
ctx.state.replaceTodos(items);
|
|
1561
1721
|
return {
|
|
1562
1722
|
count: items.length,
|
|
1563
1723
|
in_progress: items.filter((t) => t.status === "in_progress").length
|
|
@@ -1571,10 +1731,10 @@ var gitTool = {
|
|
|
1571
1731
|
description: "Run git commands. Wraps common operations: status, log, diff, commit, branch, checkout, stash, push, pull, fetch, reset.",
|
|
1572
1732
|
usageHint: "Prefer built-in subcommands over raw args. `command` is required. `message` for commits. `branch` for checkout/branch. `files` for status/diff. `format` for log.",
|
|
1573
1733
|
permission: "confirm",
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1734
|
+
// Conservative: any of these may mutate. The non-mutating commands
|
|
1735
|
+
// (status/log/diff/branch/fetch) are still gated on `permission: 'confirm'`
|
|
1736
|
+
// and `MUTATING_SUBCOMMANDS` is consulted at runtime for per-call checks.
|
|
1737
|
+
mutating: true,
|
|
1578
1738
|
timeoutMs: TIMEOUT_MS4,
|
|
1579
1739
|
inputSchema: {
|
|
1580
1740
|
type: "object",
|
|
@@ -1596,7 +1756,6 @@ var gitTool = {
|
|
|
1596
1756
|
],
|
|
1597
1757
|
description: "Git subcommand"
|
|
1598
1758
|
},
|
|
1599
|
-
args: { type: "string", description: "Raw args string (bypasses subcommand logic)" },
|
|
1600
1759
|
files: {
|
|
1601
1760
|
type: "string",
|
|
1602
1761
|
description: 'File(s) for status/diff: single path, comma-separated list, or "**/*.ts" glob'
|
|
@@ -1615,12 +1774,12 @@ var gitTool = {
|
|
|
1615
1774
|
},
|
|
1616
1775
|
async execute(input, ctx, opts) {
|
|
1617
1776
|
if (!input?.command) throw new Error("git: command is required");
|
|
1618
|
-
const gitDir = findGitDir(ctx.cwd);
|
|
1777
|
+
const gitDir = findGitDir(ctx.cwd, ctx.projectRoot);
|
|
1619
1778
|
if (!gitDir) {
|
|
1620
1779
|
return {
|
|
1621
1780
|
command: input.command,
|
|
1622
1781
|
stdout: "",
|
|
1623
|
-
stderr: "Not in a git repository",
|
|
1782
|
+
stderr: "Not in a git repository (within project root)",
|
|
1624
1783
|
exitCode: 128,
|
|
1625
1784
|
truncated: false
|
|
1626
1785
|
};
|
|
@@ -1629,7 +1788,8 @@ var gitTool = {
|
|
|
1629
1788
|
return await runGit(args, gitDir, opts.signal);
|
|
1630
1789
|
}
|
|
1631
1790
|
};
|
|
1632
|
-
function findGitDir(cwd) {
|
|
1791
|
+
function findGitDir(cwd, projectRoot) {
|
|
1792
|
+
const root = projectRoot;
|
|
1633
1793
|
let dir = cwd;
|
|
1634
1794
|
for (let i = 0; i < 20; i++) {
|
|
1635
1795
|
try {
|
|
@@ -1637,6 +1797,7 @@ function findGitDir(cwd) {
|
|
|
1637
1797
|
if (stat9.isDirectory()) return dir;
|
|
1638
1798
|
} catch {
|
|
1639
1799
|
}
|
|
1800
|
+
if (dir === root) break;
|
|
1640
1801
|
const parent = dirname(dir);
|
|
1641
1802
|
if (parent === dir) break;
|
|
1642
1803
|
dir = parent;
|
|
@@ -1644,7 +1805,6 @@ function findGitDir(cwd) {
|
|
|
1644
1805
|
return null;
|
|
1645
1806
|
}
|
|
1646
1807
|
function buildArgs(input) {
|
|
1647
|
-
if (input.args) return input.args.split(/\s+/).filter(Boolean);
|
|
1648
1808
|
const limit = input.limit ?? 20;
|
|
1649
1809
|
const files = input.files ? (Array.isArray(input.files) ? input.files : input.files.split(",")).map((s) => s.trim()).filter(Boolean) : [];
|
|
1650
1810
|
switch (input.command) {
|
|
@@ -1691,7 +1851,7 @@ function buildArgs(input) {
|
|
|
1691
1851
|
}
|
|
1692
1852
|
}
|
|
1693
1853
|
function runGit(args, cwd, signal) {
|
|
1694
|
-
return new Promise((
|
|
1854
|
+
return new Promise((resolve4) => {
|
|
1695
1855
|
let stdout = "";
|
|
1696
1856
|
let stderr = "";
|
|
1697
1857
|
const child = spawn("git", args, {
|
|
@@ -1710,7 +1870,7 @@ function runGit(args, cwd, signal) {
|
|
|
1710
1870
|
}
|
|
1711
1871
|
});
|
|
1712
1872
|
child.on("error", (err) => {
|
|
1713
|
-
|
|
1873
|
+
resolve4({
|
|
1714
1874
|
command: args[0],
|
|
1715
1875
|
stdout,
|
|
1716
1876
|
stderr: err.message,
|
|
@@ -1719,7 +1879,7 @@ function runGit(args, cwd, signal) {
|
|
|
1719
1879
|
});
|
|
1720
1880
|
});
|
|
1721
1881
|
child.on("close", (code) => {
|
|
1722
|
-
|
|
1882
|
+
resolve4({
|
|
1723
1883
|
command: args[0],
|
|
1724
1884
|
stdout: stdout.slice(0, MAX_OUTPUT3),
|
|
1725
1885
|
stderr: stderr.slice(0, MAX_OUTPUT3),
|
|
@@ -1749,51 +1909,90 @@ var patchTool = {
|
|
|
1749
1909
|
async execute(input, ctx, opts) {
|
|
1750
1910
|
if (!input?.patch) throw new Error("patch: patch content is required");
|
|
1751
1911
|
const dir = input.directory ? safeResolve(input.directory, ctx) : ctx.cwd;
|
|
1752
|
-
const strip = input.strip ?? 1;
|
|
1912
|
+
const strip = Math.max(1, input.strip ?? 1);
|
|
1753
1913
|
const dryRun = input.dry_run ?? false;
|
|
1754
|
-
const
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
"
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1914
|
+
const targets = extractDiffTargets(input.patch);
|
|
1915
|
+
for (const t of targets) {
|
|
1916
|
+
const stripped = stripPathComponents(t, strip);
|
|
1917
|
+
if (!stripped) continue;
|
|
1918
|
+
const candidate = path.resolve(dir, stripped);
|
|
1919
|
+
const rel = path.relative(ctx.projectRoot, candidate);
|
|
1920
|
+
if (rel.startsWith("..") || path.isAbsolute(rel)) {
|
|
1921
|
+
return {
|
|
1922
|
+
applied: 0,
|
|
1923
|
+
rejected: 1,
|
|
1924
|
+
files: [],
|
|
1925
|
+
dry_run: dryRun,
|
|
1926
|
+
message: `patch refused: target "${t}" resolves outside project root`
|
|
1927
|
+
};
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
const tmpDir = await fs4.mkdtemp(path.join(dir, ".wstack_patch_"));
|
|
1931
|
+
try {
|
|
1932
|
+
await fs4.chmod(tmpDir, 448).catch(() => {
|
|
1933
|
+
});
|
|
1934
|
+
const patchFile = path.join(tmpDir, "in.diff");
|
|
1935
|
+
await fs4.writeFile(patchFile, input.patch, { mode: 384 });
|
|
1936
|
+
const args = [
|
|
1937
|
+
`-p${strip}`,
|
|
1938
|
+
"--merge",
|
|
1939
|
+
...dryRun ? ["--dry-run"] : [],
|
|
1940
|
+
"-i",
|
|
1941
|
+
patchFile
|
|
1942
|
+
];
|
|
1943
|
+
const result = await runPatch(args, dir, opts.signal);
|
|
1944
|
+
if (result.exitCode !== 0 && !dryRun) {
|
|
1945
|
+
return {
|
|
1946
|
+
applied: 0,
|
|
1947
|
+
rejected: 1,
|
|
1948
|
+
files: [],
|
|
1949
|
+
dry_run: dryRun,
|
|
1950
|
+
message: `patch failed: ${result.stderr || result.stdout}`
|
|
1951
|
+
};
|
|
1952
|
+
}
|
|
1953
|
+
const patched = extractPatchedFiles(result.stdout);
|
|
1767
1954
|
return {
|
|
1768
|
-
applied:
|
|
1769
|
-
rejected:
|
|
1770
|
-
files:
|
|
1955
|
+
applied: patched.length,
|
|
1956
|
+
rejected: 0,
|
|
1957
|
+
files: patched,
|
|
1771
1958
|
dry_run: dryRun,
|
|
1772
|
-
message:
|
|
1959
|
+
message: result.stdout || "patch applied"
|
|
1773
1960
|
};
|
|
1961
|
+
} finally {
|
|
1962
|
+
await fs4.rm(tmpDir, { recursive: true, force: true }).catch(() => {
|
|
1963
|
+
});
|
|
1774
1964
|
}
|
|
1775
|
-
return {
|
|
1776
|
-
applied: result.stdout.includes("patching file") ? 1 : 0,
|
|
1777
|
-
rejected: 0,
|
|
1778
|
-
files: extractPatchedFiles(result.stdout),
|
|
1779
|
-
dry_run: dryRun,
|
|
1780
|
-
message: result.stdout || "patch applied"
|
|
1781
|
-
};
|
|
1782
1965
|
}
|
|
1783
1966
|
};
|
|
1967
|
+
function extractDiffTargets(patch) {
|
|
1968
|
+
const out = [];
|
|
1969
|
+
const re = /^\+\+\+\s+([^\t\r\n]+)/gm;
|
|
1970
|
+
for (const m of patch.matchAll(re)) {
|
|
1971
|
+
const target = m[1]?.trim();
|
|
1972
|
+
if (!target || target === "/dev/null") continue;
|
|
1973
|
+
out.push(target);
|
|
1974
|
+
}
|
|
1975
|
+
return out;
|
|
1976
|
+
}
|
|
1977
|
+
function stripPathComponents(p, strip) {
|
|
1978
|
+
const parts = p.replace(/\\/g, "/").split("/");
|
|
1979
|
+
if (parts.length <= strip) return void 0;
|
|
1980
|
+
return parts.slice(strip).join("/");
|
|
1981
|
+
}
|
|
1784
1982
|
function runPatch(args, cwd, signal) {
|
|
1785
|
-
return new Promise((
|
|
1983
|
+
return new Promise((resolve4) => {
|
|
1786
1984
|
let stdout = "";
|
|
1787
1985
|
let stderr = "";
|
|
1788
|
-
const
|
|
1986
|
+
const env = { ...process.env, LANG: "C", LC_ALL: "C" };
|
|
1987
|
+
const child = spawn("patch", args, { cwd, signal, env, stdio: ["pipe", "pipe", "pipe"] });
|
|
1789
1988
|
child.stdout?.on("data", (c) => {
|
|
1790
1989
|
stdout += c.toString();
|
|
1791
1990
|
});
|
|
1792
1991
|
child.stderr?.on("data", (c) => {
|
|
1793
1992
|
stderr += c.toString();
|
|
1794
1993
|
});
|
|
1795
|
-
child.on("close", (code) =>
|
|
1796
|
-
child.on("error", (e) =>
|
|
1994
|
+
child.on("close", (code) => resolve4({ exitCode: code ?? 1, stdout, stderr }));
|
|
1995
|
+
child.on("error", (e) => resolve4({ exitCode: 1, stdout: "", stderr: e.message }));
|
|
1797
1996
|
});
|
|
1798
1997
|
}
|
|
1799
1998
|
function extractPatchedFiles(output) {
|
|
@@ -1872,8 +2071,8 @@ var jsonTool = {
|
|
|
1872
2071
|
};
|
|
1873
2072
|
}
|
|
1874
2073
|
};
|
|
1875
|
-
function query(data,
|
|
1876
|
-
const parts =
|
|
2074
|
+
function query(data, path12) {
|
|
2075
|
+
const parts = path12.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean);
|
|
1877
2076
|
let current = data;
|
|
1878
2077
|
for (const part of parts) {
|
|
1879
2078
|
if (current === null || current === void 0) return void 0;
|
|
@@ -1979,18 +2178,18 @@ function findGitDir2(cwd) {
|
|
|
1979
2178
|
let dir = cwd;
|
|
1980
2179
|
for (let i = 0; i < 20; i++) {
|
|
1981
2180
|
try {
|
|
1982
|
-
const stat9 =
|
|
2181
|
+
const stat9 = statSync(path.join(dir, ".git"));
|
|
1983
2182
|
if (stat9.isDirectory()) return dir;
|
|
1984
2183
|
} catch {
|
|
1985
2184
|
}
|
|
1986
|
-
const parent =
|
|
2185
|
+
const parent = path.dirname(dir);
|
|
1987
2186
|
if (parent === dir) break;
|
|
1988
2187
|
dir = parent;
|
|
1989
2188
|
}
|
|
1990
2189
|
return null;
|
|
1991
2190
|
}
|
|
1992
2191
|
function runGit2(args, cwd, signal) {
|
|
1993
|
-
return new Promise((
|
|
2192
|
+
return new Promise((resolve4) => {
|
|
1994
2193
|
let stdout = "";
|
|
1995
2194
|
let stderr = "";
|
|
1996
2195
|
const child = spawn("git", args, { cwd, signal, stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -2000,8 +2199,8 @@ function runGit2(args, cwd, signal) {
|
|
|
2000
2199
|
child.stderr?.on("data", (c) => {
|
|
2001
2200
|
stderr += c.toString();
|
|
2002
2201
|
});
|
|
2003
|
-
child.on("close", (code) =>
|
|
2004
|
-
child.on("error", (e) =>
|
|
2202
|
+
child.on("close", (code) => resolve4({ stdout, stderr, exitCode: code ?? 0 }));
|
|
2203
|
+
child.on("error", (e) => resolve4({ stdout: "", stderr: e.message, exitCode: 1 }));
|
|
2005
2204
|
});
|
|
2006
2205
|
}
|
|
2007
2206
|
async function fileDiff(input, ctx, signal) {
|
|
@@ -2124,10 +2323,15 @@ var treeTool = {
|
|
|
2124
2323
|
if (queue.length > 0) {
|
|
2125
2324
|
yield queue.shift();
|
|
2126
2325
|
} else {
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2326
|
+
let pollTimer;
|
|
2327
|
+
const poll = new Promise((r) => {
|
|
2328
|
+
pollTimer = setTimeout(r, 50);
|
|
2329
|
+
});
|
|
2330
|
+
try {
|
|
2331
|
+
await Promise.race([walkPromise, poll]).catch(() => void 0);
|
|
2332
|
+
} finally {
|
|
2333
|
+
if (pollTimer) clearTimeout(pollTimer);
|
|
2334
|
+
}
|
|
2131
2335
|
}
|
|
2132
2336
|
}
|
|
2133
2337
|
await walkPromise;
|
|
@@ -2182,6 +2386,83 @@ async function walkDir(dir, depth, opts) {
|
|
|
2182
2386
|
}
|
|
2183
2387
|
}
|
|
2184
2388
|
}
|
|
2389
|
+
async function* spawnStream(opts) {
|
|
2390
|
+
const max = opts.maxBytes ?? 2e5;
|
|
2391
|
+
const flushAt = opts.flushBytes ?? 4 * 1024;
|
|
2392
|
+
let stdout = "";
|
|
2393
|
+
let stderr = "";
|
|
2394
|
+
let pending = "";
|
|
2395
|
+
let error;
|
|
2396
|
+
const child = spawn(opts.cmd, opts.args, {
|
|
2397
|
+
cwd: opts.cwd,
|
|
2398
|
+
signal: opts.signal,
|
|
2399
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2400
|
+
});
|
|
2401
|
+
const queue = [];
|
|
2402
|
+
let waiter;
|
|
2403
|
+
const wake = () => {
|
|
2404
|
+
if (waiter) {
|
|
2405
|
+
const w = waiter;
|
|
2406
|
+
waiter = void 0;
|
|
2407
|
+
w();
|
|
2408
|
+
}
|
|
2409
|
+
};
|
|
2410
|
+
child.stdout?.on("data", (c) => {
|
|
2411
|
+
const s = c.toString();
|
|
2412
|
+
if (stdout.length < max) stdout += s;
|
|
2413
|
+
queue.push({ kind: "out", data: s });
|
|
2414
|
+
wake();
|
|
2415
|
+
});
|
|
2416
|
+
child.stderr?.on("data", (c) => {
|
|
2417
|
+
const s = c.toString();
|
|
2418
|
+
if (stderr.length < max) stderr += s;
|
|
2419
|
+
queue.push({ kind: "err", data: s });
|
|
2420
|
+
wake();
|
|
2421
|
+
});
|
|
2422
|
+
child.on("error", (e) => {
|
|
2423
|
+
error = e.message;
|
|
2424
|
+
queue.push({ kind: "error", data: e.message });
|
|
2425
|
+
wake();
|
|
2426
|
+
});
|
|
2427
|
+
child.on("close", (code) => {
|
|
2428
|
+
queue.push({ kind: "close", data: "", code: code ?? 0 });
|
|
2429
|
+
wake();
|
|
2430
|
+
});
|
|
2431
|
+
let exitCode = 0;
|
|
2432
|
+
let spawnFailed = false;
|
|
2433
|
+
for (; ; ) {
|
|
2434
|
+
while (queue.length === 0) {
|
|
2435
|
+
await new Promise((resolve4) => {
|
|
2436
|
+
waiter = resolve4;
|
|
2437
|
+
});
|
|
2438
|
+
}
|
|
2439
|
+
const chunk = queue.shift();
|
|
2440
|
+
if (chunk.kind === "close") {
|
|
2441
|
+
if (!spawnFailed) exitCode = chunk.code ?? 0;
|
|
2442
|
+
break;
|
|
2443
|
+
}
|
|
2444
|
+
if (chunk.kind === "error") {
|
|
2445
|
+
spawnFailed = true;
|
|
2446
|
+
exitCode = 1;
|
|
2447
|
+
continue;
|
|
2448
|
+
}
|
|
2449
|
+
pending += chunk.data;
|
|
2450
|
+
if (pending.length >= flushAt) {
|
|
2451
|
+
yield { type: "partial_output", text: pending };
|
|
2452
|
+
pending = "";
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
if (pending.length > 0) {
|
|
2456
|
+
yield { type: "partial_output", text: pending };
|
|
2457
|
+
}
|
|
2458
|
+
return {
|
|
2459
|
+
stdout,
|
|
2460
|
+
stderr,
|
|
2461
|
+
exitCode,
|
|
2462
|
+
truncated: stdout.length >= max || stderr.length >= max,
|
|
2463
|
+
error
|
|
2464
|
+
};
|
|
2465
|
+
}
|
|
2185
2466
|
|
|
2186
2467
|
// src/lint.ts
|
|
2187
2468
|
var lintTool = {
|
|
@@ -2836,7 +3117,7 @@ async function detectManager2(cwd) {
|
|
|
2836
3117
|
return "npm";
|
|
2837
3118
|
}
|
|
2838
3119
|
function runOutdated(manager, args, cwd, signal) {
|
|
2839
|
-
return new Promise((
|
|
3120
|
+
return new Promise((resolve4) => {
|
|
2840
3121
|
let stdout = "";
|
|
2841
3122
|
let stderr = "";
|
|
2842
3123
|
const MAX = 1e5;
|
|
@@ -2849,9 +3130,9 @@ function runOutdated(manager, args, cwd, signal) {
|
|
|
2849
3130
|
});
|
|
2850
3131
|
child.on("close", (code) => {
|
|
2851
3132
|
const result = parseOutdatedOutput(stdout, code ?? 0);
|
|
2852
|
-
|
|
3133
|
+
resolve4(result);
|
|
2853
3134
|
});
|
|
2854
|
-
child.on("error", (e) =>
|
|
3135
|
+
child.on("error", (e) => resolve4({
|
|
2855
3136
|
exit_code: 1,
|
|
2856
3137
|
packages: [],
|
|
2857
3138
|
total: 0,
|
|
@@ -2937,7 +3218,14 @@ var logsTool = {
|
|
|
2937
3218
|
async execute(input, ctx, opts) {
|
|
2938
3219
|
const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
|
|
2939
3220
|
const lines = input.lines ?? 100;
|
|
2940
|
-
|
|
3221
|
+
let filterRe = null;
|
|
3222
|
+
if (input.filter) {
|
|
3223
|
+
const compiled = compileUserRegex(input.filter, "i");
|
|
3224
|
+
if (!compiled.ok) {
|
|
3225
|
+
throw new Error(`logs: ${compiled.reason}`);
|
|
3226
|
+
}
|
|
3227
|
+
filterRe = compiled.regex;
|
|
3228
|
+
}
|
|
2941
3229
|
if (input.service) {
|
|
2942
3230
|
return await dockerLogs(input.service, lines, filterRe, cwd, opts.signal);
|
|
2943
3231
|
}
|
|
@@ -2957,7 +3245,7 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
|
|
|
2957
3245
|
const args = ["logs"];
|
|
2958
3246
|
if (lines > 0) args.push("--tail", String(lines));
|
|
2959
3247
|
args.push("--timestamps", service);
|
|
2960
|
-
return new Promise((
|
|
3248
|
+
return new Promise((resolve4) => {
|
|
2961
3249
|
let stdout = "";
|
|
2962
3250
|
let stderr = "";
|
|
2963
3251
|
const MAX = 2e5;
|
|
@@ -2971,7 +3259,7 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
|
|
|
2971
3259
|
child.on("close", (code) => {
|
|
2972
3260
|
const output = stdout + stderr;
|
|
2973
3261
|
const entries = parseLogLines(output, filterRe);
|
|
2974
|
-
|
|
3262
|
+
resolve4({
|
|
2975
3263
|
source: `docker:${service}`,
|
|
2976
3264
|
entries,
|
|
2977
3265
|
total: entries.length,
|
|
@@ -2979,7 +3267,7 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
|
|
|
2979
3267
|
stream_mode: false
|
|
2980
3268
|
});
|
|
2981
3269
|
});
|
|
2982
|
-
child.on("error", (e) =>
|
|
3270
|
+
child.on("error", (e) => resolve4({
|
|
2983
3271
|
source: `docker:${service}`,
|
|
2984
3272
|
entries: [],
|
|
2985
3273
|
total: 0,
|
|
@@ -2988,29 +3276,41 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
|
|
|
2988
3276
|
}));
|
|
2989
3277
|
});
|
|
2990
3278
|
}
|
|
2991
|
-
|
|
3279
|
+
var MAX_TAIL_LINES = 1e5;
|
|
3280
|
+
async function fileLogs(path12, lines, filterRe, stream) {
|
|
2992
3281
|
const { createInterface } = await import('readline');
|
|
2993
3282
|
const { createReadStream } = await import('fs');
|
|
2994
3283
|
const entries = [];
|
|
2995
|
-
const
|
|
3284
|
+
const effLines = lines > 0 ? Math.min(lines, MAX_TAIL_LINES) : MAX_TAIL_LINES;
|
|
3285
|
+
const window = new Array(effLines);
|
|
3286
|
+
let writeIdx = 0;
|
|
3287
|
+
let totalLines = 0;
|
|
2996
3288
|
const rl = createInterface({
|
|
2997
|
-
input: createReadStream(
|
|
3289
|
+
input: createReadStream(path12),
|
|
2998
3290
|
crlfDelay: Number.POSITIVE_INFINITY
|
|
2999
3291
|
});
|
|
3000
3292
|
for await (const line of rl) {
|
|
3001
3293
|
if (filterRe && !filterRe.test(line)) continue;
|
|
3002
|
-
|
|
3294
|
+
window[writeIdx] = line;
|
|
3295
|
+
writeIdx = (writeIdx + 1) % effLines;
|
|
3296
|
+
totalLines++;
|
|
3297
|
+
}
|
|
3298
|
+
const ordered = [];
|
|
3299
|
+
const start = totalLines >= effLines ? writeIdx : 0;
|
|
3300
|
+
const count = Math.min(totalLines, effLines);
|
|
3301
|
+
for (let i = 0; i < count; i++) {
|
|
3302
|
+
const v = window[(start + i) % effLines];
|
|
3303
|
+
if (v !== void 0) ordered.push(v);
|
|
3003
3304
|
}
|
|
3004
|
-
const
|
|
3005
|
-
for (const line of sliced) {
|
|
3305
|
+
for (const line of ordered) {
|
|
3006
3306
|
const parsed = parseLine(line);
|
|
3007
3307
|
if (parsed) entries.push(parsed);
|
|
3008
3308
|
}
|
|
3009
3309
|
return {
|
|
3010
|
-
source:
|
|
3310
|
+
source: path12,
|
|
3011
3311
|
entries,
|
|
3012
3312
|
total: entries.length,
|
|
3013
|
-
truncated:
|
|
3313
|
+
truncated: totalLines > effLines,
|
|
3014
3314
|
stream_mode: stream
|
|
3015
3315
|
};
|
|
3016
3316
|
}
|
|
@@ -3287,7 +3587,7 @@ var scaffoldTool = {
|
|
|
3287
3587
|
const vars = { name, ...input.vars };
|
|
3288
3588
|
const builtIn = BUILT_IN_TEMPLATES[input.template];
|
|
3289
3589
|
if (builtIn) {
|
|
3290
|
-
return handleBuiltIn(name, builtIn.files, cwd, input.dry_run ?? false, vars);
|
|
3590
|
+
return await handleBuiltIn(name, builtIn.files, cwd, input.dry_run ?? false, vars);
|
|
3291
3591
|
}
|
|
3292
3592
|
return {
|
|
3293
3593
|
template: input.template,
|
|
@@ -3299,15 +3599,15 @@ var scaffoldTool = {
|
|
|
3299
3599
|
};
|
|
3300
3600
|
}
|
|
3301
3601
|
};
|
|
3302
|
-
function handleBuiltIn(name, templateFiles, cwd, dryRun, vars) {
|
|
3602
|
+
async function handleBuiltIn(name, templateFiles, cwd, dryRun, vars) {
|
|
3303
3603
|
const files = [];
|
|
3304
3604
|
let filesCreated = 0;
|
|
3305
3605
|
for (const [filePath, content] of Object.entries(templateFiles)) {
|
|
3306
3606
|
const resolvedPath = substituteVars(filePath, name, vars);
|
|
3307
3607
|
const fullPath = path.join(cwd, resolvedPath);
|
|
3308
3608
|
if (!dryRun) {
|
|
3309
|
-
|
|
3310
|
-
|
|
3609
|
+
await fs4.mkdir(path.dirname(fullPath), { recursive: true });
|
|
3610
|
+
await fs4.writeFile(fullPath, substituteVars(content, name, vars), "utf8");
|
|
3311
3611
|
}
|
|
3312
3612
|
files.push(resolvedPath);
|
|
3313
3613
|
filesCreated++;
|
|
@@ -3447,14 +3747,6 @@ var toolUseTool = {
|
|
|
3447
3747
|
executionMs: Date.now() - start
|
|
3448
3748
|
};
|
|
3449
3749
|
}
|
|
3450
|
-
if (tool.permission === "confirm" && input.input) {
|
|
3451
|
-
return {
|
|
3452
|
-
tool: input.tool,
|
|
3453
|
-
success: false,
|
|
3454
|
-
error: `tool_use: tool "${input.tool}" requires confirmation`,
|
|
3455
|
-
executionMs: Date.now() - start
|
|
3456
|
-
};
|
|
3457
|
-
}
|
|
3458
3750
|
try {
|
|
3459
3751
|
const result = await tool.execute(input.input, ctx, opts);
|
|
3460
3752
|
return {
|