@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.
Files changed (103) hide show
  1. package/dist/audit.d.ts +25 -0
  2. package/dist/audit.js +209 -0
  3. package/dist/audit.js.map +1 -0
  4. package/dist/bash.d.ts +16 -0
  5. package/dist/bash.js +180 -0
  6. package/dist/bash.js.map +1 -0
  7. package/dist/batch-tool-use.d.ts +26 -0
  8. package/dist/batch-tool-use.js +106 -0
  9. package/dist/batch-tool-use.js.map +1 -0
  10. package/dist/builtin.d.ts +5 -0
  11. package/dist/builtin.js +3735 -0
  12. package/dist/builtin.js.map +1 -0
  13. package/dist/diff.d.ts +20 -0
  14. package/dist/diff.js +142 -0
  15. package/dist/diff.js.map +1 -0
  16. package/dist/document.d.ts +27 -0
  17. package/dist/document.js +148 -0
  18. package/dist/document.js.map +1 -0
  19. package/dist/edit.d.ts +22 -0
  20. package/dist/edit.js +138 -0
  21. package/dist/edit.js.map +1 -0
  22. package/dist/exec.d.ts +21 -0
  23. package/dist/exec.js +159 -0
  24. package/dist/exec.js.map +1 -0
  25. package/dist/fetch.d.ts +15 -0
  26. package/dist/fetch.js +213 -0
  27. package/dist/fetch.js.map +1 -0
  28. package/dist/format.d.ts +18 -0
  29. package/dist/format.js +194 -0
  30. package/dist/format.js.map +1 -0
  31. package/dist/git.d.ts +27 -0
  32. package/dist/git.js +174 -0
  33. package/dist/git.js.map +1 -0
  34. package/dist/glob.d.ts +14 -0
  35. package/dist/glob.js +101 -0
  36. package/dist/glob.js.map +1 -0
  37. package/dist/grep.d.ts +20 -0
  38. package/dist/grep.js +264 -0
  39. package/dist/grep.js.map +1 -0
  40. package/dist/index.d.ts +34 -563
  41. package/dist/index.js +717 -442
  42. package/dist/index.js.map +1 -1
  43. package/dist/install.d.ts +19 -0
  44. package/dist/install.js +186 -0
  45. package/dist/install.js.map +1 -0
  46. package/dist/json.d.ts +20 -0
  47. package/dist/json.js +124 -0
  48. package/dist/json.js.map +1 -0
  49. package/dist/lint.d.ts +20 -0
  50. package/dist/lint.js +191 -0
  51. package/dist/lint.js.map +1 -0
  52. package/dist/logs.d.ts +27 -0
  53. package/dist/logs.js +180 -0
  54. package/dist/logs.js.map +1 -0
  55. package/dist/memory.d.ts +22 -0
  56. package/dist/memory.js +53 -0
  57. package/dist/memory.js.map +1 -0
  58. package/dist/mode.d.ts +20 -0
  59. package/dist/mode.js +81 -0
  60. package/dist/mode.js.map +1 -0
  61. package/dist/outdated.d.ts +26 -0
  62. package/dist/outdated.js +138 -0
  63. package/dist/outdated.js.map +1 -0
  64. package/dist/patch.d.ts +18 -0
  65. package/dist/patch.js +101 -0
  66. package/dist/patch.js.map +1 -0
  67. package/dist/read.d.ts +16 -0
  68. package/dist/read.js +81 -0
  69. package/dist/read.js.map +1 -0
  70. package/dist/replace.d.ts +23 -0
  71. package/dist/replace.js +196 -0
  72. package/dist/replace.js.map +1 -0
  73. package/dist/scaffold.d.ts +20 -0
  74. package/dist/scaffold.js +185 -0
  75. package/dist/scaffold.js.map +1 -0
  76. package/dist/search.d.ts +20 -0
  77. package/dist/search.js +212 -0
  78. package/dist/search.js.map +1 -0
  79. package/dist/test.d.ts +24 -0
  80. package/dist/test.js +247 -0
  81. package/dist/test.js.map +1 -0
  82. package/dist/todo.d.ts +12 -0
  83. package/dist/todo.js +53 -0
  84. package/dist/todo.js.map +1 -0
  85. package/dist/tool-help.d.ts +23 -0
  86. package/dist/tool-help.js +122 -0
  87. package/dist/tool-help.js.map +1 -0
  88. package/dist/tool-search.d.ts +22 -0
  89. package/dist/tool-search.js +70 -0
  90. package/dist/tool-search.js.map +1 -0
  91. package/dist/tool-use.d.ts +16 -0
  92. package/dist/tool-use.js +79 -0
  93. package/dist/tool-use.js.map +1 -0
  94. package/dist/tree.d.ts +21 -0
  95. package/dist/tree.js +176 -0
  96. package/dist/tree.js.map +1 -0
  97. package/dist/typecheck.d.ts +19 -0
  98. package/dist/typecheck.js +181 -0
  99. package/dist/typecheck.js.map +1 -0
  100. package/dist/write.d.ts +15 -0
  101. package/dist/write.js +77 -0
  102. package/dist/write.js.map +1 -0
  103. package/package.json +137 -4
