@wrongstack/tools 0.1.1 → 0.1.3
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/dist/audit.d.ts +25 -0
- package/dist/audit.js +209 -0
- package/dist/audit.js.map +1 -0
- package/dist/bash.d.ts +16 -0
- package/dist/bash.js +180 -0
- package/dist/bash.js.map +1 -0
- package/dist/batch-tool-use.d.ts +26 -0
- package/dist/batch-tool-use.js +106 -0
- package/dist/batch-tool-use.js.map +1 -0
- package/dist/builtin.d.ts +5 -0
- package/dist/builtin.js +3735 -0
- package/dist/builtin.js.map +1 -0
- package/dist/diff.d.ts +20 -0
- package/dist/diff.js +142 -0
- package/dist/diff.js.map +1 -0
- package/dist/document.d.ts +27 -0
- package/dist/document.js +148 -0
- package/dist/document.js.map +1 -0
- package/dist/edit.d.ts +22 -0
- package/dist/edit.js +138 -0
- package/dist/edit.js.map +1 -0
- package/dist/exec.d.ts +21 -0
- package/dist/exec.js +159 -0
- package/dist/exec.js.map +1 -0
- package/dist/fetch.d.ts +15 -0
- package/dist/fetch.js +213 -0
- package/dist/fetch.js.map +1 -0
- package/dist/format.d.ts +18 -0
- package/dist/format.js +194 -0
- package/dist/format.js.map +1 -0
- package/dist/git.d.ts +27 -0
- package/dist/git.js +174 -0
- package/dist/git.js.map +1 -0
- package/dist/glob.d.ts +14 -0
- package/dist/glob.js +101 -0
- package/dist/glob.js.map +1 -0
- package/dist/grep.d.ts +20 -0
- package/dist/grep.js +264 -0
- package/dist/grep.js.map +1 -0
- package/dist/index.d.ts +34 -563
- package/dist/index.js +717 -442
- package/dist/index.js.map +1 -1
- package/dist/install.d.ts +19 -0
- package/dist/install.js +186 -0
- package/dist/install.js.map +1 -0
- package/dist/json.d.ts +20 -0
- package/dist/json.js +124 -0
- package/dist/json.js.map +1 -0
- package/dist/lint.d.ts +20 -0
- package/dist/lint.js +191 -0
- package/dist/lint.js.map +1 -0
- package/dist/logs.d.ts +27 -0
- package/dist/logs.js +180 -0
- package/dist/logs.js.map +1 -0
- package/dist/memory.d.ts +22 -0
- package/dist/memory.js +53 -0
- package/dist/memory.js.map +1 -0
- package/dist/mode.d.ts +20 -0
- package/dist/mode.js +81 -0
- package/dist/mode.js.map +1 -0
- package/dist/outdated.d.ts +26 -0
- package/dist/outdated.js +138 -0
- package/dist/outdated.js.map +1 -0
- package/dist/patch.d.ts +18 -0
- package/dist/patch.js +101 -0
- package/dist/patch.js.map +1 -0
- package/dist/read.d.ts +16 -0
- package/dist/read.js +81 -0
- package/dist/read.js.map +1 -0
- package/dist/replace.d.ts +23 -0
- package/dist/replace.js +196 -0
- package/dist/replace.js.map +1 -0
- package/dist/scaffold.d.ts +20 -0
- package/dist/scaffold.js +185 -0
- package/dist/scaffold.js.map +1 -0
- package/dist/search.d.ts +20 -0
- package/dist/search.js +212 -0
- package/dist/search.js.map +1 -0
- package/dist/test.d.ts +24 -0
- package/dist/test.js +247 -0
- package/dist/test.js.map +1 -0
- package/dist/todo.d.ts +12 -0
- package/dist/todo.js +53 -0
- package/dist/todo.js.map +1 -0
- package/dist/tool-help.d.ts +23 -0
- package/dist/tool-help.js +122 -0
- package/dist/tool-help.js.map +1 -0
- package/dist/tool-search.d.ts +22 -0
- package/dist/tool-search.js +70 -0
- package/dist/tool-search.js.map +1 -0
- package/dist/tool-use.d.ts +16 -0
- package/dist/tool-use.js +79 -0
- package/dist/tool-use.js.map +1 -0
- package/dist/tree.d.ts +21 -0
- package/dist/tree.js +176 -0
- package/dist/tree.js.map +1 -0
- package/dist/typecheck.d.ts +19 -0
- package/dist/typecheck.js +181 -0
- package/dist/typecheck.js.map +1 -0
- package/dist/write.d.ts +15 -0
- package/dist/write.js +77 -0
- package/dist/write.js.map +1 -0
- package/package.json +137 -4
package/dist/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import * as fs4 from 'fs/promises';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
-
import {
|
|
3
|
+
import { dirname } from 'path';
|
|
4
4
|
import { spawn } from 'child_process';
|
|
5
|
+
import { atomicWrite, unifiedDiff, detectNewlineStyle, normalizeToLf, toStyle, compileGlob, stripAnsi } from '@wrongstack/core';
|
|
5
6
|
import * as os from 'os';
|
|
6
7
|
import * as dns from 'dns/promises';
|
|
7
8
|
import * as fsSync from 'fs';
|
|
9
|
+
import { statSync } from 'fs';
|
|
8
10
|
|
|
9
11
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
10
12
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
@@ -41,6 +43,83 @@ function isBinaryBuffer(buf) {
|
|
|
41
43
|
}
|
|
42
44
|
return false;
|
|
43
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
|
+
}
|
|
44
123
|
|
|
45
124
|
// src/read.ts
|
|
46
125
|
var MAX_BYTES = 5 * 1024 * 1024;
|
|
@@ -347,7 +426,7 @@ async function resolveFiles(filesInput, ctx, extraGlob) {
|
|
|
347
426
|
return resolved;
|
|
348
427
|
}
|
|
349
428
|
async function globFiles(pattern, base, extraGlob) {
|
|
350
|
-
const { spawn:
|
|
429
|
+
const { spawn: spawn11 } = await import('child_process');
|
|
351
430
|
const rgAvailable = await checkRg();
|
|
352
431
|
if (rgAvailable) {
|
|
353
432
|
try {
|
|
@@ -511,6 +590,14 @@ var grepTool = {
|
|
|
511
590
|
required: ["pattern"]
|
|
512
591
|
},
|
|
513
592
|
async execute(input, ctx, opts) {
|
|
593
|
+
let final;
|
|
594
|
+
for await (const ev of grepTool.executeStream(input, ctx, opts)) {
|
|
595
|
+
if (ev.type === "final") final = ev.output;
|
|
596
|
+
}
|
|
597
|
+
if (!final) throw new Error("grep: stream ended without final event");
|
|
598
|
+
return final;
|
|
599
|
+
},
|
|
600
|
+
async *executeStream(input, ctx, opts) {
|
|
514
601
|
if (!input?.pattern) throw new Error("grep: pattern is required");
|
|
515
602
|
const base = input.path ? safeResolve(input.path, ctx) : ctx.cwd;
|
|
516
603
|
const mode = input.output_mode ?? "content";
|
|
@@ -518,11 +605,14 @@ var grepTool = {
|
|
|
518
605
|
const rgAvailable = await detectRg(opts.signal);
|
|
519
606
|
if (rgAvailable) {
|
|
520
607
|
try {
|
|
521
|
-
|
|
608
|
+
yield* runRgStream(input, base, mode, limit, opts.signal);
|
|
609
|
+
return;
|
|
522
610
|
} catch {
|
|
523
611
|
}
|
|
524
612
|
}
|
|
525
|
-
|
|
613
|
+
yield { type: "log", text: "Falling back to native grep\u2026" };
|
|
614
|
+
const out = await runNative(input, base, mode, limit, opts.signal);
|
|
615
|
+
yield { type: "final", output: out };
|
|
526
616
|
}
|
|
527
617
|
};
|
|
528
618
|
async function detectRg(signal) {
|
|
@@ -536,35 +626,109 @@ async function detectRg(signal) {
|
|
|
536
626
|
}
|
|
537
627
|
});
|
|
538
628
|
}
|
|
539
|
-
function
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
});
|
|
629
|
+
async function* runRgStream(input, base, mode, limit, signal) {
|
|
630
|
+
const args = ["--no-heading"];
|
|
631
|
+
if (input.case_insensitive) args.push("-i");
|
|
632
|
+
if (mode === "files_with_matches") args.push("-l");
|
|
633
|
+
if (mode === "count") args.push("-c");
|
|
634
|
+
if (mode === "content") {
|
|
635
|
+
args.push("-n");
|
|
636
|
+
if (input.context_lines) args.push("-C", String(input.context_lines));
|
|
637
|
+
}
|
|
638
|
+
if (input.glob) args.push("--glob", input.glob);
|
|
639
|
+
args.push("--", input.pattern, base);
|
|
640
|
+
const matches = [];
|
|
641
|
+
let buf = "";
|
|
642
|
+
let totalLines = 0;
|
|
643
|
+
let batchSinceFlush = 0;
|
|
644
|
+
const FLUSH_AT = 16;
|
|
645
|
+
const child = spawn("rg", args, { signal, stdio: ["ignore", "pipe", "pipe"] });
|
|
646
|
+
const queue = [];
|
|
647
|
+
let waiter;
|
|
648
|
+
const wake = () => {
|
|
649
|
+
if (waiter) {
|
|
650
|
+
const w = waiter;
|
|
651
|
+
waiter = void 0;
|
|
652
|
+
w();
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
child.stdout?.on("data", (c) => {
|
|
656
|
+
queue.push({ kind: "out", data: c.toString() });
|
|
657
|
+
wake();
|
|
567
658
|
});
|
|
659
|
+
child.on("error", (e) => {
|
|
660
|
+
queue.push({ kind: "error", data: e.message });
|
|
661
|
+
wake();
|
|
662
|
+
});
|
|
663
|
+
child.on("close", () => {
|
|
664
|
+
queue.push({ kind: "close", data: "" });
|
|
665
|
+
wake();
|
|
666
|
+
});
|
|
667
|
+
let pendingBatch = [];
|
|
668
|
+
let errored = false;
|
|
669
|
+
for (; ; ) {
|
|
670
|
+
while (queue.length === 0) {
|
|
671
|
+
await new Promise((r) => {
|
|
672
|
+
waiter = r;
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
const c = queue.shift();
|
|
676
|
+
if (c.kind === "error") {
|
|
677
|
+
errored = true;
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
if (c.kind === "close") break;
|
|
681
|
+
buf += c.data;
|
|
682
|
+
const idx = buf.lastIndexOf("\n");
|
|
683
|
+
if (idx === -1) continue;
|
|
684
|
+
const ready = buf.slice(0, idx);
|
|
685
|
+
buf = buf.slice(idx + 1);
|
|
686
|
+
for (const line of ready.split("\n")) {
|
|
687
|
+
if (!line) continue;
|
|
688
|
+
totalLines++;
|
|
689
|
+
if (matches.length < limit) {
|
|
690
|
+
matches.push(line);
|
|
691
|
+
pendingBatch.push(line);
|
|
692
|
+
batchSinceFlush++;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
if (batchSinceFlush >= FLUSH_AT) {
|
|
696
|
+
yield {
|
|
697
|
+
type: "partial_output",
|
|
698
|
+
text: pendingBatch.join("\n"),
|
|
699
|
+
data: { matches_so_far: matches.length }
|
|
700
|
+
};
|
|
701
|
+
pendingBatch = [];
|
|
702
|
+
batchSinceFlush = 0;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
if (buf.trim()) {
|
|
706
|
+
for (const line of buf.split("\n")) {
|
|
707
|
+
if (!line) continue;
|
|
708
|
+
totalLines++;
|
|
709
|
+
if (matches.length < limit) {
|
|
710
|
+
matches.push(line);
|
|
711
|
+
pendingBatch.push(line);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
if (pendingBatch.length > 0) {
|
|
716
|
+
yield {
|
|
717
|
+
type: "partial_output",
|
|
718
|
+
text: pendingBatch.join("\n"),
|
|
719
|
+
data: { matches_so_far: matches.length }
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
if (errored) throw new Error("rg: spawn error");
|
|
723
|
+
yield {
|
|
724
|
+
type: "final",
|
|
725
|
+
output: {
|
|
726
|
+
matches,
|
|
727
|
+
count: totalLines,
|
|
728
|
+
truncated: totalLines > limit,
|
|
729
|
+
used: "rg"
|
|
730
|
+
}
|
|
731
|
+
};
|
|
568
732
|
}
|
|
569
733
|
async function runNative(input, base, mode, limit, signal) {
|
|
570
734
|
const flags = input.case_insensitive ? "i" : "";
|
|
@@ -635,6 +799,8 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
635
799
|
}
|
|
636
800
|
var MAX_OUTPUT = 32768;
|
|
637
801
|
var DEFAULT_TIMEOUT = 3e4;
|
|
802
|
+
var STREAM_FLUSH_INTERVAL_MS = 200;
|
|
803
|
+
var STREAM_FLUSH_BYTES = 4 * 1024;
|
|
638
804
|
var bashTool = {
|
|
639
805
|
name: "bash",
|
|
640
806
|
description: "Run a shell command. stdout and stderr are merged.",
|
|
@@ -643,6 +809,7 @@ var bashTool = {
|
|
|
643
809
|
mutating: true,
|
|
644
810
|
timeoutMs: 3e4,
|
|
645
811
|
maxOutputBytes: MAX_OUTPUT,
|
|
812
|
+
estimatedDurationMs: 3e3,
|
|
646
813
|
inputSchema: {
|
|
647
814
|
type: "object",
|
|
648
815
|
properties: {
|
|
@@ -653,6 +820,14 @@ var bashTool = {
|
|
|
653
820
|
required: ["command"]
|
|
654
821
|
},
|
|
655
822
|
async execute(input, ctx, opts) {
|
|
823
|
+
let final;
|
|
824
|
+
for await (const ev of bashTool.executeStream(input, ctx, opts)) {
|
|
825
|
+
if (ev.type === "final") final = ev.output;
|
|
826
|
+
}
|
|
827
|
+
if (!final) throw new Error("bash: stream ended without final event");
|
|
828
|
+
return final;
|
|
829
|
+
},
|
|
830
|
+
async *executeStream(input, ctx, opts) {
|
|
656
831
|
if (!input?.command) throw new Error("bash: command is required");
|
|
657
832
|
const timeoutMs = Math.min(input.timeout_ms ?? DEFAULT_TIMEOUT, 6e5);
|
|
658
833
|
const isWin = os.platform() === "win32";
|
|
@@ -660,75 +835,127 @@ var bashTool = {
|
|
|
660
835
|
const args = isWin ? ["/c", input.command] : ["-c", input.command];
|
|
661
836
|
const env = { ...process.env };
|
|
662
837
|
env["WRONGSTACK_SESSION_ID"] = ctx.session.id;
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
if (input.background) {
|
|
677
|
-
const pid = child.pid;
|
|
678
|
-
if (typeof pid === "number") child.unref();
|
|
679
|
-
return resolve2({
|
|
838
|
+
const child = spawn(shell, args, {
|
|
839
|
+
cwd: ctx.projectRoot,
|
|
840
|
+
env,
|
|
841
|
+
stdio: input.background ? "ignore" : ["ignore", "pipe", "pipe"],
|
|
842
|
+
detached: input.background,
|
|
843
|
+
signal: opts.signal
|
|
844
|
+
});
|
|
845
|
+
if (input.background) {
|
|
846
|
+
const pid = child.pid;
|
|
847
|
+
if (typeof pid === "number") child.unref();
|
|
848
|
+
yield {
|
|
849
|
+
type: "final",
|
|
850
|
+
output: {
|
|
680
851
|
output: `[background] pid=${pid ?? "unknown"}`,
|
|
681
852
|
exit_code: null,
|
|
682
853
|
timed_out: false,
|
|
683
854
|
pid
|
|
684
|
-
}
|
|
855
|
+
}
|
|
856
|
+
};
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
let buf = "";
|
|
860
|
+
let pending = "";
|
|
861
|
+
let timedOut = false;
|
|
862
|
+
const timers = [];
|
|
863
|
+
const timer = setTimeout(() => {
|
|
864
|
+
timedOut = true;
|
|
865
|
+
if (isWin) {
|
|
866
|
+
try {
|
|
867
|
+
child.kill();
|
|
868
|
+
} catch {
|
|
869
|
+
}
|
|
870
|
+
} else {
|
|
871
|
+
try {
|
|
872
|
+
child.kill("SIGTERM");
|
|
873
|
+
const killTimer = setTimeout(() => {
|
|
874
|
+
try {
|
|
875
|
+
child.kill("SIGKILL");
|
|
876
|
+
} catch {
|
|
877
|
+
}
|
|
878
|
+
}, 2e3);
|
|
879
|
+
timers.push(killTimer);
|
|
880
|
+
} catch {
|
|
881
|
+
}
|
|
685
882
|
}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
883
|
+
}, timeoutMs);
|
|
884
|
+
timers.push(timer);
|
|
885
|
+
timer.unref?.();
|
|
886
|
+
const queue = [];
|
|
887
|
+
let resolveNext = null;
|
|
888
|
+
const push = (c) => {
|
|
889
|
+
if (resolveNext) {
|
|
890
|
+
const r = resolveNext;
|
|
891
|
+
resolveNext = null;
|
|
892
|
+
r(c);
|
|
893
|
+
} else {
|
|
894
|
+
queue.push(c);
|
|
895
|
+
}
|
|
896
|
+
};
|
|
897
|
+
const next = () => new Promise((resolve2) => {
|
|
898
|
+
const c = queue.shift();
|
|
899
|
+
if (c) resolve2(c);
|
|
900
|
+
else resolveNext = resolve2;
|
|
901
|
+
});
|
|
902
|
+
let lastFlush = Date.now();
|
|
903
|
+
const flush = () => {
|
|
904
|
+
if (pending.length === 0) return null;
|
|
905
|
+
const text = pending;
|
|
906
|
+
pending = "";
|
|
907
|
+
lastFlush = Date.now();
|
|
908
|
+
return text;
|
|
909
|
+
};
|
|
910
|
+
child.stdout?.on("data", (chunk) => {
|
|
911
|
+
const text = chunk.toString();
|
|
912
|
+
buf += text;
|
|
913
|
+
pending += text;
|
|
914
|
+
push({ kind: "data", text });
|
|
915
|
+
});
|
|
916
|
+
child.stderr?.on("data", (chunk) => {
|
|
917
|
+
const text = chunk.toString();
|
|
918
|
+
buf += text;
|
|
919
|
+
pending += text;
|
|
920
|
+
push({ kind: "data", text });
|
|
921
|
+
});
|
|
922
|
+
child.on("error", (err) => {
|
|
923
|
+
for (const t of timers) clearTimeout(t);
|
|
924
|
+
push({ kind: "error", err });
|
|
925
|
+
});
|
|
926
|
+
child.on("close", (code) => {
|
|
927
|
+
for (const t of timers) clearTimeout(t);
|
|
928
|
+
push({ kind: "end", code });
|
|
929
|
+
});
|
|
930
|
+
try {
|
|
931
|
+
while (true) {
|
|
932
|
+
const c = await next();
|
|
933
|
+
if (c.kind === "error") throw c.err;
|
|
934
|
+
if (c.kind === "end") {
|
|
935
|
+
const remainder = flush();
|
|
936
|
+
if (remainder !== null) {
|
|
937
|
+
yield { type: "partial_output", text: remainder };
|
|
707
938
|
}
|
|
939
|
+
const cleaned = stripAnsi(buf).replace(/\r\n?/g, "\n");
|
|
940
|
+
yield {
|
|
941
|
+
type: "final",
|
|
942
|
+
output: {
|
|
943
|
+
output: truncateMiddle(cleaned, MAX_OUTPUT),
|
|
944
|
+
exit_code: c.code,
|
|
945
|
+
timed_out: timedOut
|
|
946
|
+
}
|
|
947
|
+
};
|
|
948
|
+
return;
|
|
708
949
|
}
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
child.on("error", (err) => {
|
|
719
|
-
for (const t of timers) clearTimeout(t);
|
|
720
|
-
reject(err);
|
|
721
|
-
});
|
|
722
|
-
child.on("close", (code) => {
|
|
723
|
-
for (const t of timers) clearTimeout(t);
|
|
724
|
-
const cleaned = stripAnsi(buf).replace(/\r\n?/g, "\n");
|
|
725
|
-
resolve2({
|
|
726
|
-
output: truncateMiddle(cleaned, MAX_OUTPUT),
|
|
727
|
-
exit_code: code,
|
|
728
|
-
timed_out: timedOut
|
|
729
|
-
});
|
|
730
|
-
});
|
|
731
|
-
});
|
|
950
|
+
const now = Date.now();
|
|
951
|
+
if (pending.length >= STREAM_FLUSH_BYTES || now - lastFlush >= STREAM_FLUSH_INTERVAL_MS) {
|
|
952
|
+
const text = flush();
|
|
953
|
+
if (text) yield { type: "partial_output", text };
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
} finally {
|
|
957
|
+
for (const t of timers) clearTimeout(t);
|
|
958
|
+
}
|
|
732
959
|
}
|
|
733
960
|
};
|
|
734
961
|
var ALLOWED_COMMANDS = {
|
|
@@ -940,7 +1167,15 @@ var fetchTool = {
|
|
|
940
1167
|
},
|
|
941
1168
|
required: ["url"]
|
|
942
1169
|
},
|
|
943
|
-
async execute(input,
|
|
1170
|
+
async execute(input, ctx, opts) {
|
|
1171
|
+
let final;
|
|
1172
|
+
for await (const ev of fetchTool.executeStream(input, ctx, opts)) {
|
|
1173
|
+
if (ev.type === "final") final = ev.output;
|
|
1174
|
+
}
|
|
1175
|
+
if (!final) throw new Error("fetch: stream ended without final event");
|
|
1176
|
+
return final;
|
|
1177
|
+
},
|
|
1178
|
+
async *executeStream(input, _ctx, opts) {
|
|
944
1179
|
if (!input?.url) throw new Error("fetch: url is required");
|
|
945
1180
|
const u = new URL(input.url);
|
|
946
1181
|
if (u.protocol !== "https:" && u.protocol !== "http:") {
|
|
@@ -950,6 +1185,7 @@ var fetchTool = {
|
|
|
950
1185
|
throw new Error("fetch: http:// blocked (HTTPS required by default)");
|
|
951
1186
|
}
|
|
952
1187
|
await assertNotPrivate(u.hostname);
|
|
1188
|
+
yield { type: "log", text: `GET ${input.url}` };
|
|
953
1189
|
const ctrl = new AbortController();
|
|
954
1190
|
const timer = setTimeout(() => ctrl.abort(new Error("fetch timeout")), TIMEOUT_MS2);
|
|
955
1191
|
const combined = combineSignals(opts.signal, ctrl.signal);
|
|
@@ -959,16 +1195,29 @@ var fetchTool = {
|
|
|
959
1195
|
if (/^image\/|^audio\/|^video\/|application\/octet-stream/.test(ct)) {
|
|
960
1196
|
throw new Error(`fetch: refusing to read binary content-type "${ct}"`);
|
|
961
1197
|
}
|
|
1198
|
+
yield { type: "log", text: `HTTP ${res.status} ${ct}`, data: { status: res.status, contentType: ct } };
|
|
962
1199
|
const reader = res.body?.getReader();
|
|
963
1200
|
let received = 0;
|
|
964
1201
|
const chunks = [];
|
|
1202
|
+
let pendingBytes = 0;
|
|
1203
|
+
const FLUSH_AT = 4 * 1024;
|
|
965
1204
|
if (reader) {
|
|
966
1205
|
for (; ; ) {
|
|
967
1206
|
const { value, done } = await reader.read();
|
|
968
1207
|
if (done) break;
|
|
969
1208
|
if (!value) continue;
|
|
970
1209
|
received += value.byteLength;
|
|
1210
|
+
pendingBytes += value.byteLength;
|
|
971
1211
|
chunks.push(value);
|
|
1212
|
+
if (pendingBytes >= FLUSH_AT) {
|
|
1213
|
+
const recent = Buffer.from(value).toString("utf8");
|
|
1214
|
+
yield {
|
|
1215
|
+
type: "partial_output",
|
|
1216
|
+
text: recent,
|
|
1217
|
+
data: { received }
|
|
1218
|
+
};
|
|
1219
|
+
pendingBytes = 0;
|
|
1220
|
+
}
|
|
972
1221
|
if (received > MAX_BYTES2) break;
|
|
973
1222
|
}
|
|
974
1223
|
}
|
|
@@ -979,11 +1228,14 @@ var fetchTool = {
|
|
|
979
1228
|
else if (format === "markdown" && ct.includes("text/html")) content = htmlToMarkdown(text);
|
|
980
1229
|
else if (ct.includes("application/json")) content = prettyJson(text);
|
|
981
1230
|
else content = text;
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
1231
|
+
yield {
|
|
1232
|
+
type: "final",
|
|
1233
|
+
output: {
|
|
1234
|
+
content: truncateMiddle(content, MAX_BYTES2),
|
|
1235
|
+
status: res.status,
|
|
1236
|
+
content_type: ct,
|
|
1237
|
+
url: res.url
|
|
1238
|
+
}
|
|
987
1239
|
};
|
|
988
1240
|
} finally {
|
|
989
1241
|
clearTimeout(timer);
|
|
@@ -1083,20 +1335,39 @@ var searchTool = {
|
|
|
1083
1335
|
},
|
|
1084
1336
|
required: ["query"]
|
|
1085
1337
|
},
|
|
1086
|
-
async execute(input,
|
|
1338
|
+
async execute(input, ctx, opts) {
|
|
1339
|
+
let final;
|
|
1340
|
+
for await (const ev of searchTool.executeStream(input, ctx, opts)) {
|
|
1341
|
+
if (ev.type === "final") final = ev.output;
|
|
1342
|
+
}
|
|
1343
|
+
if (!final) throw new Error("search: stream ended without final event");
|
|
1344
|
+
return final;
|
|
1345
|
+
},
|
|
1346
|
+
async *executeStream(input, _ctx, opts) {
|
|
1087
1347
|
if (!input?.query) throw new Error("search: query is required");
|
|
1088
1348
|
const num = Math.max(1, Math.min(input.num_results ?? DEFAULT_NUM, MAX_RESULTS));
|
|
1089
1349
|
const source = input.source ?? "duckduckgo";
|
|
1350
|
+
yield { type: "log", text: `Querying ${source} for "${input.query}"\u2026`, data: { source, query: input.query } };
|
|
1351
|
+
let output;
|
|
1090
1352
|
switch (source) {
|
|
1091
1353
|
case "duckduckgo":
|
|
1092
|
-
|
|
1354
|
+
output = await duckduckgoSearch(input.query, num, opts.signal);
|
|
1355
|
+
break;
|
|
1093
1356
|
case "google":
|
|
1094
|
-
|
|
1357
|
+
output = await googleSearch(input.query, num, opts.signal);
|
|
1358
|
+
break;
|
|
1095
1359
|
case "bing":
|
|
1096
|
-
|
|
1360
|
+
output = await bingSearch(input.query, num, opts.signal);
|
|
1361
|
+
break;
|
|
1097
1362
|
default:
|
|
1098
1363
|
throw new Error(`search: unknown source "${source}"`);
|
|
1099
1364
|
}
|
|
1365
|
+
yield {
|
|
1366
|
+
type: "partial_output",
|
|
1367
|
+
text: `${output.results.length} results from ${output.source}`,
|
|
1368
|
+
data: { count: output.results.length }
|
|
1369
|
+
};
|
|
1370
|
+
yield { type: "final", output };
|
|
1100
1371
|
}
|
|
1101
1372
|
};
|
|
1102
1373
|
async function duckduckgoSearch(query2, num, signal) {
|
|
@@ -1110,21 +1381,26 @@ async function duckduckgoSearch(query2, num, signal) {
|
|
|
1110
1381
|
truncated: results.length >= num
|
|
1111
1382
|
};
|
|
1112
1383
|
}
|
|
1384
|
+
function takeFrom(iter, max) {
|
|
1385
|
+
const out = [];
|
|
1386
|
+
for (const item of iter) {
|
|
1387
|
+
if (out.length >= max) break;
|
|
1388
|
+
out.push(item);
|
|
1389
|
+
}
|
|
1390
|
+
return out;
|
|
1391
|
+
}
|
|
1113
1392
|
function parseDuckDuckGo(html, num) {
|
|
1114
1393
|
const results = [];
|
|
1115
1394
|
const snippetRegex = /<a class="result-link"[^>]+href="([^"]+)"[^>]*>([^<]+)<\/a>/gi;
|
|
1116
1395
|
const snippet2Regex = /<a class="result-snippet"[^>]*>([^<]+)<\/a>/gi;
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
if (!match[1]) continue;
|
|
1126
|
-
snippetMatches.push(stripTags2(match[1]));
|
|
1127
|
-
}
|
|
1396
|
+
const linkMatches = takeFrom(
|
|
1397
|
+
[...html.matchAll(snippetRegex)].filter((m) => m[1] && m[2]).map((m) => ({ url: m[1], title: stripTags2(m[2]) })),
|
|
1398
|
+
num
|
|
1399
|
+
);
|
|
1400
|
+
const snippetMatches = takeFrom(
|
|
1401
|
+
[...html.matchAll(snippet2Regex)].filter((m) => m[1]).map((m) => stripTags2(m[1])),
|
|
1402
|
+
num
|
|
1403
|
+
);
|
|
1128
1404
|
for (let i = 0; i < linkMatches.length && i < num; i++) {
|
|
1129
1405
|
const entry = linkMatches[i];
|
|
1130
1406
|
results.push({
|
|
@@ -1152,23 +1428,18 @@ function parseGoogleResults(html, num) {
|
|
|
1152
1428
|
const titleRegex = /<h3[^>]*class="[^"]*DKV84"[^>]*>([^<]+)<\/h3>/gi;
|
|
1153
1429
|
const urlRegex = /<cite[^>]*>([^<]+)<\/cite>/gi;
|
|
1154
1430
|
const snippetRegex = /<span[^>]*class="[^"]*aXCZ0b[^>]*>([^<]+)<\/span>/gi;
|
|
1155
|
-
const titles =
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
const snippets = [];
|
|
1168
|
-
while ((m = snippetRegex.exec(html)) !== null && snippets.length < num) {
|
|
1169
|
-
if (!m[1]) continue;
|
|
1170
|
-
snippets.push(stripTags2(m[1]));
|
|
1171
|
-
}
|
|
1431
|
+
const titles = takeFrom(
|
|
1432
|
+
[...html.matchAll(titleRegex)].filter((m) => m[1]).map((m) => stripTags2(m[1])),
|
|
1433
|
+
num
|
|
1434
|
+
);
|
|
1435
|
+
const urls = takeFrom(
|
|
1436
|
+
[...html.matchAll(urlRegex)].filter((m) => m[1]).map((m) => stripTags2(m[1]).replace(/^\*(https?:\/\/[^\s]+).*$/, "$1")).filter((u) => u.startsWith("http")),
|
|
1437
|
+
num
|
|
1438
|
+
);
|
|
1439
|
+
const snippets = takeFrom(
|
|
1440
|
+
[...html.matchAll(snippetRegex)].filter((m) => m[1]).map((m) => stripTags2(m[1])),
|
|
1441
|
+
num
|
|
1442
|
+
);
|
|
1172
1443
|
for (let i = 0; i < Math.min(titles.length, num); i++) {
|
|
1173
1444
|
results.push({
|
|
1174
1445
|
title: titles[i] ?? "",
|
|
@@ -1194,17 +1465,14 @@ function parseBingResults(html, num) {
|
|
|
1194
1465
|
const results = [];
|
|
1195
1466
|
const titleRegex = /<h2[^>]*>\s*<a[^>]+href="([^"]+)"[^>]*>([^<]+)<\/a>\s*<\/h2>/gi;
|
|
1196
1467
|
const snippetRegex = /<p[^>]*class="[^"]*b_paractl[^"]*"[^>]*>([^<]+)<\/p>/gi;
|
|
1197
|
-
const entries =
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
if (!m[1]) continue;
|
|
1206
|
-
snippets.push(stripTags2(m[1]));
|
|
1207
|
-
}
|
|
1468
|
+
const entries = takeFrom(
|
|
1469
|
+
[...html.matchAll(titleRegex)].filter((m) => m[1] && m[2]).map((m) => ({ url: m[1], title: stripTags2(m[2]) })),
|
|
1470
|
+
num
|
|
1471
|
+
);
|
|
1472
|
+
const snippets = takeFrom(
|
|
1473
|
+
[...html.matchAll(snippetRegex)].filter((m) => m[1]).map((m) => stripTags2(m[1])),
|
|
1474
|
+
num
|
|
1475
|
+
);
|
|
1208
1476
|
for (let i = 0; i < entries.length; i++) {
|
|
1209
1477
|
results.push({
|
|
1210
1478
|
title: entries[i]?.title ?? "",
|
|
@@ -1365,11 +1633,11 @@ function findGitDir(cwd) {
|
|
|
1365
1633
|
let dir = cwd;
|
|
1366
1634
|
for (let i = 0; i < 20; i++) {
|
|
1367
1635
|
try {
|
|
1368
|
-
const stat9 =
|
|
1636
|
+
const stat9 = statSync(`${dir}/.git`);
|
|
1369
1637
|
if (stat9.isDirectory()) return dir;
|
|
1370
1638
|
} catch {
|
|
1371
1639
|
}
|
|
1372
|
-
const parent =
|
|
1640
|
+
const parent = dirname(dir);
|
|
1373
1641
|
if (parent === dir) break;
|
|
1374
1642
|
dir = parent;
|
|
1375
1643
|
}
|
|
@@ -1531,8 +1799,7 @@ function runPatch(args, cwd, signal) {
|
|
|
1531
1799
|
function extractPatchedFiles(output) {
|
|
1532
1800
|
const files = [];
|
|
1533
1801
|
const re = /patching file (.+)/gi;
|
|
1534
|
-
|
|
1535
|
-
while ((m = re.exec(output)) !== null) {
|
|
1802
|
+
for (const m of output.matchAll(re)) {
|
|
1536
1803
|
if (m[1]) files.push(m[1]);
|
|
1537
1804
|
}
|
|
1538
1805
|
return files;
|
|
@@ -1803,7 +2070,15 @@ var treeTool = {
|
|
|
1803
2070
|
}
|
|
1804
2071
|
}
|
|
1805
2072
|
},
|
|
1806
|
-
async execute(input, ctx) {
|
|
2073
|
+
async execute(input, ctx, opts) {
|
|
2074
|
+
let final;
|
|
2075
|
+
for await (const ev of treeTool.executeStream(input, ctx, opts)) {
|
|
2076
|
+
if (ev.type === "final") final = ev.output;
|
|
2077
|
+
}
|
|
2078
|
+
if (!final) throw new Error("tree: stream ended without final event");
|
|
2079
|
+
return final;
|
|
2080
|
+
},
|
|
2081
|
+
async *executeStream(input, ctx) {
|
|
1807
2082
|
const basePath = input.path ? safeResolve(input.path, ctx) : ctx.cwd;
|
|
1808
2083
|
const maxDepth = input.depth ?? 3;
|
|
1809
2084
|
const showFiles = input.show_files ?? true;
|
|
@@ -1812,9 +2087,22 @@ var treeTool = {
|
|
|
1812
2087
|
const exclude = /* @__PURE__ */ new Set([...DEFAULT_IGNORE4, ...input.exclude ?? []]);
|
|
1813
2088
|
const filterGlob = input.glob;
|
|
1814
2089
|
const lines = [basePath];
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
2090
|
+
const totals = { totalFiles: { value: 0 }, totalDirs: { value: 0 } };
|
|
2091
|
+
const queue = [];
|
|
2092
|
+
const FLUSH_EVERY = 200;
|
|
2093
|
+
let lastEmittedTotal = 0;
|
|
2094
|
+
const tickProgress = () => {
|
|
2095
|
+
const seen = totals.totalFiles.value + totals.totalDirs.value;
|
|
2096
|
+
if (seen - lastEmittedTotal >= FLUSH_EVERY) {
|
|
2097
|
+
queue.push({
|
|
2098
|
+
type: "metric",
|
|
2099
|
+
text: `${seen} entries`,
|
|
2100
|
+
data: { files: totals.totalFiles.value, dirs: totals.totalDirs.value }
|
|
2101
|
+
});
|
|
2102
|
+
lastEmittedTotal = seen;
|
|
2103
|
+
}
|
|
2104
|
+
};
|
|
2105
|
+
const walkPromise = walkDir(basePath, 0, {
|
|
1818
2106
|
maxDepth,
|
|
1819
2107
|
exclude,
|
|
1820
2108
|
showFiles,
|
|
@@ -1824,19 +2112,38 @@ var treeTool = {
|
|
|
1824
2112
|
lines,
|
|
1825
2113
|
prefix: "",
|
|
1826
2114
|
isLast: true,
|
|
1827
|
-
totalFiles:
|
|
1828
|
-
totalDirs:
|
|
2115
|
+
totalFiles: totals.totalFiles,
|
|
2116
|
+
totalDirs: totals.totalDirs,
|
|
2117
|
+
onProgress: tickProgress
|
|
1829
2118
|
});
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
2119
|
+
let walkDone = false;
|
|
2120
|
+
walkPromise.finally(() => {
|
|
2121
|
+
walkDone = true;
|
|
2122
|
+
});
|
|
2123
|
+
while (!walkDone || queue.length > 0) {
|
|
2124
|
+
if (queue.length > 0) {
|
|
2125
|
+
yield queue.shift();
|
|
2126
|
+
} else {
|
|
2127
|
+
await Promise.race([
|
|
2128
|
+
walkPromise,
|
|
2129
|
+
new Promise((r) => setTimeout(r, 50))
|
|
2130
|
+
]).catch(() => void 0);
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
await walkPromise;
|
|
2134
|
+
yield {
|
|
2135
|
+
type: "final",
|
|
2136
|
+
output: {
|
|
2137
|
+
tree: lines.join("\n"),
|
|
2138
|
+
total_files: totals.totalFiles.value,
|
|
2139
|
+
total_dirs: totals.totalDirs.value,
|
|
2140
|
+
truncated: false,
|
|
2141
|
+
path: basePath
|
|
2142
|
+
}
|
|
1836
2143
|
};
|
|
1837
2144
|
}
|
|
1838
2145
|
};
|
|
1839
|
-
async function walkDir(dir,
|
|
2146
|
+
async function walkDir(dir, depth, opts) {
|
|
1840
2147
|
const entries = await fs4.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
1841
2148
|
const filtered = entries.filter((e) => {
|
|
1842
2149
|
if (!opts.showHidden && e.name.startsWith(".")) return false;
|
|
@@ -1848,6 +2155,7 @@ async function walkDir(dir, name, depth, opts) {
|
|
|
1848
2155
|
const fileCount = filtered.filter((e) => e.isFile()).length;
|
|
1849
2156
|
opts.totalDirs.value += dirCount;
|
|
1850
2157
|
opts.totalFiles.value += fileCount;
|
|
2158
|
+
opts.onProgress?.();
|
|
1851
2159
|
}
|
|
1852
2160
|
const items = filtered.sort((a, b) => {
|
|
1853
2161
|
if (a.isDirectory() && !b.isDirectory()) return -1;
|
|
@@ -1866,7 +2174,7 @@ async function walkDir(dir, name, depth, opts) {
|
|
|
1866
2174
|
opts.lines.push(opts.prefix + branch + displayName);
|
|
1867
2175
|
if (entry.isDirectory() && (opts.maxDepth === 0 || depth < opts.maxDepth)) {
|
|
1868
2176
|
const childPrefix = opts.prefix + connector;
|
|
1869
|
-
await walkDir(path.join(dir, entry.name),
|
|
2177
|
+
await walkDir(path.join(dir, entry.name), depth + 1, {
|
|
1870
2178
|
...opts,
|
|
1871
2179
|
prefix: childPrefix,
|
|
1872
2180
|
isLast
|
|
@@ -1874,6 +2182,8 @@ async function walkDir(dir, name, depth, opts) {
|
|
|
1874
2182
|
}
|
|
1875
2183
|
}
|
|
1876
2184
|
}
|
|
2185
|
+
|
|
2186
|
+
// src/lint.ts
|
|
1877
2187
|
var lintTool = {
|
|
1878
2188
|
name: "lint",
|
|
1879
2189
|
description: "Run a linter on files. Auto-detects biome, eslint, or tslint. Use `fix` to auto-fix issues.",
|
|
@@ -1898,25 +2208,59 @@ var lintTool = {
|
|
|
1898
2208
|
}
|
|
1899
2209
|
},
|
|
1900
2210
|
async execute(input, ctx, opts) {
|
|
2211
|
+
let final;
|
|
2212
|
+
for await (const ev of lintTool.executeStream(input, ctx, opts)) {
|
|
2213
|
+
if (ev.type === "final") final = ev.output;
|
|
2214
|
+
}
|
|
2215
|
+
if (!final) throw new Error("lint: stream ended without final event");
|
|
2216
|
+
return final;
|
|
2217
|
+
},
|
|
2218
|
+
async *executeStream(input, ctx, opts) {
|
|
1901
2219
|
const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
|
|
1902
2220
|
const linter = input.linter ?? "auto";
|
|
1903
2221
|
const detected = linter === "auto" ? await detectLinter(cwd) : linter;
|
|
1904
2222
|
if (!detected) {
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
2223
|
+
yield {
|
|
2224
|
+
type: "final",
|
|
2225
|
+
output: {
|
|
2226
|
+
linter: "none",
|
|
2227
|
+
files_checked: 0,
|
|
2228
|
+
errors: 0,
|
|
2229
|
+
warnings: 0,
|
|
2230
|
+
output: "No linter found (biome.json, .eslintrc, tslint.json)",
|
|
2231
|
+
fix_applied: false,
|
|
2232
|
+
truncated: false
|
|
2233
|
+
}
|
|
1913
2234
|
};
|
|
2235
|
+
return;
|
|
1914
2236
|
}
|
|
1915
|
-
|
|
2237
|
+
yield { type: "log", text: `Running ${detected}\u2026`, data: { linter: detected } };
|
|
2238
|
+
const args = ["lint"];
|
|
2239
|
+
if (input.fix) args.push("--write");
|
|
2240
|
+
if (input.files) {
|
|
2241
|
+
const files = Array.isArray(input.files) ? input.files : input.files.split(",");
|
|
2242
|
+
args.push("--", ...files.map((f) => f.trim()));
|
|
2243
|
+
}
|
|
2244
|
+
const cmd = detected === "biome" ? "biome" : detected;
|
|
2245
|
+
const result = yield* spawnStream({ cmd, args, cwd, signal: opts.signal, maxBytes: 1e5 });
|
|
2246
|
+
const errors = (result.stdout.match(/error/g) || []).length;
|
|
2247
|
+
const warnings = (result.stdout.match(/warning/g) || []).length;
|
|
2248
|
+
yield {
|
|
2249
|
+
type: "final",
|
|
2250
|
+
output: {
|
|
2251
|
+
linter: detected,
|
|
2252
|
+
files_checked: input.files ? Array.isArray(input.files) ? input.files.length : input.files.split(",").length : 0,
|
|
2253
|
+
errors,
|
|
2254
|
+
warnings,
|
|
2255
|
+
output: result.stdout,
|
|
2256
|
+
fix_applied: input.fix ?? false,
|
|
2257
|
+
truncated: result.truncated
|
|
2258
|
+
}
|
|
2259
|
+
};
|
|
1916
2260
|
}
|
|
1917
2261
|
};
|
|
1918
2262
|
async function detectLinter(cwd) {
|
|
1919
|
-
const { stat: stat9 } =
|
|
2263
|
+
const { stat: stat9 } = await import('fs/promises');
|
|
1920
2264
|
const checks = ["biome.json", ".eslintrc.json", "tslint.json", ".eslintrc.js", "tsconfig.json"];
|
|
1921
2265
|
for (const f of checks) {
|
|
1922
2266
|
try {
|
|
@@ -1929,42 +2273,8 @@ async function detectLinter(cwd) {
|
|
|
1929
2273
|
}
|
|
1930
2274
|
return "biome";
|
|
1931
2275
|
}
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
if (input.fix) args.push("--write");
|
|
1935
|
-
if (input.files) {
|
|
1936
|
-
const files = Array.isArray(input.files) ? input.files : input.files.split(",");
|
|
1937
|
-
args.push("--", ...files.map((f) => f.trim()));
|
|
1938
|
-
}
|
|
1939
|
-
const result = await runCommand2(linter === "biome" ? "biome" : linter, args, cwd, signal);
|
|
1940
|
-
const errors = (result.stdout.match(/error/g) || []).length;
|
|
1941
|
-
const warnings = (result.stdout.match(/warning/g) || []).length;
|
|
1942
|
-
return {
|
|
1943
|
-
linter,
|
|
1944
|
-
files_checked: input.files ? Array.isArray(input.files) ? input.files.length : input.files.split(",").length : 0,
|
|
1945
|
-
errors,
|
|
1946
|
-
warnings,
|
|
1947
|
-
output: result.stdout,
|
|
1948
|
-
fix_applied: input.fix ?? false,
|
|
1949
|
-
truncated: result.truncated
|
|
1950
|
-
};
|
|
1951
|
-
}
|
|
1952
|
-
function runCommand2(cmd, args, cwd, signal) {
|
|
1953
|
-
return new Promise((resolve2) => {
|
|
1954
|
-
let stdout = "";
|
|
1955
|
-
let stderr = "";
|
|
1956
|
-
const MAX = 1e5;
|
|
1957
|
-
const child = spawn(cmd, args, { cwd, signal, stdio: ["ignore", "pipe", "pipe"] });
|
|
1958
|
-
child.stdout?.on("data", (c) => {
|
|
1959
|
-
if (stdout.length < MAX) stdout += c.toString();
|
|
1960
|
-
});
|
|
1961
|
-
child.stderr?.on("data", (c) => {
|
|
1962
|
-
if (stderr.length < MAX) stderr += c.toString();
|
|
1963
|
-
});
|
|
1964
|
-
child.on("close", (code) => resolve2({ stdout, stderr, exitCode: code ?? 0, truncated: stdout.length >= MAX }));
|
|
1965
|
-
child.on("error", (e) => resolve2({ stdout: "", stderr: e.message, exitCode: 1, truncated: false }));
|
|
1966
|
-
});
|
|
1967
|
-
}
|
|
2276
|
+
|
|
2277
|
+
// src/format.ts
|
|
1968
2278
|
var formatTool = {
|
|
1969
2279
|
name: "format",
|
|
1970
2280
|
description: "Format files with biome or prettier. Use `check` to verify without modifying.",
|
|
@@ -1992,23 +2302,59 @@ var formatTool = {
|
|
|
1992
2302
|
}
|
|
1993
2303
|
},
|
|
1994
2304
|
async execute(input, ctx, opts) {
|
|
2305
|
+
let final;
|
|
2306
|
+
for await (const ev of formatTool.executeStream(input, ctx, opts)) {
|
|
2307
|
+
if (ev.type === "final") final = ev.output;
|
|
2308
|
+
}
|
|
2309
|
+
if (!final) throw new Error("format: stream ended without final event");
|
|
2310
|
+
return final;
|
|
2311
|
+
},
|
|
2312
|
+
async *executeStream(input, ctx, opts) {
|
|
1995
2313
|
const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
|
|
1996
2314
|
const fixer = input.fixer ?? "auto";
|
|
1997
2315
|
const detected = fixer === "auto" ? await detectFixer(cwd) : fixer;
|
|
1998
2316
|
if (!detected) {
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2317
|
+
yield {
|
|
2318
|
+
type: "final",
|
|
2319
|
+
output: {
|
|
2320
|
+
fixer: "none",
|
|
2321
|
+
files_checked: 0,
|
|
2322
|
+
files_changed: 0,
|
|
2323
|
+
output: "No formatter found (biome.json, .prettierrc)",
|
|
2324
|
+
truncated: false
|
|
2325
|
+
}
|
|
2005
2326
|
};
|
|
2327
|
+
return;
|
|
2328
|
+
}
|
|
2329
|
+
yield { type: "log", text: `Running ${detected}\u2026`, data: { fixer: detected, check: !!input.check } };
|
|
2330
|
+
const args = ["format", "--write"];
|
|
2331
|
+
if (input.check) args[args.length - 1] = "--check";
|
|
2332
|
+
if (input.files) {
|
|
2333
|
+
const files = Array.isArray(input.files) ? input.files : input.files.split(",");
|
|
2334
|
+
args.push("--", ...files.map((f) => f.trim()));
|
|
2006
2335
|
}
|
|
2007
|
-
|
|
2336
|
+
const result = yield* spawnStream({
|
|
2337
|
+
cmd: detected,
|
|
2338
|
+
args,
|
|
2339
|
+
cwd,
|
|
2340
|
+
signal: opts.signal,
|
|
2341
|
+
maxBytes: 1e5
|
|
2342
|
+
});
|
|
2343
|
+
const changed = (result.stdout.match(/changed/g) || []).length;
|
|
2344
|
+
yield {
|
|
2345
|
+
type: "final",
|
|
2346
|
+
output: {
|
|
2347
|
+
fixer: detected,
|
|
2348
|
+
files_checked: 0,
|
|
2349
|
+
files_changed: changed,
|
|
2350
|
+
output: result.stdout || result.stderr || result.error || "",
|
|
2351
|
+
truncated: result.truncated
|
|
2352
|
+
}
|
|
2353
|
+
};
|
|
2008
2354
|
}
|
|
2009
2355
|
};
|
|
2010
2356
|
async function detectFixer(cwd) {
|
|
2011
|
-
const { stat: stat9 } =
|
|
2357
|
+
const { stat: stat9 } = await import('fs/promises');
|
|
2012
2358
|
try {
|
|
2013
2359
|
await stat9(`${cwd}/biome.json`);
|
|
2014
2360
|
return "biome";
|
|
@@ -2021,46 +2367,6 @@ async function detectFixer(cwd) {
|
|
|
2021
2367
|
}
|
|
2022
2368
|
}
|
|
2023
2369
|
}
|
|
2024
|
-
async function runFormatter(fixer, input, cwd, signal) {
|
|
2025
|
-
const args = ["format", "--write"];
|
|
2026
|
-
if (input.check) args[args.length - 1] = "--check";
|
|
2027
|
-
if (input.files) {
|
|
2028
|
-
const files = Array.isArray(input.files) ? input.files : input.files.split(",");
|
|
2029
|
-
args.push("--", ...files.map((f) => f.trim()));
|
|
2030
|
-
}
|
|
2031
|
-
return runCommand3(fixer, args, cwd, signal);
|
|
2032
|
-
}
|
|
2033
|
-
function runCommand3(cmd, args, cwd, signal) {
|
|
2034
|
-
return new Promise((resolve2) => {
|
|
2035
|
-
let stdout = "";
|
|
2036
|
-
let stderr = "";
|
|
2037
|
-
const MAX = 1e5;
|
|
2038
|
-
const child = spawn(cmd, args, { cwd, signal, stdio: ["ignore", "pipe", "pipe"] });
|
|
2039
|
-
child.stdout?.on("data", (c) => {
|
|
2040
|
-
if (stdout.length < MAX) stdout += c.toString();
|
|
2041
|
-
});
|
|
2042
|
-
child.stderr?.on("data", (c) => {
|
|
2043
|
-
if (stderr.length < MAX) stderr += c.toString();
|
|
2044
|
-
});
|
|
2045
|
-
child.on("close", (code) => {
|
|
2046
|
-
const changed = (stdout.match(/changed/g) || []).length;
|
|
2047
|
-
resolve2({
|
|
2048
|
-
fixer: cmd,
|
|
2049
|
-
files_checked: 0,
|
|
2050
|
-
files_changed: changed,
|
|
2051
|
-
output: stdout || stderr,
|
|
2052
|
-
truncated: stdout.length >= MAX
|
|
2053
|
-
});
|
|
2054
|
-
});
|
|
2055
|
-
child.on("error", (e) => resolve2({
|
|
2056
|
-
fixer: cmd,
|
|
2057
|
-
files_checked: 0,
|
|
2058
|
-
files_changed: 0,
|
|
2059
|
-
output: e.message,
|
|
2060
|
-
truncated: false
|
|
2061
|
-
}));
|
|
2062
|
-
});
|
|
2063
|
-
}
|
|
2064
2370
|
var typecheckTool = {
|
|
2065
2371
|
name: "typecheck",
|
|
2066
2372
|
description: "Run TypeScript type checking with `tsc --noEmit`. Checks for type errors without compiling.",
|
|
@@ -2084,19 +2390,52 @@ var typecheckTool = {
|
|
|
2084
2390
|
}
|
|
2085
2391
|
},
|
|
2086
2392
|
async execute(input, ctx, opts) {
|
|
2393
|
+
let final;
|
|
2394
|
+
for await (const ev of typecheckTool.executeStream(input, ctx, opts)) {
|
|
2395
|
+
if (ev.type === "final") final = ev.output;
|
|
2396
|
+
}
|
|
2397
|
+
if (!final) throw new Error("typecheck: stream ended without final event");
|
|
2398
|
+
return final;
|
|
2399
|
+
},
|
|
2400
|
+
async *executeStream(input, ctx, opts) {
|
|
2087
2401
|
const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
|
|
2402
|
+
let args;
|
|
2403
|
+
let project;
|
|
2088
2404
|
if (input.all) {
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2405
|
+
args = ["--noEmit"];
|
|
2406
|
+
project = "workspace";
|
|
2407
|
+
} else {
|
|
2408
|
+
const tsconfig = input.project ? safeResolve(input.project, ctx) : await findTsConfig(cwd);
|
|
2409
|
+
args = ["--noEmit"];
|
|
2410
|
+
if (input.strict) args.push("--strict");
|
|
2411
|
+
if (tsconfig) args.push("--project", tsconfig);
|
|
2412
|
+
project = tsconfig ?? "default";
|
|
2413
|
+
}
|
|
2414
|
+
yield { type: "log", text: `tsc ${args.join(" ")}`, data: { project } };
|
|
2415
|
+
const result = yield* spawnStream({
|
|
2416
|
+
cmd: "npx",
|
|
2417
|
+
args: ["tsc", ...args],
|
|
2418
|
+
cwd,
|
|
2419
|
+
signal: opts.signal,
|
|
2420
|
+
maxBytes: 2e5
|
|
2421
|
+
});
|
|
2422
|
+
const errors = (result.stdout.match(/error TS/g) || []).length;
|
|
2423
|
+
const warnings = (result.stdout.match(/warning/g) || []).length;
|
|
2424
|
+
yield {
|
|
2425
|
+
type: "final",
|
|
2426
|
+
output: {
|
|
2427
|
+
project,
|
|
2428
|
+
exit_code: result.exitCode,
|
|
2429
|
+
errors,
|
|
2430
|
+
warnings,
|
|
2431
|
+
output: result.stdout || result.stderr || result.error || "",
|
|
2432
|
+
truncated: result.truncated
|
|
2433
|
+
}
|
|
2434
|
+
};
|
|
2096
2435
|
}
|
|
2097
2436
|
};
|
|
2098
2437
|
async function findTsConfig(cwd) {
|
|
2099
|
-
const { stat: stat9 } =
|
|
2438
|
+
const { stat: stat9 } = await import('fs/promises');
|
|
2100
2439
|
const candidates = ["tsconfig.json", "tsconfig.base.json"];
|
|
2101
2440
|
for (const f of candidates) {
|
|
2102
2441
|
try {
|
|
@@ -2107,39 +2446,6 @@ async function findTsConfig(cwd) {
|
|
|
2107
2446
|
}
|
|
2108
2447
|
return null;
|
|
2109
2448
|
}
|
|
2110
|
-
function runTsc(args, cwd, signal, project) {
|
|
2111
|
-
return new Promise((resolve2) => {
|
|
2112
|
-
let stdout = "";
|
|
2113
|
-
let stderr = "";
|
|
2114
|
-
const MAX = 2e5;
|
|
2115
|
-
const child = spawn("npx", ["tsc", ...args], { cwd, signal, stdio: ["ignore", "pipe", "pipe"] });
|
|
2116
|
-
child.stdout?.on("data", (c) => {
|
|
2117
|
-
if (stdout.length < MAX) stdout += c.toString();
|
|
2118
|
-
});
|
|
2119
|
-
child.stderr?.on("data", (c) => {
|
|
2120
|
-
if (stderr.length < MAX) stderr += c.toString();
|
|
2121
|
-
});
|
|
2122
|
-
child.on("close", (code) => {
|
|
2123
|
-
const errors = (stdout.match(/error TS/g) || []).length;
|
|
2124
|
-
resolve2({
|
|
2125
|
-
project,
|
|
2126
|
-
exit_code: code ?? 0,
|
|
2127
|
-
errors,
|
|
2128
|
-
warnings: (stdout.match(/warning/g) || []).length,
|
|
2129
|
-
output: stdout || stderr,
|
|
2130
|
-
truncated: stdout.length >= MAX
|
|
2131
|
-
});
|
|
2132
|
-
});
|
|
2133
|
-
child.on("error", (e) => resolve2({
|
|
2134
|
-
project,
|
|
2135
|
-
exit_code: 1,
|
|
2136
|
-
errors: 0,
|
|
2137
|
-
warnings: 0,
|
|
2138
|
-
output: e.message,
|
|
2139
|
-
truncated: false
|
|
2140
|
-
}));
|
|
2141
|
-
});
|
|
2142
|
-
}
|
|
2143
2449
|
var testTool = {
|
|
2144
2450
|
name: "test",
|
|
2145
2451
|
description: "Run tests with vitest, jest, or mocha. Returns pass/fail counts and output.",
|
|
@@ -2159,46 +2465,57 @@ var testTool = {
|
|
|
2159
2465
|
enum: ["vitest", "jest", "mocha", "auto"],
|
|
2160
2466
|
description: "Test runner (default: auto-detect)"
|
|
2161
2467
|
},
|
|
2162
|
-
watch: {
|
|
2163
|
-
|
|
2164
|
-
description: "Run in watch mode (default: false)"
|
|
2165
|
-
},
|
|
2166
|
-
coverage: {
|
|
2167
|
-
type: "boolean",
|
|
2168
|
-
description: "Generate coverage report (default: false)"
|
|
2169
|
-
},
|
|
2468
|
+
watch: { type: "boolean", description: "Run in watch mode (default: false)" },
|
|
2469
|
+
coverage: { type: "boolean", description: "Generate coverage report (default: false)" },
|
|
2170
2470
|
cwd: { type: "string", description: "Working directory (default: cwd)" },
|
|
2171
|
-
grep: {
|
|
2172
|
-
|
|
2173
|
-
description: "Filter tests by name pattern (default: none)"
|
|
2174
|
-
},
|
|
2175
|
-
timeout: {
|
|
2176
|
-
type: "integer",
|
|
2177
|
-
description: "Test timeout in ms (default: 30000)"
|
|
2178
|
-
}
|
|
2471
|
+
grep: { type: "string", description: "Filter tests by name pattern (default: none)" },
|
|
2472
|
+
timeout: { type: "integer", description: "Test timeout in ms (default: 30000)" }
|
|
2179
2473
|
}
|
|
2180
2474
|
},
|
|
2181
2475
|
async execute(input, ctx, opts) {
|
|
2476
|
+
let final;
|
|
2477
|
+
for await (const ev of testTool.executeStream(input, ctx, opts)) {
|
|
2478
|
+
if (ev.type === "final") final = ev.output;
|
|
2479
|
+
}
|
|
2480
|
+
if (!final) throw new Error("test: stream ended without final event");
|
|
2481
|
+
return final;
|
|
2482
|
+
},
|
|
2483
|
+
async *executeStream(input, ctx, opts) {
|
|
2182
2484
|
const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
|
|
2183
2485
|
const runner = input.runner ?? "auto";
|
|
2184
2486
|
const detected = runner === "auto" ? await detectRunner(cwd) : runner;
|
|
2185
2487
|
if (!detected) {
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2488
|
+
yield {
|
|
2489
|
+
type: "final",
|
|
2490
|
+
output: {
|
|
2491
|
+
runner: "none",
|
|
2492
|
+
exit_code: 1,
|
|
2493
|
+
tests_run: 0,
|
|
2494
|
+
passed: 0,
|
|
2495
|
+
failed: 0,
|
|
2496
|
+
duration_ms: 0,
|
|
2497
|
+
output: "No test runner found (vitest.config.ts, jest.config.js, .mocharc.json)",
|
|
2498
|
+
truncated: false
|
|
2499
|
+
}
|
|
2195
2500
|
};
|
|
2501
|
+
return;
|
|
2196
2502
|
}
|
|
2197
|
-
|
|
2503
|
+
yield { type: "log", text: `Running ${detected}\u2026`, data: { runner: detected } };
|
|
2504
|
+
const start = Date.now();
|
|
2505
|
+
const args = buildArgs2(detected, input);
|
|
2506
|
+
const result = yield* spawnStream({
|
|
2507
|
+
cmd: detected,
|
|
2508
|
+
args,
|
|
2509
|
+
cwd,
|
|
2510
|
+
signal: opts.signal,
|
|
2511
|
+
maxBytes: 2e5
|
|
2512
|
+
});
|
|
2513
|
+
const duration = Date.now() - start;
|
|
2514
|
+
yield { type: "final", output: parseResult(detected, result, duration) };
|
|
2198
2515
|
}
|
|
2199
2516
|
};
|
|
2200
2517
|
async function detectRunner(cwd) {
|
|
2201
|
-
const { stat: stat9 } =
|
|
2518
|
+
const { stat: stat9 } = await import('fs/promises');
|
|
2202
2519
|
const candidates = ["vitest.config.ts", "jest.config.js", ".mocharc.json"];
|
|
2203
2520
|
for (const f of candidates) {
|
|
2204
2521
|
try {
|
|
@@ -2211,13 +2528,6 @@ async function detectRunner(cwd) {
|
|
|
2211
2528
|
}
|
|
2212
2529
|
return "vitest";
|
|
2213
2530
|
}
|
|
2214
|
-
async function runTests(runner, input, cwd, signal) {
|
|
2215
|
-
const start = Date.now();
|
|
2216
|
-
const args = buildArgs2(runner, input);
|
|
2217
|
-
const result = await runCommand4(runner, args, cwd, signal);
|
|
2218
|
-
const duration = Date.now() - start;
|
|
2219
|
-
return parseResult(runner, result, duration);
|
|
2220
|
-
}
|
|
2221
2531
|
function buildArgs2(runner, input) {
|
|
2222
2532
|
const args = [];
|
|
2223
2533
|
const timeout = input.timeout ?? 3e4;
|
|
@@ -2251,22 +2561,6 @@ function buildArgs2(runner, input) {
|
|
|
2251
2561
|
}
|
|
2252
2562
|
return args;
|
|
2253
2563
|
}
|
|
2254
|
-
function runCommand4(cmd, args, cwd, signal) {
|
|
2255
|
-
return new Promise((resolve2) => {
|
|
2256
|
-
let stdout = "";
|
|
2257
|
-
let stderr = "";
|
|
2258
|
-
const MAX = 2e5;
|
|
2259
|
-
const child = spawn(cmd, args, { cwd, signal, stdio: ["ignore", "pipe", "pipe"] });
|
|
2260
|
-
child.stdout?.on("data", (c) => {
|
|
2261
|
-
if (stdout.length < MAX) stdout += c.toString();
|
|
2262
|
-
});
|
|
2263
|
-
child.stderr?.on("data", (c) => {
|
|
2264
|
-
if (stderr.length < MAX) stderr += c.toString();
|
|
2265
|
-
});
|
|
2266
|
-
child.on("close", (code) => resolve2({ stdout, stderr, exitCode: code ?? 0, truncated: stdout.length >= MAX }));
|
|
2267
|
-
child.on("error", (e) => resolve2({ stdout: "", stderr: e.message, exitCode: 1, truncated: false }));
|
|
2268
|
-
});
|
|
2269
|
-
}
|
|
2270
2564
|
function parseResult(runner, result, duration) {
|
|
2271
2565
|
const out = result.stdout + result.stderr;
|
|
2272
2566
|
let tests_run = 0;
|
|
@@ -2275,16 +2569,16 @@ function parseResult(runner, result, duration) {
|
|
|
2275
2569
|
if (runner === "vitest") {
|
|
2276
2570
|
const passedMatch = out.match(/(\d+) passed/);
|
|
2277
2571
|
const failedMatch = out.match(/(\d+) failed/);
|
|
2278
|
-
if (passedMatch?.[1]) passed = parseInt(passedMatch[1], 10);
|
|
2279
|
-
if (failedMatch?.[1]) failed = parseInt(failedMatch[1], 10);
|
|
2572
|
+
if (passedMatch?.[1]) passed = Number.parseInt(passedMatch[1], 10);
|
|
2573
|
+
if (failedMatch?.[1]) failed = Number.parseInt(failedMatch[1], 10);
|
|
2280
2574
|
tests_run = passed + failed;
|
|
2281
2575
|
} else if (runner === "jest") {
|
|
2282
2576
|
const suitesMatch = out.match(/Test Suites:\s+(\d+)\s+total/);
|
|
2283
2577
|
const passedMatch = out.match(/Tests:\s+(\d+)\s+passed/);
|
|
2284
2578
|
const failedMatch = out.match(/Tests:\s+(\d+)\s+failed/);
|
|
2285
|
-
tests_run = parseInt(suitesMatch?.[1] ?? "0", 10);
|
|
2286
|
-
passed = parseInt(passedMatch?.[1] ?? "0", 10);
|
|
2287
|
-
failed = parseInt(failedMatch?.[1] ?? "0", 10);
|
|
2579
|
+
tests_run = Number.parseInt(suitesMatch?.[1] ?? "0", 10);
|
|
2580
|
+
passed = Number.parseInt(passedMatch?.[1] ?? "0", 10);
|
|
2581
|
+
failed = Number.parseInt(failedMatch?.[1] ?? "0", 10);
|
|
2288
2582
|
}
|
|
2289
2583
|
return {
|
|
2290
2584
|
runner,
|
|
@@ -2293,10 +2587,12 @@ function parseResult(runner, result, duration) {
|
|
|
2293
2587
|
passed,
|
|
2294
2588
|
failed,
|
|
2295
2589
|
duration_ms: duration,
|
|
2296
|
-
output: result.stdout,
|
|
2590
|
+
output: result.stdout || result.error || "",
|
|
2297
2591
|
truncated: result.truncated
|
|
2298
2592
|
};
|
|
2299
2593
|
}
|
|
2594
|
+
|
|
2595
|
+
// src/install.ts
|
|
2300
2596
|
var installTool = {
|
|
2301
2597
|
name: "install",
|
|
2302
2598
|
description: "Install npm packages. Detects pnpm/npm/yarn and uses the right package manager.",
|
|
@@ -2322,8 +2618,17 @@ var installTool = {
|
|
|
2322
2618
|
}
|
|
2323
2619
|
},
|
|
2324
2620
|
async execute(input, ctx, opts) {
|
|
2621
|
+
let final;
|
|
2622
|
+
for await (const ev of installTool.executeStream(input, ctx, opts)) {
|
|
2623
|
+
if (ev.type === "final") final = ev.output;
|
|
2624
|
+
}
|
|
2625
|
+
if (!final) throw new Error("install: stream ended without final event");
|
|
2626
|
+
return final;
|
|
2627
|
+
},
|
|
2628
|
+
async *executeStream(input, ctx, opts) {
|
|
2325
2629
|
const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
|
|
2326
2630
|
const pkgManager = await detectPackageManager(cwd);
|
|
2631
|
+
yield { type: "log", text: `Resolving with ${pkgManager}\u2026`, data: { phase: "resolve" } };
|
|
2327
2632
|
const save = input.save === "dev" ? "-D" : input.save === "optional" ? "-O" : "";
|
|
2328
2633
|
const globalFlag = input.global ? ["-g"] : [];
|
|
2329
2634
|
const args = [];
|
|
@@ -2336,15 +2641,30 @@ var installTool = {
|
|
|
2336
2641
|
} else {
|
|
2337
2642
|
args.push("install", ...globalFlag);
|
|
2338
2643
|
}
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2644
|
+
const pkgList = input.packages ? (Array.isArray(input.packages) ? input.packages : input.packages.split(",")).map((p) => p.trim()) : [];
|
|
2645
|
+
if (pkgList.length > 0) args.push(...pkgList);
|
|
2646
|
+
yield { type: "log", text: `Fetching ${pkgList.length || "all"} packages\u2026`, data: { phase: "fetch" } };
|
|
2647
|
+
const result = yield* spawnStream({
|
|
2648
|
+
cmd: pkgManager,
|
|
2649
|
+
args,
|
|
2650
|
+
cwd,
|
|
2651
|
+
signal: opts.signal,
|
|
2652
|
+
maxBytes: 1e5
|
|
2653
|
+
});
|
|
2654
|
+
yield {
|
|
2655
|
+
type: "final",
|
|
2656
|
+
output: {
|
|
2657
|
+
packages: pkgList,
|
|
2658
|
+
exit_code: result.exitCode,
|
|
2659
|
+
output: result.stdout || result.stderr || result.error || "",
|
|
2660
|
+
dry_run: args.includes("--dry-run"),
|
|
2661
|
+
truncated: result.truncated
|
|
2662
|
+
}
|
|
2663
|
+
};
|
|
2344
2664
|
}
|
|
2345
2665
|
};
|
|
2346
2666
|
async function detectPackageManager(cwd) {
|
|
2347
|
-
const { stat: stat9 } =
|
|
2667
|
+
const { stat: stat9 } = await import('fs/promises');
|
|
2348
2668
|
try {
|
|
2349
2669
|
await stat9(`${cwd}/pnpm-lock.yaml`);
|
|
2350
2670
|
return "pnpm";
|
|
@@ -2357,34 +2677,8 @@ async function detectPackageManager(cwd) {
|
|
|
2357
2677
|
}
|
|
2358
2678
|
}
|
|
2359
2679
|
}
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
let stdout = "";
|
|
2363
|
-
let stderr = "";
|
|
2364
|
-
const MAX = 1e5;
|
|
2365
|
-
const child = spawn(manager, args, { cwd, signal, stdio: ["ignore", "pipe", "pipe"] });
|
|
2366
|
-
child.stdout?.on("data", (c) => {
|
|
2367
|
-
if (stdout.length < MAX) stdout += c.toString();
|
|
2368
|
-
});
|
|
2369
|
-
child.stderr?.on("data", (c) => {
|
|
2370
|
-
if (stderr.length < MAX) stderr += c.toString();
|
|
2371
|
-
});
|
|
2372
|
-
child.on("close", (code) => resolve2({
|
|
2373
|
-
packages,
|
|
2374
|
-
exit_code: code ?? 0,
|
|
2375
|
-
output: stdout || stderr,
|
|
2376
|
-
dry_run: args.includes("--dry-run"),
|
|
2377
|
-
truncated: stdout.length >= MAX
|
|
2378
|
-
}));
|
|
2379
|
-
child.on("error", (e) => resolve2({
|
|
2380
|
-
packages,
|
|
2381
|
-
exit_code: 1,
|
|
2382
|
-
output: e.message,
|
|
2383
|
-
dry_run: false,
|
|
2384
|
-
truncated: false
|
|
2385
|
-
}));
|
|
2386
|
-
});
|
|
2387
|
-
}
|
|
2680
|
+
|
|
2681
|
+
// src/audit.ts
|
|
2388
2682
|
var auditTool = {
|
|
2389
2683
|
name: "audit",
|
|
2390
2684
|
description: "Run npm/pnpm security audit. Returns vulnerabilities sorted by severity.",
|
|
@@ -2401,30 +2695,40 @@ var auditTool = {
|
|
|
2401
2695
|
enum: ["low", "moderate", "high", "critical"],
|
|
2402
2696
|
description: "Minimum severity level to report"
|
|
2403
2697
|
},
|
|
2404
|
-
fix: {
|
|
2405
|
-
|
|
2406
|
-
description: "Attempt to fix vulnerabilities (default: false)"
|
|
2407
|
-
},
|
|
2408
|
-
packages: {
|
|
2409
|
-
type: "string",
|
|
2410
|
-
description: "Specific package(s) to audit (comma-separated)"
|
|
2411
|
-
}
|
|
2698
|
+
fix: { type: "boolean", description: "Attempt to fix vulnerabilities (default: false)" },
|
|
2699
|
+
packages: { type: "string", description: "Specific package(s) to audit (comma-separated)" }
|
|
2412
2700
|
}
|
|
2413
2701
|
},
|
|
2414
2702
|
async execute(input, ctx, opts) {
|
|
2703
|
+
let final;
|
|
2704
|
+
for await (const ev of auditTool.executeStream(input, ctx, opts)) {
|
|
2705
|
+
if (ev.type === "final") final = ev.output;
|
|
2706
|
+
}
|
|
2707
|
+
if (!final) throw new Error("audit: stream ended without final event");
|
|
2708
|
+
return final;
|
|
2709
|
+
},
|
|
2710
|
+
async *executeStream(input, ctx, opts) {
|
|
2415
2711
|
const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
|
|
2416
2712
|
const manager = await detectManager(cwd);
|
|
2713
|
+
yield { type: "log", text: `Auditing with ${manager}\u2026`, data: { manager } };
|
|
2417
2714
|
const args = ["audit", "--json"];
|
|
2418
2715
|
if (input.fix) args.push("--fix");
|
|
2419
2716
|
if (input.packages) {
|
|
2420
2717
|
const pkgs = Array.isArray(input.packages) ? input.packages : input.packages.split(",");
|
|
2421
2718
|
args.push(...pkgs.map((p) => p.trim()));
|
|
2422
2719
|
}
|
|
2423
|
-
|
|
2720
|
+
const result = yield* spawnStream({
|
|
2721
|
+
cmd: manager,
|
|
2722
|
+
args,
|
|
2723
|
+
cwd,
|
|
2724
|
+
signal: opts.signal,
|
|
2725
|
+
maxBytes: 1e5
|
|
2726
|
+
});
|
|
2727
|
+
yield { type: "final", output: parseAuditOutput(result.stdout, result.exitCode) };
|
|
2424
2728
|
}
|
|
2425
2729
|
};
|
|
2426
2730
|
async function detectManager(cwd) {
|
|
2427
|
-
const { stat: stat9 } =
|
|
2731
|
+
const { stat: stat9 } = await import('fs/promises');
|
|
2428
2732
|
try {
|
|
2429
2733
|
await stat9(`${cwd}/pnpm-lock.yaml`);
|
|
2430
2734
|
return "pnpm";
|
|
@@ -2437,32 +2741,6 @@ async function detectManager(cwd) {
|
|
|
2437
2741
|
}
|
|
2438
2742
|
return "npm";
|
|
2439
2743
|
}
|
|
2440
|
-
function runAudit(manager, args, cwd, signal) {
|
|
2441
|
-
return new Promise((resolve2) => {
|
|
2442
|
-
let stdout = "";
|
|
2443
|
-
let stderr = "";
|
|
2444
|
-
const MAX = 1e5;
|
|
2445
|
-
const child = spawn(manager, args, { cwd, signal, stdio: ["ignore", "pipe", "pipe"] });
|
|
2446
|
-
child.stdout?.on("data", (c) => {
|
|
2447
|
-
if (stdout.length < MAX) stdout += c.toString();
|
|
2448
|
-
});
|
|
2449
|
-
child.stderr?.on("data", (c) => {
|
|
2450
|
-
if (stderr.length < MAX) stderr += c.toString();
|
|
2451
|
-
});
|
|
2452
|
-
child.on("close", (code) => {
|
|
2453
|
-
const result = parseAuditOutput(stdout, code ?? 0);
|
|
2454
|
-
resolve2(result);
|
|
2455
|
-
});
|
|
2456
|
-
child.on("error", (e) => resolve2({
|
|
2457
|
-
exit_code: 1,
|
|
2458
|
-
vulnerabilities: [],
|
|
2459
|
-
total: 0,
|
|
2460
|
-
summary: e.message,
|
|
2461
|
-
output: e.message,
|
|
2462
|
-
truncated: false
|
|
2463
|
-
}));
|
|
2464
|
-
});
|
|
2465
|
-
}
|
|
2466
2744
|
function parseAuditOutput(json, exitCode) {
|
|
2467
2745
|
if (!json) {
|
|
2468
2746
|
return {
|
|
@@ -2717,7 +2995,7 @@ async function fileLogs(path10, lines, filterRe, stream) {
|
|
|
2717
2995
|
const allLines = [];
|
|
2718
2996
|
const rl = createInterface({
|
|
2719
2997
|
input: createReadStream(path10),
|
|
2720
|
-
crlfDelay:
|
|
2998
|
+
crlfDelay: Number.POSITIVE_INFINITY
|
|
2721
2999
|
});
|
|
2722
3000
|
for await (const line of rl) {
|
|
2723
3001
|
if (filterRe && !filterRe.test(line)) continue;
|
|
@@ -2859,30 +3137,27 @@ function processFile(content, absPath, style, overwrite, target) {
|
|
|
2859
3137
|
const arrowRegex = /(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\(([^)]*)\)\s*=>/g;
|
|
2860
3138
|
const classRegex = /class\s+(\w+)/g;
|
|
2861
3139
|
const typeRegex = /(?:type|interface)\s+(\w+)\s*[=<]/g;
|
|
2862
|
-
let match;
|
|
2863
3140
|
const allMatches = [];
|
|
2864
3141
|
if (target === "all" || target === "function") {
|
|
2865
|
-
|
|
2866
|
-
if (!
|
|
2867
|
-
allMatches.push({ name:
|
|
3142
|
+
for (const m of content.matchAll(functionRegex)) {
|
|
3143
|
+
if (!m[1]) continue;
|
|
3144
|
+
allMatches.push({ name: m[1], sig: m[2] ?? "", type: "function", line: content.slice(0, m.index).split("\n").length });
|
|
2868
3145
|
}
|
|
2869
|
-
|
|
2870
|
-
if (!
|
|
2871
|
-
allMatches.push({ name:
|
|
3146
|
+
for (const m of content.matchAll(arrowRegex)) {
|
|
3147
|
+
if (!m[1]) continue;
|
|
3148
|
+
allMatches.push({ name: m[1], sig: m[2] ?? "", type: "arrow", line: content.slice(0, m.index).split("\n").length });
|
|
2872
3149
|
}
|
|
2873
3150
|
}
|
|
2874
3151
|
if (target === "all" || target === "class") {
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
allMatches.push({ name: match[1], sig: "", type: "class", line: content.slice(0, match.index).split("\n").length });
|
|
3152
|
+
for (const m of content.matchAll(classRegex)) {
|
|
3153
|
+
if (!m[1]) continue;
|
|
3154
|
+
allMatches.push({ name: m[1], sig: "", type: "class", line: content.slice(0, m.index).split("\n").length });
|
|
2879
3155
|
}
|
|
2880
3156
|
}
|
|
2881
3157
|
if (target === "all" || target === "type") {
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
allMatches.push({ name: match[1], sig: match[0] ?? "", type: "type", line: content.slice(0, match.index).split("\n").length });
|
|
3158
|
+
for (const m of content.matchAll(typeRegex)) {
|
|
3159
|
+
if (!m[1]) continue;
|
|
3160
|
+
allMatches.push({ name: m[1], sig: m[0] ?? "", type: "type", line: content.slice(0, m.index).split("\n").length });
|
|
2886
3161
|
}
|
|
2887
3162
|
}
|
|
2888
3163
|
for (const m of allMatches) {
|
|
@@ -2902,7 +3177,7 @@ var BUILT_IN_TEMPLATES = {
|
|
|
2902
3177
|
files: {
|
|
2903
3178
|
"package.json": JSON.stringify({
|
|
2904
3179
|
name: "{{name}}",
|
|
2905
|
-
version: "0.
|
|
3180
|
+
version: "0.1.1",
|
|
2906
3181
|
type: "module",
|
|
2907
3182
|
main: "./dist/index.js",
|
|
2908
3183
|
scripts: { build: "tsc", test: "vitest run" },
|
|
@@ -2932,7 +3207,7 @@ describe('hello', () => {
|
|
|
2932
3207
|
files: {
|
|
2933
3208
|
"package.json": JSON.stringify({
|
|
2934
3209
|
name: "{{name}}",
|
|
2935
|
-
version: "0.
|
|
3210
|
+
version: "0.1.1",
|
|
2936
3211
|
type: "module",
|
|
2937
3212
|
bin: { "{{name}}": "./src/index.js" },
|
|
2938
3213
|
scripts: { build: "tsc", start: "node dist/index.js" }
|
|
@@ -3097,7 +3372,7 @@ var toolSearchTool = {
|
|
|
3097
3372
|
const limit = Math.min(input.limit ?? 20, 100);
|
|
3098
3373
|
const tools = ctx.tools;
|
|
3099
3374
|
const query2 = input.query?.toLowerCase() ?? "";
|
|
3100
|
-
|
|
3375
|
+
const filtered = tools.filter((t) => {
|
|
3101
3376
|
if (query2 && !t.name.toLowerCase().includes(query2) && !t.description.toLowerCase().includes(query2)) {
|
|
3102
3377
|
return false;
|
|
3103
3378
|
}
|
|
@@ -3549,7 +3824,7 @@ ${mode.description}`
|
|
|
3549
3824
|
};
|
|
3550
3825
|
}
|
|
3551
3826
|
|
|
3552
|
-
// src/
|
|
3827
|
+
// src/builtin.ts
|
|
3553
3828
|
var builtinTools = [
|
|
3554
3829
|
readTool,
|
|
3555
3830
|
writeTool,
|