@wrongstack/tools 0.2.0 → 0.3.2

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 (74) hide show
  1. package/README.md +1 -1
  2. package/dist/audit.js +1 -0
  3. package/dist/audit.js.map +1 -1
  4. package/dist/bash.js +1 -0
  5. package/dist/bash.js.map +1 -1
  6. package/dist/batch-tool-use.js +1 -0
  7. package/dist/batch-tool-use.js.map +1 -1
  8. package/dist/builtin.js +58 -8
  9. package/dist/builtin.js.map +1 -1
  10. package/dist/diff.js +1 -0
  11. package/dist/diff.js.map +1 -1
  12. package/dist/document.js +1 -0
  13. package/dist/document.js.map +1 -1
  14. package/dist/edit.js +1 -0
  15. package/dist/edit.js.map +1 -1
  16. package/dist/exec.js +5 -4
  17. package/dist/exec.js.map +1 -1
  18. package/dist/fetch.js +5 -1
  19. package/dist/fetch.js.map +1 -1
  20. package/dist/format.js +1 -0
  21. package/dist/format.js.map +1 -1
  22. package/dist/git.js +3 -2
  23. package/dist/git.js.map +1 -1
  24. package/dist/glob.js +1 -0
  25. package/dist/glob.js.map +1 -1
  26. package/dist/grep.js +18 -1
  27. package/dist/grep.js.map +1 -1
  28. package/dist/index.d.ts +1 -0
  29. package/dist/index.js +69 -9
  30. package/dist/index.js.map +1 -1
  31. package/dist/install.js +1 -0
  32. package/dist/install.js.map +1 -1
  33. package/dist/json.js +1 -0
  34. package/dist/json.js.map +1 -1
  35. package/dist/lint.js +1 -0
  36. package/dist/lint.js.map +1 -1
  37. package/dist/logs.js +1 -0
  38. package/dist/logs.js.map +1 -1
  39. package/dist/memory.js +2 -0
  40. package/dist/memory.js.map +1 -1
  41. package/dist/mode.js +1 -0
  42. package/dist/mode.js.map +1 -1
  43. package/dist/outdated.js +1 -0
  44. package/dist/outdated.js.map +1 -1
  45. package/dist/pack.d.ts +9 -0
  46. package/dist/pack.js +4219 -0
  47. package/dist/pack.js.map +1 -0
  48. package/dist/patch.js +1 -0
  49. package/dist/patch.js.map +1 -1
  50. package/dist/read.js +1 -0
  51. package/dist/read.js.map +1 -1
  52. package/dist/replace.js +1 -0
  53. package/dist/replace.js.map +1 -1
  54. package/dist/scaffold.js +1 -0
  55. package/dist/scaffold.js.map +1 -1
  56. package/dist/search.js +1 -0
  57. package/dist/search.js.map +1 -1
  58. package/dist/test.js +1 -0
  59. package/dist/test.js.map +1 -1
  60. package/dist/todo.js +1 -0
  61. package/dist/todo.js.map +1 -1
  62. package/dist/tool-help.js +1 -0
  63. package/dist/tool-help.js.map +1 -1
  64. package/dist/tool-search.js +1 -0
  65. package/dist/tool-search.js.map +1 -1
  66. package/dist/tool-use.js +1 -0
  67. package/dist/tool-use.js.map +1 -1
  68. package/dist/tree.js +1 -0
  69. package/dist/tree.js.map +1 -1
  70. package/dist/typecheck.js +1 -0
  71. package/dist/typecheck.js.map +1 -1
  72. package/dist/write.js +1 -0
  73. package/dist/write.js.map +1 -1
  74. package/package.json +6 -2
package/README.md CHANGED
@@ -21,7 +21,7 @@ pnpm add @wrongstack/tools @wrongstack/core
21
21
  | `edit` | `confirm` | yes | str_replace; requires prior `read`; FAT-aware mtime tolerance |
22
22
  | `replace` | `confirm` | yes | Regex replace across files; symlink-skipped + realpath-revalidated |
23
23
  | `glob` | `auto` | no | Glob pattern matching |
24
- | `grep` | `auto` | no | rg-backed; user-regex compiled through ReDoS guard |
24
+ | `grep` | `auto` | no | rg-backed; ReDoS-guarded regex, default ignores, accurate count totals |
25
25
  | `tree` | `auto` | no | Project tree; clears its polling timer (no leaks) |
26
26
  | `patch` | `confirm` | yes | GNU-patch diff applier; targets pre-validated against `projectRoot` |
27
27
  | `diff` | `auto` | no | Git diff against HEAD |