@@ -0,0 +1,25 @@
1
+ import { Tool } from '@wrongstack/core';
2
+
3
+ interface AuditInput {
4
+ cwd?: string;
5
+ level?: 'low' | 'moderate' | 'high' | 'critical';
6
+ fix?: boolean;
7
+ packages?: string | string[];
8
+ }
9
+ interface AuditVulnerability {
10
+ severity: string;
11
+ package: string;
12
+ title: string;
13
+ url: string;
14
+ }
15
+ interface AuditOutput {
16
+ exit_code: number;
17
+ vulnerabilities: AuditVulnerability[];
18
+ total: number;
19
+ summary: string;
20
+ output: string;
21
+ truncated: boolean;
22
+ }
23
+ declare const auditTool: Tool<AuditInput, AuditOutput>;
24
+
25
+ export { auditTool };
package/dist/audit.js ADDED
@@ -0,0 +1,209 @@
1
+ import * as path from 'path';
2
+ import { spawn } from 'child_process';
3
+
4
+ // src/_util.ts
5
+ function resolvePath(input, ctx) {
6
+ return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.cwd, input);
7
+ }
8
+ function ensureInsideRoot(absPath, ctx) {
9
+ const root = path.resolve(ctx.projectRoot);
10
+ const target = path.resolve(absPath);
11
+ const rel = path.relative(root, target);
12
+ if (rel.startsWith("..") || path.isAbsolute(rel)) {
13
+ throw new Error(`Path "${absPath}" is outside project root "${root}"`);
14
+ }
15
+ return target;
16
+ }
17
+ function safeResolve(input, ctx) {
18
+ return ensureInsideRoot(resolvePath(input, ctx), ctx);
19
+ }
20
+ async function* spawnStream(opts) {
21
+ const max = opts.maxBytes;
22
+ const flushAt = opts.flushBytes ?? 4 * 1024;
23
+ let stdout = "";
24
+ let stderr = "";
25
+ let pending = "";
26
+ let error;
27
+ const child = spawn(opts.cmd, opts.args, {
28
+ cwd: opts.cwd,
29
+ signal: opts.signal,
30
+ stdio: ["ignore", "pipe", "pipe"]
31
+ });
32
+ const queue = [];
33
+ let waiter;
34
+ const wake = () => {
35
+ if (waiter) {
36
+ const w = waiter;
37
+ waiter = void 0;
38
+ w();
39
+ }
40
+ };
41
+ child.stdout?.on("data", (c) => {
42
+ const s = c.toString();
43
+ if (stdout.length < max) stdout += s;
44
+ queue.push({ kind: "out", data: s });
45
+ wake();
46
+ });
47
+ child.stderr?.on("data", (c) => {
48
+ const s = c.toString();
49
+ if (stderr.length < max) stderr += s;
50
+ queue.push({ kind: "err", data: s });
51
+ wake();
52
+ });
53
+ child.on("error", (e) => {
54
+ error = e.message;
55
+ queue.push({ kind: "error", data: e.message });
56
+ wake();
57
+ });
58
+ child.on("close", (code) => {
59
+ queue.push({ kind: "close", data: "", code: code ?? 0 });
60
+ wake();
61
+ });
62
+ let exitCode = 0;
63
+ let spawnFailed = false;
64
+ for (; ; ) {
65
+ while (queue.length === 0) {
66
+ await new Promise((resolve2) => {
67
+ waiter = resolve2;
68
+ });
69
+ }
70
+ const chunk = queue.shift();
71
+ if (chunk.kind === "close") {
72
+ if (!spawnFailed) exitCode = chunk.code ?? 0;
73
+ break;
74
+ }
75
+ if (chunk.kind === "error") {
76
+ spawnFailed = true;
77
+ exitCode = 1;
78
+ continue;
79
+ }
80
+ pending += chunk.data;
81
+ if (pending.length >= flushAt) {
82
+ yield { type: "partial_output", text: pending };
83
+ pending = "";
84
+ }
85
+ }
86
+ if (pending.length > 0) {
87
+ yield { type: "partial_output", text: pending };
88
+ }
89
+ return {
90
+ stdout,
91
+ stderr,
92
+ exitCode,
93
+ truncated: stdout.length >= max || stderr.length >= max,
94
+ error
95
+ };
96
+ }
97
+
98
+ // src/audit.ts
99
+ var auditTool = {
100
+ name: "audit",
101
+ description: "Run npm/pnpm security audit. Returns vulnerabilities sorted by severity.",
102
+ usageHint: "Set `level` to filter minimum severity. `fix` attempts auto-fix. `packages` checks specific packages.",
103
+ permission: "confirm",
104
+ mutating: false,
105
+ timeoutMs: 6e4,
106
+ inputSchema: {
107
+ type: "object",
108
+ properties: {
109
+ cwd: { type: "string", description: "Working directory (default: cwd)" },
110
+ level: {
111
+ type: "string",
112
+ enum: ["low", "moderate", "high", "critical"],
113
+ description: "Minimum severity level to report"
114
+ },
115
+ fix: { type: "boolean", description: "Attempt to fix vulnerabilities (default: false)" },
116
+ packages: { type: "string", description: "Specific package(s) to audit (comma-separated)" }
117
+ }
118
+ },
119
+ async execute(input, ctx, opts) {
120
+ let final;
121
+ for await (const ev of auditTool.executeStream(input, ctx, opts)) {
122
+ if (ev.type === "final") final = ev.output;
123
+ }
124
+ if (!final) throw new Error("audit: stream ended without final event");
125
+ return final;
126
+ },
127
+ async *executeStream(input, ctx, opts) {
128
+ const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
129
+ const manager = await detectManager(cwd);
130
+ yield { type: "log", text: `Auditing with ${manager}\u2026`, data: { manager } };
131
+ const args = ["audit", "--json"];
132
+ if (input.fix) args.push("--fix");
133
+ if (input.packages) {
134
+ const pkgs = Array.isArray(input.packages) ? input.packages : input.packages.split(",");
135
+ args.push(...pkgs.map((p) => p.trim()));
136
+ }
137
+ const result = yield* spawnStream({
138
+ cmd: manager,
139
+ args,
140
+ cwd,
141
+ signal: opts.signal,
142
+ maxBytes: 1e5
143
+ });
144
+ yield { type: "final", output: parseAuditOutput(result.stdout, result.exitCode) };
145
+ }
146
+ };
147
+ async function detectManager(cwd) {
148
+ const { stat } = await import('fs/promises');
149
+ try {
150
+ await stat(`${cwd}/pnpm-lock.yaml`);
151
+ return "pnpm";
152
+ } catch {
153
+ }
154
+ try {
155
+ await stat(`${cwd}/yarn.lock`);
156
+ return "yarn";
157
+ } catch {
158
+ }
159
+ return "npm";
160
+ }
161
+ function parseAuditOutput(json, exitCode) {
162
+ if (!json) {
163
+ return {
164
+ exit_code: exitCode,
165
+ vulnerabilities: [],
166
+ total: 0,
167
+ summary: exitCode === 0 ? "No vulnerabilities found" : "Audit failed",
168
+ output: "",
169
+ truncated: false
170
+ };
171
+ }
172
+ try {
173
+ const data = JSON.parse(json);
174
+ const advisories = [];
175
+ const ads = data.advisories ?? {};
176
+ for (const id of Object.keys(ads)) {
177
+ const adv = ads[id];
178
+ advisories.push({
179
+ severity: adv.severity ?? "unknown",
180
+ package: adv.module_name ?? id,
181
+ title: adv.title ?? "Unknown vulnerability",
182
+ url: adv.url ?? ""
183
+ });
184
+ }
185
+ const total = advisories.length;
186
+ const summary = total === 0 ? "No vulnerabilities found" : `Found ${total} vulnerabilities: ${advisories.filter((a) => a.severity === "critical").length} critical, ${advisories.filter((a) => a.severity === "high").length} high`;
187
+ return {
188
+ exit_code: exitCode,
189
+ vulnerabilities: advisories,
190
+ total,
191
+ summary,
192
+ output: json,
193
+ truncated: json.length >= 1e5
194
+ };
195
+ } catch {
196
+ return {
197
+ exit_code: exitCode,
198
+ vulnerabilities: [],
199
+ total: 0,
200
+ summary: "Could not parse audit output",
201
+ output: json,
202
+ truncated: false
203
+ };
204
+ }
205
+ }
206
+
207
+ export { auditTool };
208
+ //# sourceMappingURL=audit.js.map
209
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/_util.ts","../src/audit.ts"],"names":["resolve"],"mappings":";;;;AAIO,SAAS,WAAA,CAAY,OAAe,GAAA,EAAsB;AAC/D,EAAA,OAAY,IAAA,CAAA,UAAA,CAAW,KAAK,CAAA,GAAS,IAAA,CAAA,SAAA,CAAU,KAAK,CAAA,GAAS,IAAA,CAAA,OAAA,CAAQ,GAAA,CAAI,GAAA,EAAK,KAAK,CAAA;AACrF;AAEO,SAAS,gBAAA,CAAiB,SAAiB,GAAA,EAAsB;AACtE,EAAA,MAAM,IAAA,GAAY,IAAA,CAAA,OAAA,CAAQ,GAAA,CAAI,WAAW,CAAA;AACzC,EAAA,MAAM,MAAA,GAAc,aAAQ,OAAO,CAAA;AACnC,EAAA,MAAM,GAAA,GAAW,IAAA,CAAA,QAAA,CAAS,IAAA,EAAM,MAAM,CAAA;AACtC,EAAA,IAAI,IAAI,UAAA,CAAW,IAAI,CAAA,IAAU,IAAA,CAAA,UAAA,CAAW,GAAG,CAAA,EAAG;AAChD,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,MAAA,EAAS,OAAO,CAAA,2BAAA,EAA8B,IAAI,CAAA,CAAA,CAAG,CAAA;AAAA,EACvE;AACA,EAAA,OAAO,MAAA;AACT;AAEO,SAAS,WAAA,CAAY,OAAe,GAAA,EAAsB;AAC/D,EAAA,OAAO,gBAAA,CAAiB,WAAA,CAAY,KAAA,EAAO,GAAG,GAAG,GAAG,CAAA;AACtD;AA6CA,gBAAuB,YACrB,IAAA,EACsD;AACtD,EAAA,MAAM,GAAA,GAAM,KAAK,QAAY;AAC7B,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,UAAA,IAAc,CAAA,GAAI,IAAA;AACvC,EAAA,IAAI,MAAA,GAAS,EAAA;AACb,EAAA,IAAI,MAAA,GAAS,EAAA;AACb,EAAA,IAAI,OAAA,GAAU,EAAA;AACd,EAAA,IAAI,KAAA;AAEJ,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,IAAA,CAAK,GAAA,EAAK,KAAK,IAAA,EAAM;AAAA,IACvC,KAAK,IAAA,CAAK,GAAA;AAAA,IACV,QAAQ,IAAA,CAAK,MAAA;AAAA,IACb,KAAA,EAAO,CAAC,QAAA,EAAU,MAAA,EAAQ,MAAM;AAAA,GACjC,CAAA;AAGD,EAAA,MAAM,QAAiB,EAAC;AACxB,EAAA,IAAI,MAAA;AACJ,EAAA,MAAM,OAAO,MAAM;AACjB,IAAA,IAAI,MAAA,EAAQ;AACV,MAAA,MAAM,CAAA,GAAI,MAAA;AACV,MAAA,MAAA,GAAS,MAAA;AACT,MAAA,CAAA,EAAE;AAAA,IACJ;AAAA,EACF,CAAA;AAEA,EAAA,KAAA,CAAM,MAAA,EAAQ,EAAA,CAAG,MAAA,EAAQ,CAAC,CAAA,KAAM;AAC9B,IAAA,MAAM,CAAA,GAAI,EAAE,QAAA,EAAS;AACrB,IAAA,IAAI,MAAA,CAAO,MAAA,GAAS,GAAA,EAAK,MAAA,IAAU,CAAA;AACnC,IAAA,KAAA,CAAM,KAAK,EAAE,IAAA,EAAM,KAAA,EAAO,IAAA,EAAM,GAAG,CAAA;AACnC,IAAA,IAAA,EAAK;AAAA,EACP,CAAC,CAAA;AACD,EAAA,KAAA,CAAM,MAAA,EAAQ,EAAA,CAAG,MAAA,EAAQ,CAAC,CAAA,KAAM;AAC9B,IAAA,MAAM,CAAA,GAAI,EAAE,QAAA,EAAS;AACrB,IAAA,IAAI,MAAA,CAAO,MAAA,GAAS,GAAA,EAAK,MAAA,IAAU,CAAA;AACnC,IAAA,KAAA,CAAM,KAAK,EAAE,IAAA,EAAM,KAAA,EAAO,IAAA,EAAM,GAAG,CAAA;AACnC,IAAA,IAAA,EAAK;AAAA,EACP,CAAC,CAAA;AACD,EAAA,KAAA,CAAM,EAAA,CAAG,OAAA,EAAS,CAAC,CAAA,KAAM;AACvB,IAAA,KAAA,GAAQ,CAAA,CAAE,OAAA;AACV,IAAA,KAAA,CAAM,KAAK,EAAE,IAAA,EAAM,SAAS,IAAA,EAAM,CAAA,CAAE,SAAS,CAAA;AAC7C,IAAA,IAAA,EAAK;AAAA,EACP,CAAC,CAAA;AACD,EAAA,KAAA,CAAM,EAAA,CAAG,OAAA,EAAS,CAAC,IAAA,KAAS;AAC1B,IAAA,KAAA,CAAM,IAAA,CAAK,EAAE,IAAA,EAAM,OAAA,EAAS,MAAM,EAAA,EAAI,IAAA,EAAM,IAAA,IAAQ,CAAA,EAAG,CAAA;AACvD,IAAA,IAAA,EAAK;AAAA,EACP,CAAC,CAAA;AAED,EAAA,IAAI,QAAA,GAAW,CAAA;AACf,EAAA,IAAI,WAAA,GAAc,KAAA;AAClB,EAAA,WAAS;AACP,IAAA,OAAO,KAAA,CAAM,WAAW,CAAA,EAAG;AACzB,MAAA,MAAM,IAAI,OAAA,CAAc,CAACA,QAAAA,KAAY;AACnC,QAAA,MAAA,GAASA,QAAAA;AAAA,MACX,CAAC,CAAA;AAAA,IACH;AACA,IAAA,MAAM,KAAA,GAAQ,MAAM,KAAA,EAAM;AAC1B,IAAA,IAAI,KAAA,CAAM,SAAS,OAAA,EAAS;AAG1B,MAAA,IAAI,CAAC,WAAA,EAAa,QAAA,GAAW,KAAA,CAAM,IAAA,IAAQ,CAAA;AAC3C,MAAA;AAAA,IACF;AACA,IAAA,IAAI,KAAA,CAAM,SAAS,OAAA,EAAS;AAC1B,MAAA,WAAA,GAAc,IAAA;AACd,MAAA,QAAA,GAAW,CAAA;AAEX,MAAA;AAAA,IACF;AACA,IAAA,OAAA,IAAW,KAAA,CAAM,IAAA;AACjB,IAAA,IAAI,OAAA,CAAQ,UAAU,OAAA,EAAS;AAC7B,MAAA,MAAM,EAAE,IAAA,EAAM,gBAAA,EAAkB,IAAA,EAAM,OAAA,EAAQ;AAC9C,MAAA,OAAA,GAAU,EAAA;AAAA,IACZ;AAAA,EACF;AACA,EAAA,IAAI,OAAA,CAAQ,SAAS,CAAA,EAAG;AACtB,IAAA,MAAM,EAAE,IAAA,EAAM,gBAAA,EAAkB,IAAA,EAAM,OAAA,EAAQ;AAAA,EAChD;AAEA,EAAA,OAAO;AAAA,IACL,MAAA;AAAA,IACA,MAAA;AAAA,IACA,QAAA;AAAA,IACA,SAAA,EAAW,MAAA,CAAO,MAAA,IAAU,GAAA,IAAO,OAAO,MAAA,IAAU,GAAA;AAAA,IACpD;AAAA,GACF;AACF;;;AC9HO,IAAM,SAAA,GAA2C;AAAA,EACtD,IAAA,EAAM,OAAA;AAAA,EACN,WAAA,EACE,0EAAA;AAAA,EACF,SAAA,EACE,uGAAA;AAAA,EACF,UAAA,EAAY,SAAA;AAAA,EACZ,QAAA,EAAU,KAAA;AAAA,EACV,SAAA,EAAW,GAAA;AAAA,EACX,WAAA,EAAa;AAAA,IACX,IAAA,EAAM,QAAA;AAAA,IACN,UAAA,EAAY;AAAA,MACV,GAAA,EAAK,EAAE,IAAA,EAAM,QAAA,EAAU,aAAa,kCAAA,EAAmC;AAAA,MACvE,KAAA,EAAO;AAAA,QACL,IAAA,EAAM,QAAA;AAAA,QACN,IAAA,EAAM,CAAC,KAAA,EAAO,UAAA,EAAY,QAAQ,UAAU,CAAA;AAAA,QAC5C,WAAA,EAAa;AAAA,OACf;AAAA,MACA,GAAA,EAAK,EAAE,IAAA,EAAM,SAAA,EAAW,aAAa,iDAAA,EAAkD;AAAA,MACvF,QAAA,EAAU,EAAE,IAAA,EAAM,QAAA,EAAU,aAAa,gDAAA;AAAiD;AAC5F,GACF;AAAA,EACA,MAAM,OAAA,CAAQ,KAAA,EAAO,GAAA,EAAK,IAAA,EAAM;AAC9B,IAAA,IAAI,KAAA;AACJ,IAAA,WAAA,MAAiB,MAAM,SAAA,CAAU,aAAA,CAAe,KAAA,EAAO,GAAA,EAAK,IAAI,CAAA,EAAG;AACjE,MAAA,IAAI,EAAA,CAAG,IAAA,KAAS,OAAA,EAAS,KAAA,GAAQ,EAAA,CAAG,MAAA;AAAA,IACtC;AACA,IAAA,IAAI,CAAC,KAAA,EAAO,MAAM,IAAI,MAAM,yCAAyC,CAAA;AACrE,IAAA,OAAO,KAAA;AAAA,EACT,CAAA;AAAA,EACA,OAAO,aAAA,CAAc,KAAA,EAAO,GAAA,EAAK,IAAA,EAAoD;AACnF,IAAA,MAAM,GAAA,GAAM,MAAM,GAAA,GAAM,WAAA,CAAY,MAAM,GAAA,EAAK,GAAG,IAAI,GAAA,CAAI,GAAA;AAC1D,IAAA,MAAM,OAAA,GAAU,MAAM,aAAA,CAAc,GAAG,CAAA;AACvC,IAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAO,IAAA,EAAM,CAAA,cAAA,EAAiB,OAAO,CAAA,MAAA,CAAA,EAAK,IAAA,EAAM,EAAE,OAAA,EAAQ,EAAE;AAE1E,IAAA,MAAM,IAAA,GAAO,CAAC,OAAA,EAAS,QAAQ,CAAA;AAC/B,IAAA,IAAI,KAAA,CAAM,GAAA,EAAK,IAAA,CAAK,IAAA,CAAK,OAAO,CAAA;AAChC,IAAA,IAAI,MAAM,QAAA,EAAU;AAClB,MAAA,MAAM,IAAA,GAAO,KAAA,CAAM,OAAA,CAAQ,KAAA,CAAM,QAAQ,CAAA,GAAI,KAAA,CAAM,QAAA,GAAW,KAAA,CAAM,QAAA,CAAS,KAAA,CAAM,GAAG,CAAA;AACtF,MAAA,IAAA,CAAK,IAAA,CAAK,GAAG,IAAA,CAAK,GAAA,CAAI,CAAC,CAAA,KAAc,CAAA,CAAE,IAAA,EAAM,CAAC,CAAA;AAAA,IAChD;AAEA,IAAA,MAAM,MAAA,GAAS,OAAO,WAAA,CAAY;AAAA,MAChC,GAAA,EAAK,OAAA;AAAA,MACL,IAAA;AAAA,MACA,GAAA;AAAA,MACA,QAAQ,IAAA,CAAK,MAAA;AAAA,MACb,QAAA,EAAU;AAAA,KACX,CAAA;AAED,IAAA,MAAM,EAAE,MAAM,OAAA,EAAS,MAAA,EAAQ,iBAAiB,MAAA,CAAO,MAAA,EAAQ,MAAA,CAAO,QAAQ,CAAA,EAAE;AAAA,EAClF;AACF;AAEA,eAAe,cAAc,GAAA,EAA8B;AACzD,EAAA,MAAM,EAAE,IAAA,EAAK,GAAI,MAAM,OAAO,aAAkB,CAAA;AAChD,EAAA,IAAI;AAAE,IAAA,MAAM,IAAA,CAAK,CAAA,EAAG,GAAG,CAAA,eAAA,CAAiB,CAAA;AAAG,IAAA,OAAO,MAAA;AAAA,EAAQ,CAAA,CAAA,MAAQ;AAAA,EAAQ;AAC1E,EAAA,IAAI;AAAE,IAAA,MAAM,IAAA,CAAK,CAAA,EAAG,GAAG,CAAA,UAAA,CAAY,CAAA;AAAG,IAAA,OAAO,MAAA;AAAA,EAAQ,CAAA,CAAA,MAAQ;AAAA,EAAQ;AACrE,EAAA,OAAO,KAAA;AACT;AAEA,SAAS,gBAAA,CAAiB,MAAc,QAAA,EAA+B;AACrE,EAAA,IAAI,CAAC,IAAA,EAAM;AACT,IAAA,OAAO;AAAA,MACL,SAAA,EAAW,QAAA;AAAA,MACX,iBAAiB,EAAC;AAAA,MAClB,KAAA,EAAO,CAAA;AAAA,MACP,OAAA,EAAS,QAAA,KAAa,CAAA,GAAI,0BAAA,GAA6B,cAAA;AAAA,MACvD,MAAA,EAAQ,EAAA;AAAA,MACR,SAAA,EAAW;AAAA,KACb;AAAA,EACF;AAEA,EAAA,IAAI;AACF,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA;AAC5B,IAAA,MAAM,aAAmC,EAAC;AAC1C,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,UAAA,IAAc,EAAC;AAChC,IAAA,KAAA,MAAW,EAAA,IAAM,MAAA,CAAO,IAAA,CAAK,GAAG,CAAA,EAAG;AACjC,MAAA,MAAM,GAAA,GAAM,IAAI,EAAE,CAAA;AAClB,MAAA,UAAA,CAAW,IAAA,CAAK;AAAA,QACd,QAAA,EAAU,IAAI,QAAA,IAAY,SAAA;AAAA,QAC1B,OAAA,EAAS,IAAI,WAAA,IAAe,EAAA;AAAA,QAC5B,KAAA,EAAO,IAAI,KAAA,IAAS,uBAAA;AAAA,QACpB,GAAA,EAAK,IAAI,GAAA,IAAO;AAAA,OACjB,CAAA;AAAA,IACH;AAEA,IAAA,MAAM,QAAQ,UAAA,CAAW,MAAA;AACzB,IAAA,MAAM,OAAA,GAAU,KAAA,KAAU,CAAA,GACtB,0BAAA,GACA,CAAA,MAAA,EAAS,KAAK,CAAA,kBAAA,EAAqB,UAAA,CAAW,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,aAAa,UAAU,CAAA,CAAE,MAAM,CAAA,WAAA,EAAc,UAAA,CAAW,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,QAAA,KAAa,MAAM,CAAA,CAAE,MAAM,CAAA,KAAA,CAAA;AAErK,IAAA,OAAO;AAAA,MACL,SAAA,EAAW,QAAA;AAAA,MACX,eAAA,EAAiB,UAAA;AAAA,MACjB,KAAA;AAAA,MACA,OAAA;AAAA,MACA,MAAA,EAAQ,IAAA;AAAA,MACR,SAAA,EAAW,KAAK,MAAA,IAAU;AAAA,KAC5B;AAAA,EACF,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO;AAAA,MACL,SAAA,EAAW,QAAA;AAAA,MACX,iBAAiB,EAAC;AAAA,MAClB,KAAA,EAAO,CAAA;AAAA,MACP,OAAA,EAAS,8BAAA;AAAA,MACT,MAAA,EAAQ,IAAA;AAAA,MACR,SAAA,EAAW;AAAA,KACb;AAAA,EACF;AACF","file":"audit.js","sourcesContent":["import * as path from 'node:path';\r\nimport { spawn } from 'node:child_process';\r\nimport type { Context, ToolProgressEvent } from '@wrongstack/core';\r\n\r\nexport function resolvePath(input: string, ctx: Context): string {\r\n return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.cwd, input);\r\n}\r\n\r\nexport function ensureInsideRoot(absPath: string, ctx: Context): string {\r\n const root = path.resolve(ctx.projectRoot);\r\n const target = path.resolve(absPath);\r\n const rel = path.relative(root, target);\r\n if (rel.startsWith('..') || path.isAbsolute(rel)) {\r\n throw new Error(`Path \"${absPath}\" is outside project root \"${root}\"`);\r\n }\r\n return target;\r\n}\r\n\r\nexport function safeResolve(input: string, ctx: Context): string {\r\n return ensureInsideRoot(resolvePath(input, ctx), ctx);\r\n}\r\n\r\nexport function truncateMiddle(s: string, max: number): string {\r\n if (Buffer.byteLength(s, 'utf8') <= max) return s;\r\n const half = Math.floor(max / 2);\r\n return (\r\n s.slice(0, half) +\r\n `\\n…[truncated ${Buffer.byteLength(s, 'utf8') - max} bytes from middle]…\\n` +\r\n s.slice(-half)\r\n );\r\n}\r\n\r\nexport function isBinaryBuffer(buf: Buffer): boolean {\r\n const len = Math.min(buf.length, 8192);\r\n for (let i = 0; i < len; i++) {\r\n if (buf[i] === 0) return true;\r\n }\r\n return false;\r\n}\r\n\r\nexport interface SpawnStreamResult {\r\n stdout: string;\r\n stderr: string;\r\n exitCode: number;\r\n truncated: boolean;\r\n error?: string;\r\n}\r\n\r\nexport interface SpawnStreamOptions {\r\n cmd: string;\r\n args: string[];\r\n cwd: string;\r\n signal: AbortSignal;\r\n maxBytes?: number;\r\n /** Bytes of new stdout/stderr to accumulate before yielding a `partial_output` event. */\r\n flushBytes?: number;\r\n}\r\n\r\n/**\r\n * Spawn a child process and yield `partial_output` progress events as\r\n * stdout/stderr arrive (batched by byte threshold), then return the full\r\n * buffered result. Shared between install/lint/format/typecheck/test/audit\r\n * so the TUI live tail sees consistent progress regardless of which tool\r\n * is running.\r\n */\r\nexport async function* spawnStream(\r\n opts: SpawnStreamOptions,\r\n): AsyncGenerator<ToolProgressEvent, SpawnStreamResult> {\r\n const max = opts.maxBytes ?? 200_000;\r\n const flushAt = opts.flushBytes ?? 4 * 1024;\r\n let stdout = '';\r\n let stderr = '';\r\n let pending = '';\r\n let error: string | undefined;\r\n\r\n const child = spawn(opts.cmd, opts.args, {\r\n cwd: opts.cwd,\r\n signal: opts.signal,\r\n stdio: ['ignore', 'pipe', 'pipe'],\r\n });\r\n\r\n type Chunk = { kind: 'out' | 'err' | 'close' | 'error'; data: string; code?: number };\r\n const queue: Chunk[] = [];\r\n let waiter: (() => void) | undefined;\r\n const wake = () => {\r\n if (waiter) {\r\n const w = waiter;\r\n waiter = undefined;\r\n w();\r\n }\r\n };\r\n\r\n child.stdout?.on('data', (c) => {\r\n const s = c.toString();\r\n if (stdout.length < max) stdout += s;\r\n queue.push({ kind: 'out', data: s });\r\n wake();\r\n });\r\n child.stderr?.on('data', (c) => {\r\n const s = c.toString();\r\n if (stderr.length < max) stderr += s;\r\n queue.push({ kind: 'err', data: s });\r\n wake();\r\n });\r\n child.on('error', (e) => {\r\n error = e.message;\r\n queue.push({ kind: 'error', data: e.message });\r\n wake();\r\n });\r\n child.on('close', (code) => {\r\n queue.push({ kind: 'close', data: '', code: code ?? 0 });\r\n wake();\r\n });\r\n\r\n let exitCode = 0;\r\n let spawnFailed = false;\r\n for (;;) {\r\n while (queue.length === 0) {\r\n await new Promise<void>((resolve) => {\r\n waiter = resolve;\r\n });\r\n }\r\n const chunk = queue.shift()!;\r\n if (chunk.kind === 'close') {\r\n // If we already saw a spawn error (ENOENT etc.), keep exitCode=1\r\n // rather than the negative platform code Node fabricates.\r\n if (!spawnFailed) exitCode = chunk.code ?? 0;\r\n break;\r\n }\r\n if (chunk.kind === 'error') {\r\n spawnFailed = true;\r\n exitCode = 1;\r\n // close usually follows\r\n continue;\r\n }\r\n pending += chunk.data;\r\n if (pending.length >= flushAt) {\r\n yield { type: 'partial_output', text: pending };\r\n pending = '';\r\n }\r\n }\r\n if (pending.length > 0) {\r\n yield { type: 'partial_output', text: pending };\r\n }\r\n\r\n return {\r\n stdout,\r\n stderr,\r\n exitCode,\r\n truncated: stdout.length >= max || stderr.length >= max,\r\n error,\r\n };\r\n}\r\n","import type { Tool, ToolStreamEvent } from '@wrongstack/core';\r\nimport { safeResolve, spawnStream } from './_util.js';\r\n\r\ninterface AuditInput {\r\n cwd?: string;\r\n level?: 'low' | 'moderate' | 'high' | 'critical';\r\n fix?: boolean;\r\n packages?: string | string[];\r\n}\r\n\r\ninterface AuditVulnerability {\r\n severity: string;\r\n package: string;\r\n title: string;\r\n url: string;\r\n}\r\n\r\ninterface AuditOutput {\r\n exit_code: number;\r\n vulnerabilities: AuditVulnerability[];\r\n total: number;\r\n summary: string;\r\n output: string;\r\n truncated: boolean;\r\n}\r\n\r\nexport const auditTool: Tool<AuditInput, AuditOutput> = {\r\n name: 'audit',\r\n description:\r\n 'Run npm/pnpm security audit. Returns vulnerabilities sorted by severity.',\r\n usageHint:\r\n 'Set `level` to filter minimum severity. `fix` attempts auto-fix. `packages` checks specific packages.',\r\n permission: 'confirm',\r\n mutating: false,\r\n timeoutMs: 60_000,\r\n inputSchema: {\r\n type: 'object',\r\n properties: {\r\n cwd: { type: 'string', description: 'Working directory (default: cwd)' },\r\n level: {\r\n type: 'string',\r\n enum: ['low', 'moderate', 'high', 'critical'],\r\n description: 'Minimum severity level to report',\r\n },\r\n fix: { type: 'boolean', description: 'Attempt to fix vulnerabilities (default: false)' },\r\n packages: { type: 'string', description: 'Specific package(s) to audit (comma-separated)' },\r\n },\r\n },\r\n async execute(input, ctx, opts) {\r\n let final: AuditOutput | undefined;\r\n for await (const ev of auditTool.executeStream!(input, ctx, opts)) {\r\n if (ev.type === 'final') final = ev.output;\r\n }\r\n if (!final) throw new Error('audit: stream ended without final event');\r\n return final;\r\n },\r\n async *executeStream(input, ctx, opts): AsyncGenerator<ToolStreamEvent<AuditOutput>> {\r\n const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;\r\n const manager = await detectManager(cwd);\r\n yield { type: 'log', text: `Auditing with ${manager}…`, data: { manager } };\r\n\r\n const args = ['audit', '--json'];\r\n if (input.fix) args.push('--fix');\r\n if (input.packages) {\r\n const pkgs = Array.isArray(input.packages) ? input.packages : input.packages.split(',');\r\n args.push(...pkgs.map((p: string) => p.trim()));\r\n }\r\n\r\n const result = yield* spawnStream({\r\n cmd: manager,\r\n args,\r\n cwd,\r\n signal: opts.signal,\r\n maxBytes: 100_000,\r\n });\r\n\r\n yield { type: 'final', output: parseAuditOutput(result.stdout, result.exitCode) };\r\n },\r\n};\r\n\r\nasync function detectManager(cwd: string): Promise<string> {\r\n const { stat } = await import('node:fs/promises');\r\n try { await stat(`${cwd}/pnpm-lock.yaml`); return 'pnpm'; } catch { /* */ }\r\n try { await stat(`${cwd}/yarn.lock`); return 'yarn'; } catch { /* */ }\r\n return 'npm';\r\n}\r\n\r\nfunction parseAuditOutput(json: string, exitCode: number): AuditOutput {\r\n if (!json) {\r\n return {\r\n exit_code: exitCode,\r\n vulnerabilities: [],\r\n total: 0,\r\n summary: exitCode === 0 ? 'No vulnerabilities found' : 'Audit failed',\r\n output: '',\r\n truncated: false,\r\n };\r\n }\r\n\r\n try {\r\n const data = JSON.parse(json);\r\n const advisories: AuditVulnerability[] = [];\r\n const ads = data.advisories ?? {};\r\n for (const id of Object.keys(ads)) {\r\n const adv = ads[id];\r\n advisories.push({\r\n severity: adv.severity ?? 'unknown',\r\n package: adv.module_name ?? id,\r\n title: adv.title ?? 'Unknown vulnerability',\r\n url: adv.url ?? '',\r\n });\r\n }\r\n\r\n const total = advisories.length;\r\n const summary = total === 0\r\n ? 'No vulnerabilities found'\r\n : `Found ${total} vulnerabilities: ${advisories.filter((a) => a.severity === 'critical').length} critical, ${advisories.filter((a) => a.severity === 'high').length} high`;\r\n\r\n return {\r\n exit_code: exitCode,\r\n vulnerabilities: advisories,\r\n total,\r\n summary,\r\n output: json,\r\n truncated: json.length >= 100_000,\r\n };\r\n } catch {\r\n return {\r\n exit_code: exitCode,\r\n vulnerabilities: [],\r\n total: 0,\r\n summary: 'Could not parse audit output',\r\n output: json,\r\n truncated: false,\r\n };\r\n }\r\n}\r\n"]}
package/dist/bash.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { Tool } from '@wrongstack/core';
2
+
3
+ interface BashInput {
4
+ command: string;
5
+ timeout_ms?: number;
6
+ background?: boolean;
7
+ }
8
+ interface BashOutput {
9
+ output: string;
10
+ exit_code: number | null;
11
+ timed_out: boolean;
12
+ pid?: number;
13
+ }
14
+ declare const bashTool: Tool<BashInput, BashOutput>;
15
+
16
+ export { type BashInput, type BashOutput, bashTool };
package/dist/bash.js ADDED
@@ -0,0 +1,180 @@
1
+ import { spawn } from 'child_process';
2
+ import * as os from 'os';
3
+ import { stripAnsi } from '@wrongstack/core';
4
+ import 'path';
5
+
6
+ // src/bash.ts
7
+ function truncateMiddle(s, max) {
8
+ if (Buffer.byteLength(s, "utf8") <= max) return s;
9
+ const half = Math.floor(max / 2);
10
+ return s.slice(0, half) + `
11
+ \u2026[truncated ${Buffer.byteLength(s, "utf8") - max} bytes from middle]\u2026
12
+ ` + s.slice(-half);
13
+ }
14
+
15
+ // src/bash.ts
16
+ var MAX_OUTPUT = 32768;
17
+ var DEFAULT_TIMEOUT = 3e4;
18
+ var STREAM_FLUSH_INTERVAL_MS = 200;
19
+ var STREAM_FLUSH_BYTES = 4 * 1024;
20
+ var bashTool = {
21
+ name: "bash",
22
+ description: "Run a shell command. stdout and stderr are merged.",
23
+ 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.",
24
+ permission: "confirm",
25
+ mutating: true,
26
+ timeoutMs: 3e4,
27
+ maxOutputBytes: MAX_OUTPUT,
28
+ estimatedDurationMs: 3e3,
29
+ inputSchema: {
30
+ type: "object",
31
+ properties: {
32
+ command: { type: "string" },
33
+ timeout_ms: { type: "integer" },
34
+ background: { type: "boolean" }
35
+ },
36
+ required: ["command"]
37
+ },
38
+ async execute(input, ctx, opts) {
39
+ let final;
40
+ for await (const ev of bashTool.executeStream(input, ctx, opts)) {
41
+ if (ev.type === "final") final = ev.output;
42
+ }
43
+ if (!final) throw new Error("bash: stream ended without final event");
44
+ return final;
45
+ },
46
+ async *executeStream(input, ctx, opts) {
47
+ if (!input?.command) throw new Error("bash: command is required");
48
+ const timeoutMs = Math.min(input.timeout_ms ?? DEFAULT_TIMEOUT, 6e5);
49
+ const isWin = os.platform() === "win32";
50
+ const shell = isWin ? process.env["COMSPEC"] ?? "cmd.exe" : process.env["SHELL"] ?? "/bin/bash";
51
+ const args = isWin ? ["/c", input.command] : ["-c", input.command];
52
+ const env = { ...process.env };
53
+ env["WRONGSTACK_SESSION_ID"] = ctx.session.id;
54
+ const child = spawn(shell, args, {
55
+ cwd: ctx.projectRoot,
56
+ env,
57
+ stdio: input.background ? "ignore" : ["ignore", "pipe", "pipe"],
58
+ detached: input.background,
59
+ signal: opts.signal
60
+ });
61
+ if (input.background) {
62
+ const pid = child.pid;
63
+ if (typeof pid === "number") child.unref();
64
+ yield {
65
+ type: "final",
66
+ output: {
67
+ output: `[background] pid=${pid ?? "unknown"}`,
68
+ exit_code: null,
69
+ timed_out: false,
70
+ pid
71
+ }
72
+ };
73
+ return;
74
+ }
75
+ let buf = "";
76
+ let pending = "";
77
+ let timedOut = false;
78
+ const timers = [];
79
+ const timer = setTimeout(() => {
80
+ timedOut = true;
81
+ if (isWin) {
82
+ try {
83
+ child.kill();
84
+ } catch {
85
+ }
86
+ } else {
87
+ try {
88
+ child.kill("SIGTERM");
89
+ const killTimer = setTimeout(() => {
90
+ try {
91
+ child.kill("SIGKILL");
92
+ } catch {
93
+ }
94
+ }, 2e3);
95
+ timers.push(killTimer);
96
+ } catch {
97
+ }
98
+ }
99
+ }, timeoutMs);
100
+ timers.push(timer);
101
+ timer.unref?.();
102
+ const queue = [];
103
+ let resolveNext = null;
104
+ const push = (c) => {
105
+ if (resolveNext) {
106
+ const r = resolveNext;
107
+ resolveNext = null;
108
+ r(c);
109
+ } else {
110
+ queue.push(c);
111
+ }
112
+ };
113
+ const next = () => new Promise((resolve2) => {
114
+ const c = queue.shift();
115
+ if (c) resolve2(c);
116
+ else resolveNext = resolve2;
117
+ });
118
+ let lastFlush = Date.now();
119
+ const flush = () => {
120
+ if (pending.length === 0) return null;
121
+ const text = pending;
122
+ pending = "";
123
+ lastFlush = Date.now();
124
+ return text;
125
+ };
126
+ child.stdout?.on("data", (chunk) => {
127
+ const text = chunk.toString();
128
+ buf += text;
129
+ pending += text;
130
+ push({ kind: "data", text });
131
+ });
132
+ child.stderr?.on("data", (chunk) => {
133
+ const text = chunk.toString();
134
+ buf += text;
135
+ pending += text;
136
+ push({ kind: "data", text });
137
+ });
138
+ child.on("error", (err) => {
139
+ for (const t of timers) clearTimeout(t);
140
+ push({ kind: "error", err });
141
+ });
142
+ child.on("close", (code) => {
143
+ for (const t of timers) clearTimeout(t);
144
+ push({ kind: "end", code });
145
+ });
146
+ try {
147
+ while (true) {
148
+ const c = await next();
149
+ if (c.kind === "error") throw c.err;
150
+ if (c.kind === "end") {
151
+ const remainder = flush();
152
+ if (remainder !== null) {
153
+ yield { type: "partial_output", text: remainder };
154
+ }
155
+ const cleaned = stripAnsi(buf).replace(/\r\n?/g, "\n");
156
+ yield {
157
+ type: "final",
158
+ output: {
159
+ output: truncateMiddle(cleaned, MAX_OUTPUT),
160
+ exit_code: c.code,
161
+ timed_out: timedOut
162
+ }
163
+ };
164
+ return;
165
+ }
166
+ const now = Date.now();
167
+ if (pending.length >= STREAM_FLUSH_BYTES || now - lastFlush >= STREAM_FLUSH_INTERVAL_MS) {
168
+ const text = flush();
169
+ if (text) yield { type: "partial_output", text };
170
+ }
171
+ }
172
+ } finally {
173
+ for (const t of timers) clearTimeout(t);
174
+ }
175
+ }
176
+ };
177
+
178
+ export { bashTool };
179
+ //# sourceMappingURL=bash.js.map
180
+ //# sourceMappingURL=bash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/_util.ts","../src/bash.ts"],"names":["spawn","resolve"],"mappings":"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","file":"bash.js","sourcesContent":["import * as path from 'node:path';\r\nimport { spawn } from 'node:child_process';\r\nimport type { Context, ToolProgressEvent } from '@wrongstack/core';\r\n\r\nexport function resolvePath(input: string, ctx: Context): string {\r\n return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.cwd, input);\r\n}\r\n\r\nexport function ensureInsideRoot(absPath: string, ctx: Context): string {\r\n const root = path.resolve(ctx.projectRoot);\r\n const target = path.resolve(absPath);\r\n const rel = path.relative(root, target);\r\n if (rel.startsWith('..') || path.isAbsolute(rel)) {\r\n throw new Error(`Path \"${absPath}\" is outside project root \"${root}\"`);\r\n }\r\n return target;\r\n}\r\n\r\nexport function safeResolve(input: string, ctx: Context): string {\r\n return ensureInsideRoot(resolvePath(input, ctx), ctx);\r\n}\r\n\r\nexport function truncateMiddle(s: string, max: number): string {\r\n if (Buffer.byteLength(s, 'utf8') <= max) return s;\r\n const half = Math.floor(max / 2);\r\n return (\r\n s.slice(0, half) +\r\n `\\n…[truncated ${Buffer.byteLength(s, 'utf8') - max} bytes from middle]…\\n` +\r\n s.slice(-half)\r\n );\r\n}\r\n\r\nexport function isBinaryBuffer(buf: Buffer): boolean {\r\n const len = Math.min(buf.length, 8192);\r\n for (let i = 0; i < len; i++) {\r\n if (buf[i] === 0) return true;\r\n }\r\n return false;\r\n}\r\n\r\nexport interface SpawnStreamResult {\r\n stdout: string;\r\n stderr: string;\r\n exitCode: number;\r\n truncated: boolean;\r\n error?: string;\r\n}\r\n\r\nexport interface SpawnStreamOptions {\r\n cmd: string;\r\n args: string[];\r\n cwd: string;\r\n signal: AbortSignal;\r\n maxBytes?: number;\r\n /** Bytes of new stdout/stderr to accumulate before yielding a `partial_output` event. */\r\n flushBytes?: number;\r\n}\r\n\r\n/**\r\n * Spawn a child process and yield `partial_output` progress events as\r\n * stdout/stderr arrive (batched by byte threshold), then return the full\r\n * buffered result. Shared between install/lint/format/typecheck/test/audit\r\n * so the TUI live tail sees consistent progress regardless of which tool\r\n * is running.\r\n */\r\nexport async function* spawnStream(\r\n opts: SpawnStreamOptions,\r\n): AsyncGenerator<ToolProgressEvent, SpawnStreamResult> {\r\n const max = opts.maxBytes ?? 200_000;\r\n const flushAt = opts.flushBytes ?? 4 * 1024;\r\n let stdout = '';\r\n let stderr = '';\r\n let pending = '';\r\n let error: string | undefined;\r\n\r\n const child = spawn(opts.cmd, opts.args, {\r\n cwd: opts.cwd,\r\n signal: opts.signal,\r\n stdio: ['ignore', 'pipe', 'pipe'],\r\n });\r\n\r\n type Chunk = { kind: 'out' | 'err' | 'close' | 'error'; data: string; code?: number };\r\n const queue: Chunk[] = [];\r\n let waiter: (() => void) | undefined;\r\n const wake = () => {\r\n if (waiter) {\r\n const w = waiter;\r\n waiter = undefined;\r\n w();\r\n }\r\n };\r\n\r\n child.stdout?.on('data', (c) => {\r\n const s = c.toString();\r\n if (stdout.length < max) stdout += s;\r\n queue.push({ kind: 'out', data: s });\r\n wake();\r\n });\r\n child.stderr?.on('data', (c) => {\r\n const s = c.toString();\r\n if (stderr.length < max) stderr += s;\r\n queue.push({ kind: 'err', data: s });\r\n wake();\r\n });\r\n child.on('error', (e) => {\r\n error = e.message;\r\n queue.push({ kind: 'error', data: e.message });\r\n wake();\r\n });\r\n child.on('close', (code) => {\r\n queue.push({ kind: 'close', data: '', code: code ?? 0 });\r\n wake();\r\n });\r\n\r\n let exitCode = 0;\r\n let spawnFailed = false;\r\n for (;;) {\r\n while (queue.length === 0) {\r\n await new Promise<void>((resolve) => {\r\n waiter = resolve;\r\n });\r\n }\r\n const chunk = queue.shift()!;\r\n if (chunk.kind === 'close') {\r\n // If we already saw a spawn error (ENOENT etc.), keep exitCode=1\r\n // rather than the negative platform code Node fabricates.\r\n if (!spawnFailed) exitCode = chunk.code ?? 0;\r\n break;\r\n }\r\n if (chunk.kind === 'error') {\r\n spawnFailed = true;\r\n exitCode = 1;\r\n // close usually follows\r\n continue;\r\n }\r\n pending += chunk.data;\r\n if (pending.length >= flushAt) {\r\n yield { type: 'partial_output', text: pending };\r\n pending = '';\r\n }\r\n }\r\n if (pending.length > 0) {\r\n yield { type: 'partial_output', text: pending };\r\n }\r\n\r\n return {\r\n stdout,\r\n stderr,\r\n exitCode,\r\n truncated: stdout.length >= max || stderr.length >= max,\r\n error,\r\n };\r\n}\r\n","import { spawn } from 'node:child_process';\r\nimport * as os from 'node:os';\r\nimport type { Tool, ToolStreamEvent } from '@wrongstack/core';\r\nimport { stripAnsi } from '@wrongstack/core';\r\nimport { truncateMiddle } from './_util.js';\r\n\r\ninterface BashInput {\r\n command: string;\r\n timeout_ms?: number;\r\n background?: boolean;\r\n}\r\n\r\ninterface BashOutput {\r\n output: string;\r\n exit_code: number | null;\r\n timed_out: boolean;\r\n pid?: number;\r\n}\r\n\r\nconst MAX_OUTPUT = 32_768;\r\nconst DEFAULT_TIMEOUT = 30_000;\r\n// Flush partial_output every 200ms or when 4 KiB accumulates — whichever\r\n// comes first. Smaller batches make the TUI feel responsive; larger ones\r\n// keep EventBus traffic reasonable on chatty processes.\r\nconst STREAM_FLUSH_INTERVAL_MS = 200;\r\nconst STREAM_FLUSH_BYTES = 4 * 1024;\r\n\r\nexport const bashTool: Tool<BashInput, BashOutput> = {\r\n name: 'bash',\r\n description: 'Run a shell command. stdout and stderr are merged.',\r\n usageHint:\r\n '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.',\r\n permission: 'confirm',\r\n mutating: true,\r\n timeoutMs: 30_000,\r\n maxOutputBytes: MAX_OUTPUT,\r\n estimatedDurationMs: 3_000,\r\n inputSchema: {\r\n type: 'object',\r\n properties: {\r\n command: { type: 'string' },\r\n timeout_ms: { type: 'integer' },\r\n background: { type: 'boolean' },\r\n },\r\n required: ['command'],\r\n },\r\n async execute(input, ctx, opts) {\r\n let final: BashOutput | undefined;\r\n for await (const ev of bashTool.executeStream!(input, ctx, opts)) {\r\n if (ev.type === 'final') final = ev.output;\r\n }\r\n if (!final) throw new Error('bash: stream ended without final event');\r\n return final;\r\n },\r\n async *executeStream(input, ctx, opts): AsyncGenerator<ToolStreamEvent<BashOutput>> {\r\n if (!input?.command) throw new Error('bash: command is required');\r\n const timeoutMs = Math.min(input.timeout_ms ?? DEFAULT_TIMEOUT, 600_000);\r\n\r\n const isWin = os.platform() === 'win32';\r\n const shell = isWin\r\n ? process.env['COMSPEC'] ?? 'cmd.exe'\r\n : process.env['SHELL'] ?? '/bin/bash';\r\n const args = isWin ? ['/c', input.command] : ['-c', input.command];\r\n\r\n const env: NodeJS.ProcessEnv = { ...process.env };\r\n env['WRONGSTACK_SESSION_ID'] = ctx.session.id;\r\n\r\n const child = spawn(shell, args, {\r\n cwd: ctx.projectRoot,\r\n env,\r\n stdio: input.background ? 'ignore' : ['ignore', 'pipe', 'pipe'],\r\n detached: input.background,\r\n signal: opts.signal,\r\n });\r\n\r\n if (input.background) {\r\n const pid = child.pid;\r\n if (typeof pid === 'number') child.unref();\r\n yield {\r\n type: 'final',\r\n output: {\r\n output: `[background] pid=${pid ?? 'unknown'}`,\r\n exit_code: null,\r\n timed_out: false,\r\n pid,\r\n },\r\n };\r\n return;\r\n }\r\n\r\n let buf = '';\r\n let pending = '';\r\n let timedOut = false;\r\n const timers: NodeJS.Timeout[] = [];\r\n const timer = setTimeout(() => {\r\n timedOut = true;\r\n if (isWin) {\r\n try { child.kill(); } catch { /* ignore */ }\r\n } else {\r\n try {\r\n child.kill('SIGTERM');\r\n const killTimer = setTimeout(() => {\r\n try { child.kill('SIGKILL'); } catch { /* ignore */ }\r\n }, 2000);\r\n timers.push(killTimer);\r\n } catch { /* ignore */ }\r\n }\r\n }, timeoutMs);\r\n timers.push(timer);\r\n timer.unref?.();\r\n\r\n // Bridge the EventEmitter-style child to an async iterator. We push\r\n // chunks into a queue and let the generator pull them; this lets us\r\n // yield 'partial_output' events to the executor at flush boundaries.\r\n type Chunk = { kind: 'data'; text: string } | { kind: 'end'; code: number | null } | { kind: 'error'; err: Error };\r\n const queue: Chunk[] = [];\r\n let resolveNext: ((c: Chunk) => void) | null = null;\r\n const push = (c: Chunk) => {\r\n if (resolveNext) {\r\n const r = resolveNext;\r\n resolveNext = null;\r\n r(c);\r\n } else {\r\n queue.push(c);\r\n }\r\n };\r\n const next = (): Promise<Chunk> => new Promise((resolve) => {\r\n const c = queue.shift();\r\n if (c) resolve(c);\r\n else resolveNext = resolve;\r\n });\r\n\r\n let lastFlush = Date.now();\r\n const flush = () => {\r\n if (pending.length === 0) return null;\r\n const text = pending;\r\n pending = '';\r\n lastFlush = Date.now();\r\n return text;\r\n };\r\n\r\n child.stdout?.on('data', (chunk) => {\r\n const text = chunk.toString();\r\n buf += text;\r\n pending += text;\r\n push({ kind: 'data', text });\r\n });\r\n child.stderr?.on('data', (chunk) => {\r\n const text = chunk.toString();\r\n buf += text;\r\n pending += text;\r\n push({ kind: 'data', text });\r\n });\r\n child.on('error', (err) => {\r\n for (const t of timers) clearTimeout(t);\r\n push({ kind: 'error', err });\r\n });\r\n child.on('close', (code) => {\r\n for (const t of timers) clearTimeout(t);\r\n push({ kind: 'end', code });\r\n });\r\n\r\n try {\r\n while (true) {\r\n const c = await next();\r\n if (c.kind === 'error') throw c.err;\r\n if (c.kind === 'end') {\r\n const remainder = flush();\r\n if (remainder !== null) {\r\n yield { type: 'partial_output', text: remainder };\r\n }\r\n const cleaned = stripAnsi(buf).replace(/\\r\\n?/g, '\\n');\r\n yield {\r\n type: 'final',\r\n output: {\r\n output: truncateMiddle(cleaned, MAX_OUTPUT),\r\n exit_code: c.code,\r\n timed_out: timedOut,\r\n },\r\n };\r\n return;\r\n }\r\n // Decide whether to flush. Time-based OR size-based to keep latency\r\n // low for slow-emitting commands without overwhelming the TUI for\r\n // chatty ones.\r\n const now = Date.now();\r\n if (pending.length >= STREAM_FLUSH_BYTES || now - lastFlush >= STREAM_FLUSH_INTERVAL_MS) {\r\n const text = flush();\r\n if (text) yield { type: 'partial_output', text };\r\n }\r\n }\r\n } finally {\r\n for (const t of timers) clearTimeout(t);\r\n }\r\n },\r\n};\r\n\r\n// Re-export types so consumers can narrow on stream events.\r\nexport type { BashInput, BashOutput };\r\n"]}
@@ -0,0 +1,26 @@
1
+ import { Tool } from '@wrongstack/core';
2
+
3
+ interface BatchToolUseInput {
4
+ calls: {
5
+ tool: string;
6
+ input: Record<string, unknown>;
7
+ }[];
8
+ stop_on_error?: boolean;
9
+ parallel?: boolean;
10
+ }
11
+ interface BatchToolUseOutput {
12
+ results: {
13
+ tool: string;
14
+ success: boolean;
15
+ result?: unknown;
16
+ error?: string;
17
+ executionMs: number;
18
+ }[];
19
+ total: number;
20
+ succeeded: number;
21
+ failed: number;
22
+ stop_on_error: boolean;
23
+ }
24
+ declare const batchToolUseTool: Tool<BatchToolUseInput, BatchToolUseOutput>;
25
+
26
+ export { batchToolUseTool };
@@ -0,0 +1,106 @@
1
+ // src/batch-tool-use.ts
2
+ var batchToolUseTool = {
3
+ name: "batch_tool_use",
4
+ description: "Execute multiple tool calls in sequence or parallel. Returns all results.",
5
+ usageHint: "Set `calls` array with tool names and inputs. `stop_on_error` halts on first failure. `parallel` runs concurrently (default: true).",
6
+ permission: "confirm",
7
+ mutating: false,
8
+ timeoutMs: 12e4,
9
+ inputSchema: {
10
+ type: "object",
11
+ properties: {
12
+ calls: {
13
+ type: "array",
14
+ items: {
15
+ type: "object",
16
+ properties: {
17
+ tool: { type: "string" },
18
+ input: { type: "object" }
19
+ },
20
+ required: ["tool"]
21
+ },
22
+ description: "Array of tool calls to execute"
23
+ },
24
+ stop_on_error: {
25
+ type: "boolean",
26
+ description: "Stop execution on first error (default: false)"
27
+ },
28
+ parallel: {
29
+ type: "boolean",
30
+ description: "Execute calls in parallel (default: true)"
31
+ }
32
+ },
33
+ required: ["calls"]
34
+ },
35
+ async execute(input, ctx, opts) {
36
+ if (!input?.calls || input.calls.length === 0) {
37
+ return {
38
+ results: [],
39
+ total: 0,
40
+ succeeded: 0,
41
+ failed: 0,
42
+ stop_on_error: false
43
+ };
44
+ }
45
+ const results = [];
46
+ let succeeded = 0;
47
+ let failed = 0;
48
+ if (input.parallel !== false) {
49
+ const promises = input.calls.map(async (call) => executeSingle(call, ctx, opts));
50
+ const allResults = await Promise.all(promises);
51
+ results.push(...allResults);
52
+ succeeded = allResults.filter((r) => r.success).length;
53
+ failed = allResults.filter((r) => !r.success).length;
54
+ } else {
55
+ for (const call of input.calls) {
56
+ const result = await executeSingle(call, ctx, opts);
57
+ results.push(result);
58
+ if (result.success) {
59
+ succeeded++;
60
+ } else {
61
+ failed++;
62
+ if (input.stop_on_error) break;
63
+ }
64
+ }
65
+ }
66
+ return {
67
+ results,
68
+ total: input.calls.length,
69
+ succeeded,
70
+ failed,
71
+ stop_on_error: input.stop_on_error ?? false
72
+ };
73
+ }
74
+ };
75
+ async function executeSingle(call, ctx, opts) {
76
+ const start = Date.now();
77
+ const tool = ctx.tools.find((t) => t.name === call.tool);
78
+ if (!tool) {
79
+ return {
80
+ tool: call.tool,
81
+ success: false,
82
+ error: `tool "${call.tool}" not found`,
83
+ executionMs: Date.now() - start
84
+ };
85
+ }
86
+ try {
87
+ const result = await tool.execute(call.input, ctx, opts);
88
+ return {
89
+ tool: call.tool,
90
+ success: true,
91
+ result,
92
+ executionMs: Date.now() - start
93
+ };
94
+ } catch (e) {
95
+ return {
96
+ tool: call.tool,
97
+ success: false,
98
+ error: e instanceof Error ? e.message : String(e),
99
+ executionMs: Date.now() - start
100
+ };
101
+ }
102
+ }
103
+
104
+ export { batchToolUseTool };
105
+ //# sourceMappingURL=batch-tool-use.js.map
106
+ //# sourceMappingURL=batch-tool-use.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/batch-tool-use.ts"],"names":[],"mappings":"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","file":"batch-tool-use.js","sourcesContent":["import type { Tool } from '@wrongstack/core';\n\ninterface BatchToolUseInput {\n calls: {\n tool: string;\n input: Record<string, unknown>;\n }[];\n stop_on_error?: boolean;\n parallel?: boolean;\n}\n\ninterface BatchToolUseOutput {\n results: {\n tool: string;\n success: boolean;\n result?: unknown;\n error?: string;\n executionMs: number;\n }[];\n total: number;\n succeeded: number;\n failed: number;\n stop_on_error: boolean;\n}\n\nexport const batchToolUseTool: Tool<BatchToolUseInput, BatchToolUseOutput> = {\n name: 'batch_tool_use',\n description:\n 'Execute multiple tool calls in sequence or parallel. Returns all results.',\n usageHint:\n 'Set `calls` array with tool names and inputs. `stop_on_error` halts on first failure. `parallel` runs concurrently (default: true).',\n permission: 'confirm',\n mutating: false,\n timeoutMs: 120_000,\n inputSchema: {\n type: 'object',\n properties: {\n calls: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n tool: { type: 'string' },\n input: { type: 'object' },\n },\n required: ['tool'],\n },\n description: 'Array of tool calls to execute',\n },\n stop_on_error: {\n type: 'boolean',\n description: 'Stop execution on first error (default: false)',\n },\n parallel: {\n type: 'boolean',\n description: 'Execute calls in parallel (default: true)',\n },\n },\n required: ['calls'],\n },\n async execute(input, ctx, opts) {\n if (!input?.calls || input.calls.length === 0) {\n return {\n results: [],\n total: 0,\n succeeded: 0,\n failed: 0,\n stop_on_error: false,\n };\n }\n\n const results: BatchToolUseOutput['results'] = [];\n let succeeded = 0;\n let failed = 0;\n\n if (input.parallel !== false) {\n const promises = input.calls.map(async (call) => executeSingle(call, ctx, opts));\n const allResults = await Promise.all(promises);\n results.push(...allResults);\n succeeded = allResults.filter((r) => r.success).length;\n failed = allResults.filter((r) => !r.success).length;\n } else {\n for (const call of input.calls) {\n const result = await executeSingle(call, ctx, opts);\n results.push(result);\n if (result.success) {\n succeeded++;\n } else {\n failed++;\n if (input.stop_on_error) break;\n }\n }\n }\n\n return {\n results,\n total: input.calls.length,\n succeeded,\n failed,\n stop_on_error: input.stop_on_error ?? false,\n };\n },\n};\n\nasync function executeSingle(\n call: { tool: string; input: Record<string, unknown> },\n ctx: import('@wrongstack/core').Context,\n opts: { signal: AbortSignal },\n): Promise<BatchToolUseOutput['results'][0]> {\n const start = Date.now();\n const tool = ctx.tools.find((t: Tool) => t.name === call.tool);\n\n if (!tool) {\n return {\n tool: call.tool,\n success: false,\n error: `tool \"${call.tool}\" not found`,\n executionMs: Date.now() - start,\n };\n }\n\n try {\n const result = await tool.execute(call.input, ctx, opts);\n return {\n tool: call.tool,\n success: true,\n result,\n executionMs: Date.now() - start,\n };\n } catch (e) {\n return {\n tool: call.tool,\n success: false,\n error: e instanceof Error ? e.message : String(e),\n executionMs: Date.now() - start,\n };\n }\n}"]}
@@ -0,0 +1,5 @@
1
+ import { Tool } from '@wrongstack/core';
2
+
3
+ declare const builtinTools: Tool[];
4
+
5
+ export { builtinTools };