@wrongstack/tools 0.1.2 → 0.1.4

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 (104) hide show
  1. package/LICENSE +17 -13
  2. package/dist/audit.d.ts +25 -0
  3. package/dist/audit.js +209 -0
  4. package/dist/audit.js.map +1 -0
  5. package/dist/bash.d.ts +16 -0
  6. package/dist/bash.js +180 -0
  7. package/dist/bash.js.map +1 -0
  8. package/dist/batch-tool-use.d.ts +26 -0
  9. package/dist/batch-tool-use.js +106 -0
  10. package/dist/batch-tool-use.js.map +1 -0
  11. package/dist/builtin.d.ts +5 -0
  12. package/dist/builtin.js +3735 -0
  13. package/dist/builtin.js.map +1 -0
  14. package/dist/diff.d.ts +20 -0
  15. package/dist/diff.js +142 -0
  16. package/dist/diff.js.map +1 -0
  17. package/dist/document.d.ts +27 -0
  18. package/dist/document.js +148 -0
  19. package/dist/document.js.map +1 -0
  20. package/dist/edit.d.ts +22 -0
  21. package/dist/edit.js +138 -0
  22. package/dist/edit.js.map +1 -0
  23. package/dist/exec.d.ts +21 -0
  24. package/dist/exec.js +159 -0
  25. package/dist/exec.js.map +1 -0
  26. package/dist/fetch.d.ts +15 -0
  27. package/dist/fetch.js +213 -0
  28. package/dist/fetch.js.map +1 -0
  29. package/dist/format.d.ts +18 -0
  30. package/dist/format.js +194 -0
  31. package/dist/format.js.map +1 -0
  32. package/dist/git.d.ts +27 -0
  33. package/dist/git.js +174 -0
  34. package/dist/git.js.map +1 -0
  35. package/dist/glob.d.ts +14 -0
  36. package/dist/glob.js +101 -0
  37. package/dist/glob.js.map +1 -0
  38. package/dist/grep.d.ts +20 -0
  39. package/dist/grep.js +264 -0
  40. package/dist/grep.js.map +1 -0
  41. package/dist/index.d.ts +34 -563
  42. package/dist/index.js +715 -440
  43. package/dist/index.js.map +1 -1
  44. package/dist/install.d.ts +19 -0
  45. package/dist/install.js +186 -0
  46. package/dist/install.js.map +1 -0
  47. package/dist/json.d.ts +20 -0
  48. package/dist/json.js +124 -0
  49. package/dist/json.js.map +1 -0
  50. package/dist/lint.d.ts +20 -0
  51. package/dist/lint.js +191 -0
  52. package/dist/lint.js.map +1 -0
  53. package/dist/logs.d.ts +27 -0
  54. package/dist/logs.js +180 -0
  55. package/dist/logs.js.map +1 -0
  56. package/dist/memory.d.ts +22 -0
  57. package/dist/memory.js +53 -0
  58. package/dist/memory.js.map +1 -0
  59. package/dist/mode.d.ts +20 -0
  60. package/dist/mode.js +81 -0
  61. package/dist/mode.js.map +1 -0
  62. package/dist/outdated.d.ts +26 -0
  63. package/dist/outdated.js +138 -0
  64. package/dist/outdated.js.map +1 -0
  65. package/dist/patch.d.ts +18 -0
  66. package/dist/patch.js +101 -0
  67. package/dist/patch.js.map +1 -0
  68. package/dist/read.d.ts +16 -0
  69. package/dist/read.js +81 -0
  70. package/dist/read.js.map +1 -0
  71. package/dist/replace.d.ts +23 -0
  72. package/dist/replace.js +196 -0
  73. package/dist/replace.js.map +1 -0
  74. package/dist/scaffold.d.ts +20 -0
  75. package/dist/scaffold.js +185 -0
  76. package/dist/scaffold.js.map +1 -0
  77. package/dist/search.d.ts +20 -0
  78. package/dist/search.js +212 -0
  79. package/dist/search.js.map +1 -0
  80. package/dist/test.d.ts +24 -0
  81. package/dist/test.js +247 -0
  82. package/dist/test.js.map +1 -0
  83. package/dist/todo.d.ts +12 -0
  84. package/dist/todo.js +53 -0
  85. package/dist/todo.js.map +1 -0
  86. package/dist/tool-help.d.ts +23 -0
  87. package/dist/tool-help.js +122 -0
  88. package/dist/tool-help.js.map +1 -0
  89. package/dist/tool-search.d.ts +22 -0
  90. package/dist/tool-search.js +70 -0
  91. package/dist/tool-search.js.map +1 -0
  92. package/dist/tool-use.d.ts +16 -0
  93. package/dist/tool-use.js +79 -0
  94. package/dist/tool-use.js.map +1 -0
  95. package/dist/tree.d.ts +21 -0
  96. package/dist/tree.js +176 -0
  97. package/dist/tree.js.map +1 -0
  98. package/dist/typecheck.d.ts +19 -0
  99. package/dist/typecheck.js +181 -0
  100. package/dist/typecheck.js.map +1 -0
  101. package/dist/write.d.ts +15 -0
  102. package/dist/write.js +77 -0
  103. package/dist/write.js.map +1 -0
  104. package/package.json +147 -4
package/LICENSE CHANGED
@@ -1,17 +1,21 @@
1
- Apache License
2
- Version 2.0, January 2004
3
- http://www.apache.org/licenses/
1
+ MIT License
4
2
 
5
- Copyright 2026 ECOSTACK TECHNOLOGY OÜ
3
+ Copyright (c) 2026 ECOSTACK TECHNOLOGY OÜ
6
4
 
7
- Licensed under the Apache License, Version 2.0 (the "License");
8
- you may not use this file except in compliance with the License.
9
- You may obtain a copy of the License at
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
10
11
 
11
- http://www.apache.org/licenses/LICENSE-2.0
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
12
14
 
13
- Unless required by applicable law or agreed to in writing, software
14
- distributed under the License is distributed on an "AS IS" BASIS,
15
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
- See the License for the specific language governing permissions and
17
- limitations under the License.
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -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":"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","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 };