package/dist/audit.js CHANGED
@@ -100,6 +100,7 @@ function safeResolve(input, ctx) {
100
100
  // src/audit.ts
101
101
  var auditTool = {
102
102
  name: "audit",
103
+ category: "Package Management",
103
104
  description: "Run npm/pnpm security audit. Returns vulnerabilities sorted by severity.",
104
105
  usageHint: "Set `level` to filter minimum severity. `fix` attempts auto-fix. `packages` checks specific packages.",
105
106
  permission: "confirm",
package/dist/audit.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/_spawn-stream.ts","../src/_util.ts","../src/audit.ts"],"names":["resolve"],"mappings":"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","file":"audit.js","sourcesContent":["import { spawn } from 'node:child_process';\nimport { buildChildEnv } from '@wrongstack/core';\nimport type { ToolProgressEvent } from '@wrongstack/core';\n\nexport interface SpawnStreamResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n truncated: boolean;\n error?: string;\n}\n\nexport interface SpawnStreamOptions {\n cmd: string;\n args: string[];\n cwd: string;\n signal: AbortSignal;\n maxBytes?: number;\n /** Bytes of new stdout/stderr to accumulate before yielding a `partial_output` event. */\n flushBytes?: number;\n}\n\n/**\n * Spawn a child process and yield `partial_output` progress events as\n * stdout/stderr arrive (batched by byte threshold), then return the full\n * buffered result. Shared between install/lint/format/typecheck/test/audit\n * so the TUI live tail sees consistent progress regardless of which tool\n * is running.\n */\nexport async function* spawnStream(\n opts: SpawnStreamOptions,\n): AsyncGenerator<ToolProgressEvent, SpawnStreamResult> {\n const max = opts.maxBytes ?? 200_000;\n const flushAt = opts.flushBytes ?? 4 * 1024;\n let stdout = '';\n let stderr = '';\n let pending = '';\n let error: string | undefined;\n\n const child = spawn(opts.cmd, opts.args, {\n cwd: opts.cwd,\n signal: opts.signal,\n env: buildChildEnv(),\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n type Chunk = { kind: 'out' | 'err' | 'close' | 'error'; data: string; code?: number };\n const queue: Chunk[] = [];\n let waiter: (() => void) | undefined;\n const wake = () => {\n if (waiter) {\n const w = waiter;\n waiter = undefined;\n w();\n }\n };\n\n child.stdout?.on('data', (c) => {\n const s = c.toString();\n if (stdout.length < max) stdout += s;\n queue.push({ kind: 'out', data: s });\n wake();\n });\n child.stderr?.on('data', (c) => {\n const s = c.toString();\n if (stderr.length < max) stderr += s;\n queue.push({ kind: 'err', data: s });\n wake();\n });\n child.on('error', (e) => {\n error = e.message;\n queue.push({ kind: 'error', data: e.message });\n wake();\n });\n child.on('close', (code) => {\n queue.push({ kind: 'close', data: '', code: code ?? 0 });\n wake();\n });\n\n let exitCode = 0;\n let spawnFailed = false;\n for (;;) {\n while (queue.length === 0) {\n await new Promise<void>((resolve) => {\n waiter = resolve;\n });\n }\n const chunk = queue.shift()!;\n if (chunk.kind === 'close') {\n // If we already saw a spawn error (ENOENT etc.), keep exitCode=1\n // rather than the negative platform code Node fabricates.\n if (!spawnFailed) exitCode = chunk.code ?? 0;\n break;\n }\n if (chunk.kind === 'error') {\n spawnFailed = true;\n exitCode = 1;\n // close usually follows\n continue;\n }\n pending += chunk.data;\n if (pending.length >= flushAt) {\n yield { type: 'partial_output', text: pending };\n pending = '';\n }\n }\n if (pending.length > 0) {\n yield { type: 'partial_output', text: pending };\n }\n\n return {\n stdout,\n stderr,\n exitCode,\n truncated: stdout.length >= max || stderr.length >= max,\n error,\n };\n}\n","import * as path from 'node:path';\nimport type { Context } from '@wrongstack/core';\n\nexport function resolvePath(input: string, ctx: Context): string {\n return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.cwd, input);\n}\n\nexport function ensureInsideRoot(absPath: string, ctx: Context): string {\n const root = path.resolve(ctx.projectRoot);\n const target = path.resolve(absPath);\n const rel = path.relative(root, target);\n if (rel.startsWith('..') || path.isAbsolute(rel)) {\n throw new Error(`Path \"${absPath}\" is outside project root \"${root}\"`);\n }\n return target;\n}\n\nexport function safeResolve(input: string, ctx: Context): string {\n return ensureInsideRoot(resolvePath(input, ctx), ctx);\n}\n\nexport function truncateMiddle(s: string, max: number): string {\n if (Buffer.byteLength(s, 'utf8') <= max) return s;\n const half = Math.floor(max / 2);\n return (\n s.slice(0, half) +\n `\\n…[truncated ${Buffer.byteLength(s, 'utf8') - max} bytes from middle]…\\n` +\n s.slice(-half)\n );\n}\n\nexport function isBinaryBuffer(buf: Buffer): boolean {\n const len = Math.min(buf.length, 8192);\n for (let i = 0; i < len; i++) {\n if (buf[i] === 0) return true;\n }\n return false;\n}\n","import type { Tool, ToolStreamEvent } from '@wrongstack/core';\nimport { spawnStream } from './_spawn-stream.js';\nimport { safeResolve } from './_util.js';\n\ninterface AuditInput {\n cwd?: string;\n level?: 'low' | 'moderate' | 'high' | 'critical';\n fix?: boolean;\n packages?: string | string[];\n}\n\ninterface AuditVulnerability {\n severity: string;\n package: string;\n title: string;\n url: string;\n}\n\ninterface AuditOutput {\n exit_code: number;\n vulnerabilities: AuditVulnerability[];\n total: number;\n summary: string;\n output: string;\n truncated: boolean;\n}\n\nexport const auditTool: Tool<AuditInput, AuditOutput> = {\n name: 'audit',\n description: 'Run npm/pnpm security audit. Returns vulnerabilities sorted by severity.',\n usageHint:\n 'Set `level` to filter minimum severity. `fix` attempts auto-fix. `packages` checks specific packages.',\n permission: 'confirm',\n mutating: false,\n timeoutMs: 60_000,\n inputSchema: {\n type: 'object',\n properties: {\n cwd: { type: 'string', description: 'Working directory (default: cwd)' },\n level: {\n type: 'string',\n enum: ['low', 'moderate', 'high', 'critical'],\n description: 'Minimum severity level to report',\n },\n fix: { type: 'boolean', description: 'Attempt to fix vulnerabilities (default: false)' },\n packages: { type: 'string', description: 'Specific package(s) to audit (comma-separated)' },\n },\n },\n async execute(input, ctx, opts) {\n let final: AuditOutput | undefined;\n for await (const ev of auditTool.executeStream!(input, ctx, opts)) {\n if (ev.type === 'final') final = ev.output;\n }\n if (!final) throw new Error('audit: stream ended without final event');\n return final;\n },\n async *executeStream(input, ctx, opts): AsyncGenerator<ToolStreamEvent<AuditOutput>> {\n const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;\n const manager = await detectManager(cwd);\n yield { type: 'log', text: `Auditing with ${manager}…`, data: { manager } };\n\n const args = ['audit', '--json'];\n if (input.fix) args.push('--fix');\n if (input.packages) {\n const pkgs = Array.isArray(input.packages) ? input.packages : input.packages.split(',');\n args.push(...pkgs.map((p: string) => p.trim()));\n }\n\n const result = yield* spawnStream({\n cmd: manager,\n args,\n cwd,\n signal: opts.signal,\n maxBytes: 100_000,\n });\n\n yield { type: 'final', output: parseAuditOutput(result.stdout, result.exitCode) };\n },\n};\n\nasync function detectManager(cwd: string): Promise<string> {\n const { stat } = await import('node:fs/promises');\n try {\n await stat(`${cwd}/pnpm-lock.yaml`);\n return 'pnpm';\n } catch {\n /* */\n }\n try {\n await stat(`${cwd}/yarn.lock`);\n return 'yarn';\n } catch {\n /* */\n }\n return 'npm';\n}\n\nfunction parseAuditOutput(json: string, exitCode: number): AuditOutput {\n if (!json) {\n return {\n exit_code: exitCode,\n vulnerabilities: [],\n total: 0,\n summary: exitCode === 0 ? 'No vulnerabilities found' : 'Audit failed',\n output: '',\n truncated: false,\n };\n }\n\n try {\n const data = JSON.parse(json);\n const advisories: AuditVulnerability[] = [];\n const ads = data.advisories ?? {};\n for (const id of Object.keys(ads)) {\n const adv = ads[id];\n advisories.push({\n severity: adv.severity ?? 'unknown',\n package: adv.module_name ?? id,\n title: adv.title ?? 'Unknown vulnerability',\n url: adv.url ?? '',\n });\n }\n\n const total = advisories.length;\n const summary =\n total === 0\n ? 'No vulnerabilities found'\n : `Found ${total} vulnerabilities: ${advisories.filter((a) => a.severity === 'critical').length} critical, ${advisories.filter((a) => a.severity === 'high').length} high`;\n\n return {\n exit_code: exitCode,\n vulnerabilities: advisories,\n total,\n summary,\n output: json,\n truncated: json.length >= 100_000,\n };\n } catch {\n return {\n exit_code: exitCode,\n vulnerabilities: [],\n total: 0,\n summary: 'Could not parse audit output',\n output: json,\n truncated: false,\n };\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/_spawn-stream.ts","../src/_util.ts","../src/audit.ts"],"names":["resolve"],"mappings":"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","file":"audit.js","sourcesContent":["import { spawn } from 'node:child_process';\nimport { buildChildEnv } from '@wrongstack/core';\nimport type { ToolProgressEvent } from '@wrongstack/core';\n\nexport interface SpawnStreamResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n truncated: boolean;\n error?: string;\n}\n\nexport interface SpawnStreamOptions {\n cmd: string;\n args: string[];\n cwd: string;\n signal: AbortSignal;\n maxBytes?: number;\n /** Bytes of new stdout/stderr to accumulate before yielding a `partial_output` event. */\n flushBytes?: number;\n}\n\n/**\n * Spawn a child process and yield `partial_output` progress events as\n * stdout/stderr arrive (batched by byte threshold), then return the full\n * buffered result. Shared between install/lint/format/typecheck/test/audit\n * so the TUI live tail sees consistent progress regardless of which tool\n * is running.\n */\nexport async function* spawnStream(\n opts: SpawnStreamOptions,\n): AsyncGenerator<ToolProgressEvent, SpawnStreamResult> {\n const max = opts.maxBytes ?? 200_000;\n const flushAt = opts.flushBytes ?? 4 * 1024;\n let stdout = '';\n let stderr = '';\n let pending = '';\n let error: string | undefined;\n\n const child = spawn(opts.cmd, opts.args, {\n cwd: opts.cwd,\n signal: opts.signal,\n env: buildChildEnv(),\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n type Chunk = { kind: 'out' | 'err' | 'close' | 'error'; data: string; code?: number };\n const queue: Chunk[] = [];\n let waiter: (() => void) | undefined;\n const wake = () => {\n if (waiter) {\n const w = waiter;\n waiter = undefined;\n w();\n }\n };\n\n child.stdout?.on('data', (c) => {\n const s = c.toString();\n if (stdout.length < max) stdout += s;\n queue.push({ kind: 'out', data: s });\n wake();\n });\n child.stderr?.on('data', (c) => {\n const s = c.toString();\n if (stderr.length < max) stderr += s;\n queue.push({ kind: 'err', data: s });\n wake();\n });\n child.on('error', (e) => {\n error = e.message;\n queue.push({ kind: 'error', data: e.message });\n wake();\n });\n child.on('close', (code) => {\n queue.push({ kind: 'close', data: '', code: code ?? 0 });\n wake();\n });\n\n let exitCode = 0;\n let spawnFailed = false;\n for (;;) {\n while (queue.length === 0) {\n await new Promise<void>((resolve) => {\n waiter = resolve;\n });\n }\n const chunk = queue.shift()!;\n if (chunk.kind === 'close') {\n // If we already saw a spawn error (ENOENT etc.), keep exitCode=1\n // rather than the negative platform code Node fabricates.\n if (!spawnFailed) exitCode = chunk.code ?? 0;\n break;\n }\n if (chunk.kind === 'error') {\n spawnFailed = true;\n exitCode = 1;\n // close usually follows\n continue;\n }\n pending += chunk.data;\n if (pending.length >= flushAt) {\n yield { type: 'partial_output', text: pending };\n pending = '';\n }\n }\n if (pending.length > 0) {\n yield { type: 'partial_output', text: pending };\n }\n\n return {\n stdout,\n stderr,\n exitCode,\n truncated: stdout.length >= max || stderr.length >= max,\n error,\n };\n}\n","import * as path from 'node:path';\nimport type { Context } from '@wrongstack/core';\n\nexport function resolvePath(input: string, ctx: Context): string {\n return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.cwd, input);\n}\n\nexport function ensureInsideRoot(absPath: string, ctx: Context): string {\n const root = path.resolve(ctx.projectRoot);\n const target = path.resolve(absPath);\n const rel = path.relative(root, target);\n if (rel.startsWith('..') || path.isAbsolute(rel)) {\n throw new Error(`Path \"${absPath}\" is outside project root \"${root}\"`);\n }\n return target;\n}\n\nexport function safeResolve(input: string, ctx: Context): string {\n return ensureInsideRoot(resolvePath(input, ctx), ctx);\n}\n\nexport function truncateMiddle(s: string, max: number): string {\n if (Buffer.byteLength(s, 'utf8') <= max) return s;\n const half = Math.floor(max / 2);\n return (\n s.slice(0, half) +\n `\\n…[truncated ${Buffer.byteLength(s, 'utf8') - max} bytes from middle]…\\n` +\n s.slice(-half)\n );\n}\n\nexport function isBinaryBuffer(buf: Buffer): boolean {\n const len = Math.min(buf.length, 8192);\n for (let i = 0; i < len; i++) {\n if (buf[i] === 0) return true;\n }\n return false;\n}\n","import type { Tool, ToolStreamEvent } from '@wrongstack/core';\nimport { spawnStream } from './_spawn-stream.js';\nimport { safeResolve } from './_util.js';\n\ninterface AuditInput {\n cwd?: string;\n level?: 'low' | 'moderate' | 'high' | 'critical';\n fix?: boolean;\n packages?: string | string[];\n}\n\ninterface AuditVulnerability {\n severity: string;\n package: string;\n title: string;\n url: string;\n}\n\ninterface AuditOutput {\n exit_code: number;\n vulnerabilities: AuditVulnerability[];\n total: number;\n summary: string;\n output: string;\n truncated: boolean;\n}\n\nexport const auditTool: Tool<AuditInput, AuditOutput> = {\n name: 'audit',\n category: 'Package Management',\n description: 'Run npm/pnpm security audit. Returns vulnerabilities sorted by severity.',\n usageHint:\n 'Set `level` to filter minimum severity. `fix` attempts auto-fix. `packages` checks specific packages.',\n permission: 'confirm',\n mutating: false,\n timeoutMs: 60_000,\n inputSchema: {\n type: 'object',\n properties: {\n cwd: { type: 'string', description: 'Working directory (default: cwd)' },\n level: {\n type: 'string',\n enum: ['low', 'moderate', 'high', 'critical'],\n description: 'Minimum severity level to report',\n },\n fix: { type: 'boolean', description: 'Attempt to fix vulnerabilities (default: false)' },\n packages: { type: 'string', description: 'Specific package(s) to audit (comma-separated)' },\n },\n },\n async execute(input, ctx, opts) {\n let final: AuditOutput | undefined;\n for await (const ev of auditTool.executeStream!(input, ctx, opts)) {\n if (ev.type === 'final') final = ev.output;\n }\n if (!final) throw new Error('audit: stream ended without final event');\n return final;\n },\n async *executeStream(input, ctx, opts): AsyncGenerator<ToolStreamEvent<AuditOutput>> {\n const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;\n const manager = await detectManager(cwd);\n yield { type: 'log', text: `Auditing with ${manager}…`, data: { manager } };\n\n const args = ['audit', '--json'];\n if (input.fix) args.push('--fix');\n if (input.packages) {\n const pkgs = Array.isArray(input.packages) ? input.packages : input.packages.split(',');\n args.push(...pkgs.map((p: string) => p.trim()));\n }\n\n const result = yield* spawnStream({\n cmd: manager,\n args,\n cwd,\n signal: opts.signal,\n maxBytes: 100_000,\n });\n\n yield { type: 'final', output: parseAuditOutput(result.stdout, result.exitCode) };\n },\n};\n\nasync function detectManager(cwd: string): Promise<string> {\n const { stat } = await import('node:fs/promises');\n try {\n await stat(`${cwd}/pnpm-lock.yaml`);\n return 'pnpm';\n } catch {\n /* */\n }\n try {\n await stat(`${cwd}/yarn.lock`);\n return 'yarn';\n } catch {\n /* */\n }\n return 'npm';\n}\n\nfunction parseAuditOutput(json: string, exitCode: number): AuditOutput {\n if (!json) {\n return {\n exit_code: exitCode,\n vulnerabilities: [],\n total: 0,\n summary: exitCode === 0 ? 'No vulnerabilities found' : 'Audit failed',\n output: '',\n truncated: false,\n };\n }\n\n try {\n const data = JSON.parse(json);\n const advisories: AuditVulnerability[] = [];\n const ads = data.advisories ?? {};\n for (const id of Object.keys(ads)) {\n const adv = ads[id];\n advisories.push({\n severity: adv.severity ?? 'unknown',\n package: adv.module_name ?? id,\n title: adv.title ?? 'Unknown vulnerability',\n url: adv.url ?? '',\n });\n }\n\n const total = advisories.length;\n const summary =\n total === 0\n ? 'No vulnerabilities found'\n : `Found ${total} vulnerabilities: ${advisories.filter((a) => a.severity === 'critical').length} critical, ${advisories.filter((a) => a.severity === 'high').length} high`;\n\n return {\n exit_code: exitCode,\n vulnerabilities: advisories,\n total,\n summary,\n output: json,\n truncated: json.length >= 100_000,\n };\n } catch {\n return {\n exit_code: exitCode,\n vulnerabilities: [],\n total: 0,\n summary: 'Could not parse audit output',\n output: json,\n truncated: false,\n };\n }\n}\n"]}
package/dist/bash.js CHANGED
@@ -19,6 +19,7 @@ var STREAM_FLUSH_INTERVAL_MS = 200;
19
19
  var STREAM_FLUSH_BYTES = 4 * 1024;
20
20
  var bashTool = {
21
21
  name: "bash",
22
+ category: "Shell",
22
23
  description: "Run a shell command. stdout and stderr are merged.",
23
24
  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
25
  permission: "confirm",
package/dist/bash.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/_util.ts","../src/bash.ts"],"names":["resolve"],"mappings":"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","file":"bash.js","sourcesContent":["import * as path from 'node:path';\nimport type { Context } from '@wrongstack/core';\n\nexport function resolvePath(input: string, ctx: Context): string {\n return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.cwd, input);\n}\n\nexport function ensureInsideRoot(absPath: string, ctx: Context): string {\n const root = path.resolve(ctx.projectRoot);\n const target = path.resolve(absPath);\n const rel = path.relative(root, target);\n if (rel.startsWith('..') || path.isAbsolute(rel)) {\n throw new Error(`Path \"${absPath}\" is outside project root \"${root}\"`);\n }\n return target;\n}\n\nexport function safeResolve(input: string, ctx: Context): string {\n return ensureInsideRoot(resolvePath(input, ctx), ctx);\n}\n\nexport function truncateMiddle(s: string, max: number): string {\n if (Buffer.byteLength(s, 'utf8') <= max) return s;\n const half = Math.floor(max / 2);\n return (\n s.slice(0, half) +\n `\\n…[truncated ${Buffer.byteLength(s, 'utf8') - max} bytes from middle]…\\n` +\n s.slice(-half)\n );\n}\n\nexport function isBinaryBuffer(buf: Buffer): boolean {\n const len = Math.min(buf.length, 8192);\n for (let i = 0; i < len; i++) {\n if (buf[i] === 0) return true;\n }\n return false;\n}\n","import { spawn } from 'node:child_process';\nimport * as os from 'node:os';\nimport type { Tool, ToolStreamEvent } from '@wrongstack/core';\nimport { stripAnsi } from '@wrongstack/core';\nimport { buildChildEnv } from './_env.js';\nimport { truncateMiddle } from './_util.js';\n\ninterface BashInput {\n command: string;\n timeout_ms?: number;\n background?: boolean;\n}\n\ninterface BashOutput {\n output: string;\n exit_code: number | null;\n timed_out: boolean;\n pid?: number;\n}\n\nconst MAX_OUTPUT = 32_768;\nconst DEFAULT_TIMEOUT = 30_000;\n// Flush partial_output every 200ms or when 4 KiB accumulates — whichever\n// comes first. Smaller batches make the TUI feel responsive; larger ones\n// keep EventBus traffic reasonable on chatty processes.\nconst STREAM_FLUSH_INTERVAL_MS = 200;\nconst STREAM_FLUSH_BYTES = 4 * 1024;\n\nexport const bashTool: Tool<BashInput, BashOutput> = {\n name: 'bash',\n description: 'Run a shell command. stdout and stderr are merged.',\n usageHint:\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.',\n permission: 'confirm',\n mutating: true,\n // Trust rules match on the literal `command` string. Without subjectKey\n // the policy heuristic would have done the same here, but declaring it\n // explicitly removes the implicit cross-tool aliasing.\n subjectKey: 'command',\n timeoutMs: 30_000,\n maxOutputBytes: MAX_OUTPUT,\n estimatedDurationMs: 3_000,\n inputSchema: {\n type: 'object',\n properties: {\n command: { type: 'string' },\n timeout_ms: { type: 'integer' },\n background: { type: 'boolean' },\n },\n required: ['command'],\n },\n async execute(input, ctx, opts) {\n let final: BashOutput | undefined;\n for await (const ev of bashTool.executeStream!(input, ctx, opts)) {\n if (ev.type === 'final') final = ev.output;\n }\n if (!final) throw new Error('bash: stream ended without final event');\n return final;\n },\n async *executeStream(input, ctx, opts): AsyncGenerator<ToolStreamEvent<BashOutput>> {\n if (!input?.command) throw new Error('bash: command is required');\n const timeoutMs = Math.max(1, Math.min(input.timeout_ms ?? DEFAULT_TIMEOUT, 600_000));\n\n const isWin = os.platform() === 'win32';\n const shell = isWin\n ? (process.env['COMSPEC'] ?? 'cmd.exe')\n : (process.env['SHELL'] ?? '/bin/bash');\n const args = isWin ? ['/c', input.command] : ['-c', input.command];\n\n const env = buildChildEnv(ctx.session?.id);\n\n // On POSIX we put the shell in its own process group so that timeout /\n // abort can kill the entire group with `process.kill(-pid)`. Otherwise\n // `bash -c \"sleep 9999 & disown\"` would leave the grandchild running.\n // `detached: true` is also reused for the user-facing background mode;\n // we always want detached on POSIX, only on Windows is it tied to the\n // explicit background flag.\n const detached = isWin ? !!input.background : true;\n const child = spawn(shell, args, {\n cwd: ctx.projectRoot,\n env,\n stdio: input.background ? 'ignore' : ['ignore', 'pipe', 'pipe'],\n detached,\n signal: opts.signal,\n });\n\n if (input.background) {\n const pid = child.pid;\n if (typeof pid === 'number') child.unref();\n yield {\n type: 'final',\n output: {\n output: `[background] pid=${pid ?? 'unknown'}`,\n exit_code: null,\n timed_out: false,\n pid,\n },\n };\n return;\n }\n\n let buf = '';\n let pending = '';\n let timedOut = false;\n const timers: NodeJS.Timeout[] = [];\n const timer = setTimeout(() => {\n timedOut = true;\n if (isWin) {\n try {\n child.kill();\n } catch {\n /* ignore */\n }\n } else {\n try {\n // Kill the process group, not just the shell — pid is positive,\n // group id is the negated pid. Without this a runaway grandchild\n // ('sleep 9999 & disown') survives bash termination.\n if (typeof child.pid === 'number') {\n try {\n process.kill(-child.pid, 'SIGTERM');\n } catch {\n child.kill('SIGTERM');\n }\n } else {\n child.kill('SIGTERM');\n }\n const killTimer = setTimeout(() => {\n try {\n if (typeof child.pid === 'number') {\n try {\n process.kill(-child.pid, 'SIGKILL');\n } catch {\n child.kill('SIGKILL');\n }\n } else {\n child.kill('SIGKILL');\n }\n } catch {\n /* ignore */\n }\n }, 2000);\n timers.push(killTimer);\n } catch {\n /* ignore */\n }\n }\n }, timeoutMs);\n timers.push(timer);\n timer.unref?.();\n\n // Bridge the EventEmitter-style child to an async iterator. We push\n // chunks into a queue and let the generator pull them; this lets us\n // yield 'partial_output' events to the executor at flush boundaries.\n type Chunk =\n | { kind: 'data'; text: string }\n | { kind: 'end'; code: number | null }\n | { kind: 'error'; err: Error };\n const queue: Chunk[] = [];\n let resolveNext: ((c: Chunk) => void) | null = null;\n const push = (c: Chunk) => {\n if (resolveNext) {\n const r = resolveNext;\n resolveNext = null;\n r(c);\n } else {\n queue.push(c);\n }\n };\n const next = (): Promise<Chunk> =>\n new Promise((resolve) => {\n const c = queue.shift();\n if (c) resolve(c);\n else resolveNext = resolve;\n });\n\n let lastFlush = Date.now();\n const flush = () => {\n if (pending.length === 0) return null;\n const text = pending;\n pending = '';\n lastFlush = Date.now();\n return text;\n };\n\n child.stdout?.on('data', (chunk) => {\n const text = chunk.toString();\n buf += text;\n pending += text;\n push({ kind: 'data', text });\n });\n child.stderr?.on('data', (chunk) => {\n const text = chunk.toString();\n buf += text;\n pending += text;\n push({ kind: 'data', text });\n });\n child.on('error', (err) => {\n for (const t of timers) clearTimeout(t);\n push({ kind: 'error', err });\n });\n child.on('close', (code) => {\n for (const t of timers) clearTimeout(t);\n push({ kind: 'end', code });\n });\n\n try {\n while (true) {\n const c = await next();\n if (c.kind === 'error') throw c.err;\n if (c.kind === 'end') {\n const remainder = flush();\n if (remainder !== null) {\n yield { type: 'partial_output', text: remainder };\n }\n const cleaned = stripAnsi(buf).replace(/\\r\\n?/g, '\\n');\n yield {\n type: 'final',\n output: {\n output: truncateMiddle(cleaned, MAX_OUTPUT),\n exit_code: c.code,\n timed_out: timedOut,\n },\n };\n return;\n }\n // Decide whether to flush. Time-based OR size-based to keep latency\n // low for slow-emitting commands without overwhelming the TUI for\n // chatty ones.\n const now = Date.now();\n if (pending.length >= STREAM_FLUSH_BYTES || now - lastFlush >= STREAM_FLUSH_INTERVAL_MS) {\n const text = flush();\n if (text) yield { type: 'partial_output', text };\n }\n }\n } finally {\n for (const t of timers) clearTimeout(t);\n }\n },\n};\n\n// Re-export types so consumers can narrow on stream events.\nexport type { BashInput, BashOutput };\n"]}
1
+ {"version":3,"sources":["../src/_util.ts","../src/bash.ts"],"names":["resolve"],"mappings":"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","file":"bash.js","sourcesContent":["import * as path from 'node:path';\nimport type { Context } from '@wrongstack/core';\n\nexport function resolvePath(input: string, ctx: Context): string {\n return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.cwd, input);\n}\n\nexport function ensureInsideRoot(absPath: string, ctx: Context): string {\n const root = path.resolve(ctx.projectRoot);\n const target = path.resolve(absPath);\n const rel = path.relative(root, target);\n if (rel.startsWith('..') || path.isAbsolute(rel)) {\n throw new Error(`Path \"${absPath}\" is outside project root \"${root}\"`);\n }\n return target;\n}\n\nexport function safeResolve(input: string, ctx: Context): string {\n return ensureInsideRoot(resolvePath(input, ctx), ctx);\n}\n\nexport function truncateMiddle(s: string, max: number): string {\n if (Buffer.byteLength(s, 'utf8') <= max) return s;\n const half = Math.floor(max / 2);\n return (\n s.slice(0, half) +\n `\\n…[truncated ${Buffer.byteLength(s, 'utf8') - max} bytes from middle]…\\n` +\n s.slice(-half)\n );\n}\n\nexport function isBinaryBuffer(buf: Buffer): boolean {\n const len = Math.min(buf.length, 8192);\n for (let i = 0; i < len; i++) {\n if (buf[i] === 0) return true;\n }\n return false;\n}\n","import { spawn } from 'node:child_process';\nimport * as os from 'node:os';\nimport type { Tool, ToolStreamEvent } from '@wrongstack/core';\nimport { stripAnsi } from '@wrongstack/core';\nimport { buildChildEnv } from './_env.js';\nimport { truncateMiddle } from './_util.js';\n\ninterface BashInput {\n command: string;\n timeout_ms?: number;\n background?: boolean;\n}\n\ninterface BashOutput {\n output: string;\n exit_code: number | null;\n timed_out: boolean;\n pid?: number;\n}\n\nconst MAX_OUTPUT = 32_768;\nconst DEFAULT_TIMEOUT = 30_000;\n// Flush partial_output every 200ms or when 4 KiB accumulates — whichever\n// comes first. Smaller batches make the TUI feel responsive; larger ones\n// keep EventBus traffic reasonable on chatty processes.\nconst STREAM_FLUSH_INTERVAL_MS = 200;\nconst STREAM_FLUSH_BYTES = 4 * 1024;\n\nexport const bashTool: Tool<BashInput, BashOutput> = {\n name: 'bash',\n category: 'Shell',\n description: 'Run a shell command. stdout and stderr are merged.',\n usageHint:\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.',\n permission: 'confirm',\n mutating: true,\n // Trust rules match on the literal `command` string. Without subjectKey\n // the policy heuristic would have done the same here, but declaring it\n // explicitly removes the implicit cross-tool aliasing.\n subjectKey: 'command',\n timeoutMs: 30_000,\n maxOutputBytes: MAX_OUTPUT,\n estimatedDurationMs: 3_000,\n inputSchema: {\n type: 'object',\n properties: {\n command: { type: 'string' },\n timeout_ms: { type: 'integer' },\n background: { type: 'boolean' },\n },\n required: ['command'],\n },\n async execute(input, ctx, opts) {\n let final: BashOutput | undefined;\n for await (const ev of bashTool.executeStream!(input, ctx, opts)) {\n if (ev.type === 'final') final = ev.output;\n }\n if (!final) throw new Error('bash: stream ended without final event');\n return final;\n },\n async *executeStream(input, ctx, opts): AsyncGenerator<ToolStreamEvent<BashOutput>> {\n if (!input?.command) throw new Error('bash: command is required');\n const timeoutMs = Math.max(1, Math.min(input.timeout_ms ?? DEFAULT_TIMEOUT, 600_000));\n\n const isWin = os.platform() === 'win32';\n const shell = isWin\n ? (process.env['COMSPEC'] ?? 'cmd.exe')\n : (process.env['SHELL'] ?? '/bin/bash');\n const args = isWin ? ['/c', input.command] : ['-c', input.command];\n\n const env = buildChildEnv(ctx.session?.id);\n\n // On POSIX we put the shell in its own process group so that timeout /\n // abort can kill the entire group with `process.kill(-pid)`. Otherwise\n // `bash -c \"sleep 9999 & disown\"` would leave the grandchild running.\n // `detached: true` is also reused for the user-facing background mode;\n // we always want detached on POSIX, only on Windows is it tied to the\n // explicit background flag.\n const detached = isWin ? !!input.background : true;\n const child = spawn(shell, args, {\n cwd: ctx.projectRoot,\n env,\n stdio: input.background ? 'ignore' : ['ignore', 'pipe', 'pipe'],\n detached,\n signal: opts.signal,\n });\n\n if (input.background) {\n const pid = child.pid;\n if (typeof pid === 'number') child.unref();\n yield {\n type: 'final',\n output: {\n output: `[background] pid=${pid ?? 'unknown'}`,\n exit_code: null,\n timed_out: false,\n pid,\n },\n };\n return;\n }\n\n let buf = '';\n let pending = '';\n let timedOut = false;\n const timers: NodeJS.Timeout[] = [];\n const timer = setTimeout(() => {\n timedOut = true;\n if (isWin) {\n try {\n child.kill();\n } catch {\n /* ignore */\n }\n } else {\n try {\n // Kill the process group, not just the shell — pid is positive,\n // group id is the negated pid. Without this a runaway grandchild\n // ('sleep 9999 & disown') survives bash termination.\n if (typeof child.pid === 'number') {\n try {\n process.kill(-child.pid, 'SIGTERM');\n } catch {\n child.kill('SIGTERM');\n }\n } else {\n child.kill('SIGTERM');\n }\n const killTimer = setTimeout(() => {\n try {\n if (typeof child.pid === 'number') {\n try {\n process.kill(-child.pid, 'SIGKILL');\n } catch {\n child.kill('SIGKILL');\n }\n } else {\n child.kill('SIGKILL');\n }\n } catch {\n /* ignore */\n }\n }, 2000);\n timers.push(killTimer);\n } catch {\n /* ignore */\n }\n }\n }, timeoutMs);\n timers.push(timer);\n timer.unref?.();\n\n // Bridge the EventEmitter-style child to an async iterator. We push\n // chunks into a queue and let the generator pull them; this lets us\n // yield 'partial_output' events to the executor at flush boundaries.\n type Chunk =\n | { kind: 'data'; text: string }\n | { kind: 'end'; code: number | null }\n | { kind: 'error'; err: Error };\n const queue: Chunk[] = [];\n let resolveNext: ((c: Chunk) => void) | null = null;\n const push = (c: Chunk) => {\n if (resolveNext) {\n const r = resolveNext;\n resolveNext = null;\n r(c);\n } else {\n queue.push(c);\n }\n };\n const next = (): Promise<Chunk> =>\n new Promise((resolve) => {\n const c = queue.shift();\n if (c) resolve(c);\n else resolveNext = resolve;\n });\n\n let lastFlush = Date.now();\n const flush = () => {\n if (pending.length === 0) return null;\n const text = pending;\n pending = '';\n lastFlush = Date.now();\n return text;\n };\n\n child.stdout?.on('data', (chunk) => {\n const text = chunk.toString();\n buf += text;\n pending += text;\n push({ kind: 'data', text });\n });\n child.stderr?.on('data', (chunk) => {\n const text = chunk.toString();\n buf += text;\n pending += text;\n push({ kind: 'data', text });\n });\n child.on('error', (err) => {\n for (const t of timers) clearTimeout(t);\n push({ kind: 'error', err });\n });\n child.on('close', (code) => {\n for (const t of timers) clearTimeout(t);\n push({ kind: 'end', code });\n });\n\n try {\n while (true) {\n const c = await next();\n if (c.kind === 'error') throw c.err;\n if (c.kind === 'end') {\n const remainder = flush();\n if (remainder !== null) {\n yield { type: 'partial_output', text: remainder };\n }\n const cleaned = stripAnsi(buf).replace(/\\r\\n?/g, '\\n');\n yield {\n type: 'final',\n output: {\n output: truncateMiddle(cleaned, MAX_OUTPUT),\n exit_code: c.code,\n timed_out: timedOut,\n },\n };\n return;\n }\n // Decide whether to flush. Time-based OR size-based to keep latency\n // low for slow-emitting commands without overwhelming the TUI for\n // chatty ones.\n const now = Date.now();\n if (pending.length >= STREAM_FLUSH_BYTES || now - lastFlush >= STREAM_FLUSH_INTERVAL_MS) {\n const text = flush();\n if (text) yield { type: 'partial_output', text };\n }\n }\n } finally {\n for (const t of timers) clearTimeout(t);\n }\n },\n};\n\n// Re-export types so consumers can narrow on stream events.\nexport type { BashInput, BashOutput };\n"]}
@@ -1,6 +1,7 @@
1
1
  // src/batch-tool-use.ts
2
2
  var batchToolUseTool = {
3
3
  name: "batch_tool_use",
4
+ category: "Meta",
4
5
  description: "Execute multiple tool calls in sequence or parallel. Returns all results.",
5
6
  usageHint: "Set `calls` array with tool names and inputs. `stop_on_error` halts on first failure. `parallel` runs concurrently (default: true).",
6
7
  permission: "confirm",
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/batch-tool-use.ts"],"names":[],"mappings":";AAyBO,IAAM,gBAAA,GAAgE;AAAA,EAC3E,IAAA,EAAM,gBAAA;AAAA,EACN,WAAA,EAAa,2EAAA;AAAA,EACb,SAAA,EACE,qIAAA;AAAA,EACF,UAAA,EAAY,SAAA;AAAA,EACZ,QAAA,EAAU,IAAA;AAAA,EACV,SAAA,EAAW,IAAA;AAAA,EACX,WAAA,EAAa;AAAA,IACX,IAAA,EAAM,QAAA;AAAA,IACN,UAAA,EAAY;AAAA,MACV,KAAA,EAAO;AAAA,QACL,IAAA,EAAM,OAAA;AAAA,QACN,KAAA,EAAO;AAAA,UACL,IAAA,EAAM,QAAA;AAAA,UACN,UAAA,EAAY;AAAA,YACV,IAAA,EAAM,EAAE,IAAA,EAAM,QAAA,EAAS;AAAA,YACvB,KAAA,EAAO,EAAE,IAAA,EAAM,QAAA;AAAS,WAC1B;AAAA,UACA,QAAA,EAAU,CAAC,MAAM;AAAA,SACnB;AAAA,QACA,WAAA,EAAa;AAAA,OACf;AAAA,MACA,aAAA,EAAe;AAAA,QACb,IAAA,EAAM,SAAA;AAAA,QACN,WAAA,EAAa;AAAA,OACf;AAAA,MACA,QAAA,EAAU;AAAA,QACR,IAAA,EAAM,SAAA;AAAA,QACN,WAAA,EAAa;AAAA;AACf,KACF;AAAA,IACA,QAAA,EAAU,CAAC,OAAO;AAAA,GACpB;AAAA,EACA,MAAM,OAAA,CAAQ,KAAA,EAAO,GAAA,EAAK,IAAA,EAAM;AAC9B,IAAA,IAAI,CAAC,KAAA,EAAO,KAAA,IAAS,KAAA,CAAM,KAAA,CAAM,WAAW,CAAA,EAAG;AAC7C,MAAA,OAAO;AAAA,QACL,SAAS,EAAC;AAAA,QACV,KAAA,EAAO,CAAA;AAAA,QACP,SAAA,EAAW,CAAA;AAAA,QACX,MAAA,EAAQ,CAAA;AAAA,QACR,aAAA,EAAe;AAAA,OACjB;AAAA,IACF;AAEA,IAAA,MAAM,UAAyC,EAAC;AAChD,IAAA,IAAI,SAAA,GAAY,CAAA;AAChB,IAAA,IAAI,MAAA,GAAS,CAAA;AAEb,IAAA,IAAI,KAAA,CAAM,aAAa,KAAA,EAAO;AAC5B,MAAA,MAAM,QAAA,GAAW,KAAA,CAAM,KAAA,CAAM,GAAA,CAAI,OAAO,SAAS,aAAA,CAAc,IAAA,EAAM,GAAA,EAAK,IAAI,CAAC,CAAA;AAC/E,MAAA,MAAM,UAAA,GAAa,MAAM,OAAA,CAAQ,GAAA,CAAI,QAAQ,CAAA;AAC7C,MAAA,OAAA,CAAQ,IAAA,CAAK,GAAG,UAAU,CAAA;AAC1B,MAAA,SAAA,GAAY,WAAW,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,OAAO,CAAA,CAAE,MAAA;AAChD,MAAA,MAAA,GAAS,WAAW,MAAA,CAAO,CAAC,MAAM,CAAC,CAAA,CAAE,OAAO,CAAA,CAAE,MAAA;AAAA,IAChD,CAAA,MAAO;AACL,MAAA,KAAA,MAAW,IAAA,IAAQ,MAAM,KAAA,EAAO;AAC9B,QAAA,MAAM,MAAA,GAAS,MAAM,aAAA,CAAc,IAAA,EAAM,KAAK,IAAI,CAAA;AAClD,QAAA,OAAA,CAAQ,KAAK,MAAM,CAAA;AACnB,QAAA,IAAI,OAAO,OAAA,EAAS;AAClB,UAAA,SAAA,EAAA;AAAA,QACF,CAAA,MAAO;AACL,UAAA,MAAA,EAAA;AACA,UAAA,IAAI,MAAM,aAAA,EAAe;AAAA,QAC3B;AAAA,MACF;AAAA,IACF;AAEA,IAAA,OAAO;AAAA,MACL,OAAA;AAAA,MACA,KAAA,EAAO,MAAM,KAAA,CAAM,MAAA;AAAA,MACnB,SAAA;AAAA,MACA,MAAA;AAAA,MACA,aAAA,EAAe,MAAM,aAAA,IAAiB;AAAA,KACxC;AAAA,EACF;AACF;AAEA,eAAe,aAAA,CACb,IAAA,EACA,GAAA,EACA,IAAA,EAC2C;AAC3C,EAAA,MAAM,KAAA,GAAQ,KAAK,GAAA,EAAI;AACvB,EAAA,MAAM,IAAA,GAAO,IAAI,KAAA,CAAM,IAAA,CAAK,CAAC,CAAA,KAAY,CAAA,CAAE,IAAA,KAAS,IAAA,CAAK,IAAI,CAAA;AAE7D,EAAA,IAAI,CAAC,IAAA,EAAM;AACT,IAAA,OAAO;AAAA,MACL,MAAM,IAAA,CAAK,IAAA;AAAA,MACX,OAAA,EAAS,KAAA;AAAA,MACT,KAAA,EAAO,CAAA,MAAA,EAAS,IAAA,CAAK,IAAI,CAAA,WAAA,CAAA;AAAA,MACzB,WAAA,EAAa,IAAA,CAAK,GAAA,EAAI,GAAI;AAAA,KAC5B;AAAA,EACF;AAEA,EAAA,IAAI;AACF,IAAA,MAAM,SAAS,MAAM,IAAA,CAAK,QAAQ,IAAA,CAAK,KAAA,EAAO,KAAK,IAAI,CAAA;AACvD,IAAA,OAAO;AAAA,MACL,MAAM,IAAA,CAAK,IAAA;AAAA,MACX,OAAA,EAAS,IAAA;AAAA,MACT,MAAA;AAAA,MACA,WAAA,EAAa,IAAA,CAAK,GAAA,EAAI,GAAI;AAAA,KAC5B;AAAA,EACF,SAAS,CAAA,EAAG;AACV,IAAA,OAAO;AAAA,MACL,MAAM,IAAA,CAAK,IAAA;AAAA,MACX,OAAA,EAAS,KAAA;AAAA,MACT,OAAO,CAAA,YAAa,KAAA,GAAQ,CAAA,CAAE,OAAA,GAAU,OAAO,CAAC,CAAA;AAAA,MAChD,WAAA,EAAa,IAAA,CAAK,GAAA,EAAI,GAAI;AAAA,KAC5B;AAAA,EACF;AACF","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: '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: true,\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}\n"]}
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 category: 'Meta',\n description: '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: true,\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}\n"]}
package/dist/builtin.js CHANGED
@@ -125,6 +125,7 @@ function isBinaryBuffer(buf) {
125
125
  // src/audit.ts
126
126
  var auditTool = {
127
127
  name: "audit",
128
+ category: "Package Management",
128
129
  description: "Run npm/pnpm security audit. Returns vulnerabilities sorted by severity.",
129
130
  usageHint: "Set `level` to filter minimum severity. `fix` attempts auto-fix. `packages` checks specific packages.",
130
131
  permission: "confirm",
@@ -238,6 +239,7 @@ var STREAM_FLUSH_INTERVAL_MS = 200;
238
239
  var STREAM_FLUSH_BYTES = 4 * 1024;
239
240
  var bashTool = {
240
241
  name: "bash",
242
+ category: "Shell",
241
243
  description: "Run a shell command. stdout and stderr are merged.",
242
244
  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.",
243
245
  permission: "confirm",
@@ -417,6 +419,7 @@ var bashTool = {
417
419
  // src/batch-tool-use.ts
418
420
  var batchToolUseTool = {
419
421
  name: "batch_tool_use",
422
+ category: "Meta",
420
423
  description: "Execute multiple tool calls in sequence or parallel. Returns all results.",
421
424
  usageHint: "Set `calls` array with tool names and inputs. `stop_on_error` halts on first failure. `parallel` runs concurrently (default: true).",
422
425
  permission: "confirm",
@@ -518,6 +521,7 @@ async function executeSingle(call, ctx, opts) {
518
521
  }
519
522
  var diffTool = {
520
523
  name: "diff",
524
+ category: "Filesystem",
521
525
  description: "Show differences between files, commits, or branches. Supports staged vs working tree.",
522
526
  usageHint: "Use `files` for file paths, `a`/`b` for commit refs, `staged` for git index. `mode`: unified (default), stat, side-by-side.",
523
527
  permission: "auto",
@@ -634,6 +638,7 @@ function formatUnified(lines, context) {
634
638
  }
635
639
  var documentTool = {
636
640
  name: "document",
641
+ category: "Project",
637
642
  description: "Generate or update documentation comments for functions, classes, and types. Supports JSDoc, TSDoc, and block comments.",
638
643
  usageHint: "Set `target` for what to document. `files` for paths. `style` for comment format. `overwrite` replaces existing docs.",
639
644
  permission: "confirm",
@@ -782,6 +787,7 @@ function processFile(content, absPath, style, overwrite, target) {
782
787
  }
783
788
  var editTool = {
784
789
  name: "edit",
790
+ category: "Filesystem",
785
791
  description: "Make a surgical edit by replacing exact text. Fails if `old_string` is not unique unless `replace_all` is true.",
786
792
  usageHint: "Always `read` the file first. `old_string` must be an EXACT match (whitespace included). If multiple matches exist, either narrow `old_string` with more context or set `replace_all: true`.",
787
793
  permission: "confirm",
@@ -891,9 +897,9 @@ function findSimilarity(haystack, needle) {
891
897
  return line;
892
898
  }
893
899
  var ALLOWED_COMMANDS = {
894
- node: ["--version", "-e", "-p", "-r", "--input-type=module"],
895
- npm: ["--version", "init", "install", "test", "run", "list", "pkg", "doctor"],
896
- pnpm: ["--version", "init", "install", "add", "remove", "exec", "list", "run", "dlx"],
900
+ node: ["--version", "-r", "--input-type=module"],
901
+ npm: ["--version", "init", "install", "test", "list", "pkg", "doctor"],
902
+ pnpm: ["--version", "init", "install", "add", "remove", "list"],
897
903
  npx: ["--version"],
898
904
  git: [
899
905
  "--version",
@@ -921,7 +927,7 @@ var ALLOWED_COMMANDS = {
921
927
  mv: [],
922
928
  rm: ["-rf"],
923
929
  touch: [],
924
- bun: ["--version", "run", "add", "init"],
930
+ bun: ["--version", "add", "init"],
925
931
  tsc: ["--version", "--noEmit", "--project"],
926
932
  vitest: ["--version", "run", "--coverage"],
927
933
  biome: ["--version", "lint", "format", "check"],
@@ -938,6 +944,7 @@ var MAX_OUTPUT2 = 2e5;
938
944
  var TIMEOUT_MS = 3e4;
939
945
  var execTool = {
940
946
  name: "exec",
947
+ category: "Shell",
941
948
  description: "Restricted shell that only runs pre-approved commands with constrained arguments. Safer alternative to `bash`.",
942
949
  usageHint: "Set `command` (must be in allowlist). `args` passed through. For arbitrary shell access use the `bash` tool instead.",
943
950
  permission: "confirm",
@@ -1083,6 +1090,7 @@ async function fetchWithRedirectLimit(url, maxRedirects, signal) {
1083
1090
  }
1084
1091
  var fetchTool = {
1085
1092
  name: "fetch",
1093
+ category: "Network",
1086
1094
  description: "Fetch the contents of a URL. HTML is converted to markdown by default.",
1087
1095
  usageHint: "HTTPS only by default. Localhost and RFC1918 ranges blocked unless WRONGSTACK_FETCH_ALLOW_PRIVATE=1. Max 5 redirects, 20s timeout, 128KB cap.",
1088
1096
  permission: "confirm",
@@ -1314,7 +1322,10 @@ function htmlToMarkdown(html) {
1314
1322
  });
1315
1323
  s = s.replace(/<(strong|b)[^>]*>([\s\S]*?)<\/\1>/gi, "**$2**");
1316
1324
  s = s.replace(/<(em|i)[^>]*>([\s\S]*?)<\/\1>/gi, "*$2*");
1317
- s = s.replace(/<a [^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/gi, "[$2]($1)");
1325
+ s = s.replace(/<a [^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/gi, (_m, href, text) => {
1326
+ const safe = /^(https?|ftps?):\/\//i.test(href);
1327
+ return safe ? `[${text}](${href})` : text;
1328
+ });
1318
1329
  s = s.replace(/<pre[^>]*>([\s\S]*?)<\/pre>/gi, (_m, c) => "\n```\n" + stripTags(c) + "\n```\n");
1319
1330
  s = s.replace(/<code[^>]*>([\s\S]*?)<\/code>/gi, "`$1`");
1320
1331
  s = s.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, "- $1\n");
@@ -1331,6 +1342,7 @@ function stripTags(s) {
1331
1342
  // src/format.ts
1332
1343
  var formatTool = {
1333
1344
  name: "format",
1345
+ category: "Code Quality",
1334
1346
  description: "Format files with biome or prettier. Use `check` to verify without modifying.",
1335
1347
  usageHint: "Set `files` (glob or comma-separated). `check` only validates. `fixer` forces tool.",
1336
1348
  permission: "confirm",
@@ -1429,6 +1441,7 @@ var TIMEOUT_MS3 = 3e4;
1429
1441
  var MAX_OUTPUT3 = 1e5;
1430
1442
  var gitTool = {
1431
1443
  name: "git",
1444
+ category: "Git",
1432
1445
  description: "Run git commands. Wraps common operations: status, log, diff, commit, branch, checkout, stash, push, pull, fetch, reset.",
1433
1446
  usageHint: "Prefer built-in subcommands over raw args. `command` is required. `message` for commits. `branch` for checkout/branch. `files` for status/diff. `format` for log.",
1434
1447
  permission: "confirm",
@@ -1530,11 +1543,11 @@ function buildArgs(input) {
1530
1543
  ...files.length ? ["--", ...files] : []
1531
1544
  ];
1532
1545
  case "branch":
1533
- return input.branch ? ["branch", input.branch] : ["branch"];
1546
+ return input.branch ? ["branch", ...input.branch.startsWith("-") ? [] : [input.branch]] : ["branch"];
1534
1547
  case "checkout":
1535
1548
  return [
1536
1549
  "checkout",
1537
- ...input.branch ? [input.branch] : [],
1550
+ ...input.branch ? ["--", input.branch] : [],
1538
1551
  ...files.length ? ["--", ...files] : []
1539
1552
  ];
1540
1553
  case "stash":
@@ -1593,6 +1606,7 @@ function runGit2(args, cwd, signal) {
1593
1606
  var DEFAULT_IGNORE = ["node_modules", ".git", "dist", "build", ".next", "coverage", ".turbo"];
1594
1607
  var globTool = {
1595
1608
  name: "glob",
1609
+ category: "Filesystem",
1596
1610
  description: "Find files matching a glob pattern. Returns paths sorted by mtime (newest first).",
1597
1611
  usageHint: "Examples: `**/*.ts`, `src/**/*.test.ts`, `*.json`. Common dirs (node_modules, .git, dist) are ignored by default. Returns up to 1000 paths.",
1598
1612
  permission: "auto",
@@ -1709,6 +1723,7 @@ function capSubject(line) {
1709
1723
  var DEFAULT_IGNORE2 = ["node_modules", ".git", "dist", "build", ".next", "coverage"];
1710
1724
  var grepTool = {
1711
1725
  name: "grep",
1726
+ category: "Search",
1712
1727
  description: "Search file contents with a regex. Uses ripgrep when available.",
1713
1728
  usageHint: 'Pattern is regex. Use `output_mode: "content"` for matched lines, `"files_with_matches"` for paths, `"count"` for tallies. `glob` filters files (e.g. `*.ts`).',
1714
1729
  permission: "auto",
@@ -1741,6 +1756,10 @@ var grepTool = {
1741
1756
  const base = input.path ? safeResolve(input.path, ctx) : ctx.cwd;
1742
1757
  const mode = input.output_mode ?? "content";
1743
1758
  const limit = Math.max(1, Math.min(input.limit ?? 200, 2e3));
1759
+ const validation = compileUserRegex(input.pattern, input.case_insensitive ? "i" : "");
1760
+ if (!validation.ok) {
1761
+ throw new Error(`grep: ${validation.reason}`);
1762
+ }
1744
1763
  const rgAvailable = await detectRg(opts.signal);
1745
1764
  if (rgAvailable) {
1746
1765
  try {
@@ -1774,11 +1793,15 @@ async function* runRgStream(input, base, mode, limit, signal) {
1774
1793
  args.push("-n");
1775
1794
  if (input.context_lines) args.push("-C", String(input.context_lines));
1776
1795
  }
1796
+ for (const ignored of DEFAULT_IGNORE2) {
1797
+ args.push("--glob", `!${ignored}/**`, "--glob", `!**/${ignored}/**`);
1798
+ }
1777
1799
  if (input.glob) args.push("--glob", input.glob);
1778
1800
  args.push("--", input.pattern, base);
1779
1801
  const matches = [];
1780
1802
  let buf = "";
1781
1803
  let totalLines = 0;
1804
+ let totalCount = 0;
1782
1805
  let batchSinceFlush = 0;
1783
1806
  const FLUSH_AT = 16;
1784
1807
  const MAX_BUF_BYTES = 1e6;
@@ -1835,6 +1858,7 @@ async function* runRgStream(input, base, mode, limit, signal) {
1835
1858
  for (const line of ready.split("\n")) {
1836
1859
  if (!line) continue;
1837
1860
  totalLines++;
1861
+ if (mode === "count") totalCount += parseRgCountLine(line);
1838
1862
  if (matches.length < limit) {
1839
1863
  matches.push(line);
1840
1864
  pendingBatch.push(line);
@@ -1855,6 +1879,7 @@ async function* runRgStream(input, base, mode, limit, signal) {
1855
1879
  for (const line of buf.split("\n")) {
1856
1880
  if (!line) continue;
1857
1881
  totalLines++;
1882
+ if (mode === "count") totalCount += parseRgCountLine(line);
1858
1883
  if (matches.length < limit) {
1859
1884
  matches.push(line);
1860
1885
  pendingBatch.push(line);
@@ -1873,12 +1898,18 @@ async function* runRgStream(input, base, mode, limit, signal) {
1873
1898
  type: "final",
1874
1899
  output: {
1875
1900
  matches,
1876
- count: totalLines,
1901
+ count: mode === "count" ? totalCount : totalLines,
1877
1902
  truncated: totalLines > limit || bufOverflow,
1878
1903
  used: "rg"
1879
1904
  }
1880
1905
  };
1881
1906
  }
1907
+ function parseRgCountLine(line) {
1908
+ const idx = line.lastIndexOf(":");
1909
+ if (idx === -1) return 0;
1910
+ const n = Number.parseInt(line.slice(idx + 1), 10);
1911
+ return Number.isFinite(n) ? n : 0;
1912
+ }
1882
1913
  async function runNative(input, base, mode, limit, signal) {
1883
1914
  const flags = input.case_insensitive ? "i" : "";
1884
1915
  const compiled = compileUserRegex(input.pattern, flags);
@@ -1955,6 +1986,7 @@ async function runNative(input, base, mode, limit, signal) {
1955
1986
  // src/install.ts
1956
1987
  var installTool = {
1957
1988
  name: "install",
1989
+ category: "Package Management",
1958
1990
  description: "Install npm packages. Detects pnpm/npm/yarn and uses the right package manager.",
1959
1991
  usageHint: "Set `packages` to install. `save` as dependency type. `global` for global install. `dry_run` to preview.",
1960
1992
  permission: "confirm",
@@ -2048,6 +2080,7 @@ async function detectPackageManager(cwd) {
2048
2080
  }
2049
2081
  var jsonTool = {
2050
2082
  name: "json",
2083
+ category: "Data",
2051
2084
  description: "Parse, query, and validate JSON/JSON5/YAML. Use `query` with JMESPath-like paths to extract values.",
2052
2085
  usageHint: 'Provide `file` path or `data` string. `query` supports dot notation (e.g. "results[0].name"). `format` outputs in specified format.',
2053
2086
  permission: "auto",
@@ -2170,6 +2203,7 @@ function toYaml(data, indent = 0) {
2170
2203
  // src/lint.ts
2171
2204
  var lintTool = {
2172
2205
  name: "lint",
2206
+ category: "Code Quality",
2173
2207
  description: "Run a linter on files. Auto-detects biome, eslint, or tslint. Use `fix` to auto-fix issues.",
2174
2208
  usageHint: "Set `files` (glob or comma-separated). `fix` applies corrections. `linter` forces specific tool.",
2175
2209
  permission: "confirm",
@@ -2259,6 +2293,7 @@ async function detectLinter(cwd) {
2259
2293
  }
2260
2294
  var logsTool = {
2261
2295
  name: "logs",
2296
+ category: "Logs",
2262
2297
  description: "Stream or fetch logs from a service or file. Supports Docker, systemd, or plain log files.",
2263
2298
  usageHint: "Set `service` for Docker/systemd, `path` for file. `lines` limits output. `stream` for tail -f behavior. `filter` regex filters lines.",
2264
2299
  permission: "confirm",
@@ -2436,6 +2471,7 @@ function parseLine(line) {
2436
2471
  }
2437
2472
  var outdatedTool = {
2438
2473
  name: "outdated",
2474
+ category: "Package Management",
2439
2475
  description: "Check for outdated npm packages. Shows current, wanted, and latest versions.",
2440
2476
  usageHint: "Set `check` to filter specific packages. `format` as list or table. `include_deprecated` shows deprecated packages.",
2441
2477
  permission: "auto",
@@ -2547,6 +2583,7 @@ function parseOutdatedOutput(json, exitCode) {
2547
2583
  }
2548
2584
  var patchTool = {
2549
2585
  name: "patch",
2586
+ category: "Filesystem",
2550
2587
  description: "Apply a unified diff patch to files. Writes .orig and .rej files on failure.",
2551
2588
  usageHint: "Set `patch` (the diff text). `directory` defaults to cwd. `strip` removes leading path components. `dry_run` previews.",
2552
2589
  permission: "confirm",
@@ -2655,6 +2692,7 @@ function extractPatchedFiles(output) {
2655
2692
  }
2656
2693
  var planTool = {
2657
2694
  name: "plan",
2695
+ category: "Session",
2658
2696
  description: "Inspect or edit the strategic plan board for this session. Plans persist across resume (unlike todos). Use this to lay out the multi-step approach before diving in, then mark steps in_progress/done as the work proceeds.",
2659
2697
  usageHint: "Set action to one of: show | add | start | done | remove | clear. Pass `title` for add. Pass `target` (item id, 1-based index, or title substring) for start/done/remove. Always returns the formatted plan plus open/total counts.",
2660
2698
  permission: "auto",
@@ -2753,6 +2791,7 @@ function mkResult(plan, ok, message) {
2753
2791
  var MAX_BYTES2 = 5 * 1024 * 1024;
2754
2792
  var readTool = {
2755
2793
  name: "read",
2794
+ category: "Filesystem",
2756
2795
  description: "Read the contents of a file. Lines are 1-indexed and prefixed with line numbers.",
2757
2796
  usageHint: "Read a file before editing it. Returns lines numbered like ` 1\u2192content`. Use `offset` and `limit` for large files (default reads up to 2000 lines).",
2758
2797
  permission: "auto",
@@ -2805,6 +2844,7 @@ var readTool = {
2805
2844
  var DEFAULT_IGNORE3 = ["node_modules", ".git", "dist", "build", ".next", "coverage"];
2806
2845
  var replaceTool = {
2807
2846
  name: "replace",
2847
+ category: "Transform",
2808
2848
  description: "Batch replace a pattern across multiple files matched by glob. Returns diff for each modified file.",
2809
2849
  usageHint: 'Use `glob` for broad patterns (e.g. "**/*.ts"). Set `dry_run: true` to preview without modifying. `files` can be a single path, comma-separated list, or glob pattern.',
2810
2850
  permission: "confirm",
@@ -3084,6 +3124,7 @@ describe('{{Name}}', () => {
3084
3124
  };
3085
3125
  var scaffoldTool = {
3086
3126
  name: "scaffold",
3127
+ category: "Project",
3087
3128
  description: "Generate boilerplate code from built-in templates or paths. Creates package.json, source files, tests.",
3088
3129
  usageHint: "Set `template` (npm-package, cli-tool, react-component) and `name`. `vars` for template variables. `dry_run` preview.",
3089
3130
  permission: "confirm",
@@ -3172,6 +3213,7 @@ var MAX_RESULTS = 50;
3172
3213
  var TIMEOUT_MS4 = 15e3;
3173
3214
  var searchTool = {
3174
3215
  name: "search",
3216
+ category: "Search",
3175
3217
  description: "Search the web for information. Returns title, URL, and snippet for each result.",
3176
3218
  usageHint: "Set `num_results` (1-50, default 10). Use `source` to pick engine: duckduckgo (default), google, bing.",
3177
3219
  permission: "confirm",
@@ -3380,6 +3422,7 @@ function stripTags2(html) {
3380
3422
  }
3381
3423
  var testTool = {
3382
3424
  name: "test",
3425
+ category: "Code Quality",
3383
3426
  description: "Run tests with vitest, jest, or mocha. Returns pass/fail counts and output.",
3384
3427
  usageHint: "Set `files` for specific tests. `watch` enables watch mode. `coverage` generates coverage report. `grep` filters by name.",
3385
3428
  permission: "confirm",
@@ -3527,6 +3570,7 @@ function parseResult(runner, result, duration) {
3527
3570
  // src/todo.ts
3528
3571
  var todoTool = {
3529
3572
  name: "todo",
3573
+ category: "Session",
3530
3574
  description: "Replace the current todo list with a new set of items.",
3531
3575
  usageHint: "Use for multi-step tasks. Replace the full list on each call. At most ONE task may be in_progress at a time. Items have id, content, status (pending|in_progress|completed), and optional activeForm.",
3532
3576
  permission: "auto",
@@ -3577,6 +3621,7 @@ var todoTool = {
3577
3621
  // src/tool-help.ts
3578
3622
  var toolHelpTool = {
3579
3623
  name: "tool_help",
3624
+ category: "Meta",
3580
3625
  description: "Get help and usage information for a specific tool or list all available tools.",
3581
3626
  usageHint: "Set `tool` for specific help. Omit to list all tools. `format`: short (one-liner), full (schema), markdown (formatted).",
3582
3627
  permission: "auto",
@@ -3698,6 +3743,7 @@ function formatAllToolsMarkdown(tools) {
3698
3743
  // src/tool-search.ts
3699
3744
  var toolSearchTool = {
3700
3745
  name: "tool_search",
3746
+ category: "Meta",
3701
3747
  description: "Search available tools by name, description, tags, permission level, or mutating flag.",
3702
3748
  usageHint: "Set `query` for keyword search. `tags` to filter by category. `permission` to filter by required permission. `mutating` to filter by mutating flag.",
3703
3749
  permission: "auto",
@@ -3765,6 +3811,7 @@ var toolSearchTool = {
3765
3811
  // src/tool-use.ts
3766
3812
  var toolUseTool = {
3767
3813
  name: "tool_use",
3814
+ category: "Meta",
3768
3815
  description: "Execute a specific tool by name with given input. Useful when the agent knows exactly which tool to call.",
3769
3816
  usageHint: "Set `tool` with exact tool name and `input` with the tool parameters. Returns result or error.",
3770
3817
  permission: "confirm",
@@ -3840,6 +3887,7 @@ var DEFAULT_IGNORE4 = [
3840
3887
  ];
3841
3888
  var treeTool = {
3842
3889
  name: "tree",
3890
+ category: "Filesystem",
3843
3891
  description: "Display directory structure as an ASCII tree. Shows files and folders with indentation.",
3844
3892
  usageHint: "Set `path` (default: cwd). `depth` limits nesting (default: 3). `glob` filters files. `exclude` ignores dirs. `show_files` toggles file listing (default: true).",
3845
3893
  permission: "auto",
@@ -3994,6 +4042,7 @@ async function walkDir(dir, depth, opts) {
3994
4042
  }
3995
4043
  var typecheckTool = {
3996
4044
  name: "typecheck",
4045
+ category: "Code Quality",
3997
4046
  description: "Run TypeScript type checking with `tsc --noEmit`. Checks for type errors without compiling.",
3998
4047
  usageHint: "Set `project` for tsconfig path (default: nearest). `strict` enables strictest flags. `all` checks all projects in workspace.",
3999
4048
  permission: "confirm",
@@ -4073,6 +4122,7 @@ async function findTsConfig(cwd) {
4073
4122
  }
4074
4123
  var writeTool = {
4075
4124
  name: "write",
4125
+ category: "Filesystem",
4076
4126
  description: "Write or overwrite a file. For existing files, prefer `edit` over `write`.",
4077
4127
  usageHint: "Use `write` for new files or full replacements. For partial edits use `edit`. Existing files must have been `read` first in this session.",
4078
4128
  permission: "confirm",