@wrongstack/tools 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audit.d.ts +25 -0
- package/dist/audit.js +209 -0
- package/dist/audit.js.map +1 -0
- package/dist/bash.d.ts +16 -0
- package/dist/bash.js +180 -0
- package/dist/bash.js.map +1 -0
- package/dist/batch-tool-use.d.ts +26 -0
- package/dist/batch-tool-use.js +106 -0
- package/dist/batch-tool-use.js.map +1 -0
- package/dist/builtin.d.ts +5 -0
- package/dist/builtin.js +3735 -0
- package/dist/builtin.js.map +1 -0
- package/dist/diff.d.ts +20 -0
- package/dist/diff.js +142 -0
- package/dist/diff.js.map +1 -0
- package/dist/document.d.ts +27 -0
- package/dist/document.js +148 -0
- package/dist/document.js.map +1 -0
- package/dist/edit.d.ts +22 -0
- package/dist/edit.js +138 -0
- package/dist/edit.js.map +1 -0
- package/dist/exec.d.ts +21 -0
- package/dist/exec.js +159 -0
- package/dist/exec.js.map +1 -0
- package/dist/fetch.d.ts +15 -0
- package/dist/fetch.js +213 -0
- package/dist/fetch.js.map +1 -0
- package/dist/format.d.ts +18 -0
- package/dist/format.js +194 -0
- package/dist/format.js.map +1 -0
- package/dist/git.d.ts +27 -0
- package/dist/git.js +174 -0
- package/dist/git.js.map +1 -0
- package/dist/glob.d.ts +14 -0
- package/dist/glob.js +101 -0
- package/dist/glob.js.map +1 -0
- package/dist/grep.d.ts +20 -0
- package/dist/grep.js +264 -0
- package/dist/grep.js.map +1 -0
- package/dist/index.d.ts +34 -563
- package/dist/index.js +717 -442
- package/dist/index.js.map +1 -1
- package/dist/install.d.ts +19 -0
- package/dist/install.js +186 -0
- package/dist/install.js.map +1 -0
- package/dist/json.d.ts +20 -0
- package/dist/json.js +124 -0
- package/dist/json.js.map +1 -0
- package/dist/lint.d.ts +20 -0
- package/dist/lint.js +191 -0
- package/dist/lint.js.map +1 -0
- package/dist/logs.d.ts +27 -0
- package/dist/logs.js +180 -0
- package/dist/logs.js.map +1 -0
- package/dist/memory.d.ts +22 -0
- package/dist/memory.js +53 -0
- package/dist/memory.js.map +1 -0
- package/dist/mode.d.ts +20 -0
- package/dist/mode.js +81 -0
- package/dist/mode.js.map +1 -0
- package/dist/outdated.d.ts +26 -0
- package/dist/outdated.js +138 -0
- package/dist/outdated.js.map +1 -0
- package/dist/patch.d.ts +18 -0
- package/dist/patch.js +101 -0
- package/dist/patch.js.map +1 -0
- package/dist/read.d.ts +16 -0
- package/dist/read.js +81 -0
- package/dist/read.js.map +1 -0
- package/dist/replace.d.ts +23 -0
- package/dist/replace.js +196 -0
- package/dist/replace.js.map +1 -0
- package/dist/scaffold.d.ts +20 -0
- package/dist/scaffold.js +185 -0
- package/dist/scaffold.js.map +1 -0
- package/dist/search.d.ts +20 -0
- package/dist/search.js +212 -0
- package/dist/search.js.map +1 -0
- package/dist/test.d.ts +24 -0
- package/dist/test.js +247 -0
- package/dist/test.js.map +1 -0
- package/dist/todo.d.ts +12 -0
- package/dist/todo.js +53 -0
- package/dist/todo.js.map +1 -0
- package/dist/tool-help.d.ts +23 -0
- package/dist/tool-help.js +122 -0
- package/dist/tool-help.js.map +1 -0
- package/dist/tool-search.d.ts +22 -0
- package/dist/tool-search.js +70 -0
- package/dist/tool-search.js.map +1 -0
- package/dist/tool-use.d.ts +16 -0
- package/dist/tool-use.js +79 -0
- package/dist/tool-use.js.map +1 -0
- package/dist/tree.d.ts +21 -0
- package/dist/tree.js +176 -0
- package/dist/tree.js.map +1 -0
- package/dist/typecheck.d.ts +19 -0
- package/dist/typecheck.js +181 -0
- package/dist/typecheck.js.map +1 -0
- package/dist/write.d.ts +15 -0
- package/dist/write.js +77 -0
- package/dist/write.js.map +1 -0
- package/package.json +137 -4
package/dist/logs.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Tool } from '@wrongstack/core';
|
|
2
|
+
|
|
3
|
+
interface LogsInput {
|
|
4
|
+
service?: string;
|
|
5
|
+
path?: string;
|
|
6
|
+
lines?: number;
|
|
7
|
+
stream?: boolean;
|
|
8
|
+
filter?: string;
|
|
9
|
+
since?: '1h' | '6h' | '24h' | 'all';
|
|
10
|
+
cwd?: string;
|
|
11
|
+
}
|
|
12
|
+
interface LogEntry {
|
|
13
|
+
timestamp: string;
|
|
14
|
+
level: string;
|
|
15
|
+
message: string;
|
|
16
|
+
source?: string;
|
|
17
|
+
}
|
|
18
|
+
interface LogsOutput {
|
|
19
|
+
source: string;
|
|
20
|
+
entries: LogEntry[];
|
|
21
|
+
total: number;
|
|
22
|
+
truncated: boolean;
|
|
23
|
+
stream_mode: boolean;
|
|
24
|
+
}
|
|
25
|
+
declare const logsTool: Tool<LogsInput, LogsOutput>;
|
|
26
|
+
|
|
27
|
+
export { logsTool };
|
package/dist/logs.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
|
|
4
|
+
// src/logs.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
|
+
|
|
21
|
+
// src/logs.ts
|
|
22
|
+
var logsTool = {
|
|
23
|
+
name: "logs",
|
|
24
|
+
description: "Stream or fetch logs from a service or file. Supports Docker, systemd, or plain log files.",
|
|
25
|
+
usageHint: "Set `service` for Docker/systemd, `path` for file. `lines` limits output. `stream` for tail -f behavior. `filter` regex filters lines.",
|
|
26
|
+
permission: "confirm",
|
|
27
|
+
mutating: false,
|
|
28
|
+
timeoutMs: 3e4,
|
|
29
|
+
inputSchema: {
|
|
30
|
+
type: "object",
|
|
31
|
+
properties: {
|
|
32
|
+
service: {
|
|
33
|
+
type: "string",
|
|
34
|
+
description: "Service name for Docker or systemd journal"
|
|
35
|
+
},
|
|
36
|
+
path: {
|
|
37
|
+
type: "string",
|
|
38
|
+
description: "Path to log file (alternative to service)"
|
|
39
|
+
},
|
|
40
|
+
lines: {
|
|
41
|
+
type: "integer",
|
|
42
|
+
description: "Number of log lines to fetch (default: 100, 0 for all)",
|
|
43
|
+
minimum: 0,
|
|
44
|
+
maximum: 1e4
|
|
45
|
+
},
|
|
46
|
+
stream: {
|
|
47
|
+
type: "boolean",
|
|
48
|
+
description: "Stream logs continuously (like tail -f) (default: false)"
|
|
49
|
+
},
|
|
50
|
+
filter: {
|
|
51
|
+
type: "string",
|
|
52
|
+
description: "Regex pattern to filter log lines"
|
|
53
|
+
},
|
|
54
|
+
since: {
|
|
55
|
+
type: "string",
|
|
56
|
+
enum: ["1h", "6h", "24h", "all"],
|
|
57
|
+
description: "Only show logs since duration"
|
|
58
|
+
},
|
|
59
|
+
cwd: { type: "string", description: "Working directory (default: cwd)" }
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
async execute(input, ctx, opts) {
|
|
63
|
+
const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
|
|
64
|
+
const lines = input.lines ?? 100;
|
|
65
|
+
const filterRe = input.filter ? new RegExp(input.filter, "i") : null;
|
|
66
|
+
if (input.service) {
|
|
67
|
+
return await dockerLogs(input.service, lines, filterRe, cwd, opts.signal);
|
|
68
|
+
}
|
|
69
|
+
if (input.path) {
|
|
70
|
+
return await fileLogs(safeResolve(input.path, ctx), lines, filterRe, input.stream ?? false);
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
source: "none",
|
|
74
|
+
entries: [],
|
|
75
|
+
total: 0,
|
|
76
|
+
truncated: false,
|
|
77
|
+
stream_mode: false
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
|
|
82
|
+
const args = ["logs"];
|
|
83
|
+
if (lines > 0) args.push("--tail", String(lines));
|
|
84
|
+
args.push("--timestamps", service);
|
|
85
|
+
return new Promise((resolve2) => {
|
|
86
|
+
let stdout = "";
|
|
87
|
+
let stderr = "";
|
|
88
|
+
const MAX = 2e5;
|
|
89
|
+
const child = spawn("docker", args, { cwd, signal, stdio: ["ignore", "pipe", "pipe"] });
|
|
90
|
+
child.stdout?.on("data", (c) => {
|
|
91
|
+
if (stdout.length < MAX) stdout += c.toString();
|
|
92
|
+
});
|
|
93
|
+
child.stderr?.on("data", (c) => {
|
|
94
|
+
if (stderr.length < MAX) stderr += c.toString();
|
|
95
|
+
});
|
|
96
|
+
child.on("close", (code) => {
|
|
97
|
+
const output = stdout + stderr;
|
|
98
|
+
const entries = parseLogLines(output, filterRe);
|
|
99
|
+
resolve2({
|
|
100
|
+
source: `docker:${service}`,
|
|
101
|
+
entries,
|
|
102
|
+
total: entries.length,
|
|
103
|
+
truncated: output.length >= MAX,
|
|
104
|
+
stream_mode: false
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
child.on("error", (e) => resolve2({
|
|
108
|
+
source: `docker:${service}`,
|
|
109
|
+
entries: [],
|
|
110
|
+
total: 0,
|
|
111
|
+
truncated: false,
|
|
112
|
+
stream_mode: false
|
|
113
|
+
}));
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
async function fileLogs(path2, lines, filterRe, stream) {
|
|
117
|
+
const { createInterface } = await import('readline');
|
|
118
|
+
const { createReadStream } = await import('fs');
|
|
119
|
+
const entries = [];
|
|
120
|
+
const allLines = [];
|
|
121
|
+
const rl = createInterface({
|
|
122
|
+
input: createReadStream(path2),
|
|
123
|
+
crlfDelay: Number.POSITIVE_INFINITY
|
|
124
|
+
});
|
|
125
|
+
for await (const line of rl) {
|
|
126
|
+
if (filterRe && !filterRe.test(line)) continue;
|
|
127
|
+
allLines.push(line);
|
|
128
|
+
}
|
|
129
|
+
const sliced = lines > 0 ? allLines.slice(-lines) : allLines;
|
|
130
|
+
for (const line of sliced) {
|
|
131
|
+
const parsed = parseLine(line);
|
|
132
|
+
if (parsed) entries.push(parsed);
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
source: path2,
|
|
136
|
+
entries,
|
|
137
|
+
total: entries.length,
|
|
138
|
+
truncated: allLines.length > lines && lines > 0,
|
|
139
|
+
stream_mode: stream
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function parseLogLines(output, filterRe) {
|
|
143
|
+
const lines = output.split("\n").filter(Boolean);
|
|
144
|
+
const entries = [];
|
|
145
|
+
for (const line of lines) {
|
|
146
|
+
if (filterRe && !filterRe.test(line)) continue;
|
|
147
|
+
const parsed = parseLine(line);
|
|
148
|
+
if (parsed) entries.push(parsed);
|
|
149
|
+
}
|
|
150
|
+
return entries;
|
|
151
|
+
}
|
|
152
|
+
function parseLine(line) {
|
|
153
|
+
const tsRe = /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?)\s+(?:\[?(\w+)\]?)\s*(.*)/;
|
|
154
|
+
const match = tsRe.exec(line);
|
|
155
|
+
if (match) {
|
|
156
|
+
return {
|
|
157
|
+
timestamp: match[1] ?? "",
|
|
158
|
+
level: match[2]?.toLowerCase() ?? "info",
|
|
159
|
+
message: match[3] ?? ""
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
const levelRe = /(?:ERROR|WARN|INFO|DEBUG|TRACE)\s+(.*)/i;
|
|
163
|
+
const levelMatch = levelRe.exec(line);
|
|
164
|
+
if (levelMatch) {
|
|
165
|
+
return {
|
|
166
|
+
timestamp: "",
|
|
167
|
+
level: levelMatch[1]?.toLowerCase() ?? "info",
|
|
168
|
+
message: levelMatch[2] ?? line
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
timestamp: "",
|
|
173
|
+
level: "info",
|
|
174
|
+
message: line
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export { logsTool };
|
|
179
|
+
//# sourceMappingURL=logs.js.map
|
|
180
|
+
//# sourceMappingURL=logs.js.map
|
package/dist/logs.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/_util.ts","../src/logs.ts"],"names":["resolve","spawn","path"],"mappings":"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","file":"logs.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 type { Tool } from '@wrongstack/core';\r\nimport { safeResolve } from './_util.js';\r\n\r\ninterface LogsInput {\r\n service?: string;\r\n path?: string;\r\n lines?: number;\r\n stream?: boolean;\r\n filter?: string;\r\n since?: '1h' | '6h' | '24h' | 'all';\r\n cwd?: string;\r\n}\r\n\r\ninterface LogEntry {\r\n timestamp: string;\r\n level: string;\r\n message: string;\r\n source?: string;\r\n}\r\n\r\ninterface LogsOutput {\r\n source: string;\r\n entries: LogEntry[];\r\n total: number;\r\n truncated: boolean;\r\n stream_mode: boolean;\r\n}\r\n\r\nexport const logsTool: Tool<LogsInput, LogsOutput> = {\r\n name: 'logs',\r\n description:\r\n 'Stream or fetch logs from a service or file. Supports Docker, systemd, or plain log files.',\r\n usageHint:\r\n 'Set `service` for Docker/systemd, `path` for file. `lines` limits output. `stream` for tail -f behavior. `filter` regex filters lines.',\r\n permission: 'confirm',\r\n mutating: false,\r\n timeoutMs: 30_000,\r\n inputSchema: {\r\n type: 'object',\r\n properties: {\r\n service: {\r\n type: 'string',\r\n description: 'Service name for Docker or systemd journal',\r\n },\r\n path: {\r\n type: 'string',\r\n description: 'Path to log file (alternative to service)',\r\n },\r\n lines: {\r\n type: 'integer',\r\n description: 'Number of log lines to fetch (default: 100, 0 for all)',\r\n minimum: 0,\r\n maximum: 10000,\r\n },\r\n stream: {\r\n type: 'boolean',\r\n description: 'Stream logs continuously (like tail -f) (default: false)',\r\n },\r\n filter: {\r\n type: 'string',\r\n description: 'Regex pattern to filter log lines',\r\n },\r\n since: {\r\n type: 'string',\r\n enum: ['1h', '6h', '24h', 'all'],\r\n description: 'Only show logs since duration',\r\n },\r\n cwd: { type: 'string', description: 'Working directory (default: cwd)' },\r\n },\r\n },\r\n async execute(input, ctx, opts) {\r\n const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;\r\n const lines = input.lines ?? 100;\r\n const filterRe = input.filter ? new RegExp(input.filter, 'i') : null;\r\n\r\n if (input.service) {\r\n return await dockerLogs(input.service, lines, filterRe, cwd, opts.signal);\r\n }\r\n\r\n if (input.path) {\r\n return await fileLogs(safeResolve(input.path, ctx), lines, filterRe, input.stream ?? false);\r\n }\r\n\r\n return {\r\n source: 'none',\r\n entries: [],\r\n total: 0,\r\n truncated: false,\r\n stream_mode: false,\r\n };\r\n },\r\n};\r\n\r\nasync function dockerLogs(\r\n service: string,\r\n lines: number,\r\n filterRe: RegExp | null,\r\n cwd: string,\r\n signal: AbortSignal,\r\n since?: string,\r\n): Promise<LogsOutput> {\r\n const args = ['logs'];\r\n if (lines > 0) args.push('--tail', String(lines));\r\n if (since) {\r\n const sinceMap: Record<string, string> = { '1h': '1h', '6h': '6h', '24h': '24h' };\r\n args.push('--since', sinceMap[since] ?? '1h');\r\n }\r\n args.push('--timestamps', service);\r\n\r\n return new Promise((resolve) => {\r\n let stdout = '';\r\n let stderr = '';\r\n const MAX = 200_000;\r\n\r\n const child = spawn('docker', args, { cwd, signal, stdio: ['ignore', 'pipe', 'pipe'] });\r\n child.stdout?.on('data', (c) => { if (stdout.length < MAX) stdout += c.toString(); });\r\n child.stderr?.on('data', (c) => { if (stderr.length < MAX) stderr += c.toString(); });\r\n child.on('close', (code) => {\r\n const output = stdout + stderr;\r\n const entries = parseLogLines(output, filterRe);\r\n resolve({\r\n source: `docker:${service}`,\r\n entries,\r\n total: entries.length,\r\n truncated: output.length >= MAX,\r\n stream_mode: false,\r\n });\r\n });\r\n child.on('error', (e) => resolve({\r\n source: `docker:${service}`,\r\n entries: [],\r\n total: 0,\r\n truncated: false,\r\n stream_mode: false,\r\n }));\r\n });\r\n}\r\n\r\nasync function fileLogs(\r\n path: string,\r\n lines: number,\r\n filterRe: RegExp | null,\r\n stream: boolean,\r\n): Promise<LogsOutput> {\r\n const { createInterface } = await import('node:readline');\r\n const { createReadStream } = await import('node:fs');\r\n const entries: LogEntry[] = [];\r\n const allLines: string[] = [];\r\n\r\n const rl = createInterface({\r\n input: createReadStream(path),\r\n crlfDelay: Number.POSITIVE_INFINITY,\r\n });\r\n\r\n for await (const line of rl) {\r\n if (filterRe && !filterRe.test(line)) continue;\r\n allLines.push(line);\r\n }\r\n\r\n const sliced = lines > 0 ? allLines.slice(-lines) : allLines;\r\n for (const line of sliced) {\r\n const parsed = parseLine(line);\r\n if (parsed) entries.push(parsed);\r\n }\r\n\r\n return {\r\n source: path,\r\n entries,\r\n total: entries.length,\r\n truncated: allLines.length > lines && lines > 0,\r\n stream_mode: stream,\r\n };\r\n}\r\n\r\nfunction parseLogLines(output: string, filterRe: RegExp | null): LogEntry[] {\r\n const lines = output.split('\\n').filter(Boolean);\r\n const entries: LogEntry[] = [];\r\n\r\n for (const line of lines) {\r\n if (filterRe && !filterRe.test(line)) continue;\r\n const parsed = parseLine(line);\r\n if (parsed) entries.push(parsed);\r\n }\r\n\r\n return entries;\r\n}\r\n\r\nfunction parseLine(line: string): LogEntry | null {\r\n const tsRe = /^(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?Z?)\\s+(?:\\[?(\\w+)\\]?)\\s*(.*)/;\r\n const match = tsRe.exec(line);\r\n\r\n if (match) {\r\n return {\r\n timestamp: match[1] ?? '',\r\n level: match[2]?.toLowerCase() ?? 'info',\r\n message: match[3] ?? '',\r\n };\r\n }\r\n\r\n const levelRe = /(?:ERROR|WARN|INFO|DEBUG|TRACE)\\s+(.*)/i;\r\n const levelMatch = levelRe.exec(line);\r\n\r\n if (levelMatch) {\r\n return {\r\n timestamp: '',\r\n level: levelMatch[1]?.toLowerCase() ?? 'info',\r\n message: levelMatch[2] ?? line,\r\n };\r\n }\r\n\r\n return {\r\n timestamp: '',\r\n level: 'info',\r\n message: line,\r\n };\r\n}"]}
|
package/dist/memory.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { MemoryStore, Tool, MemoryScope } from '@wrongstack/core';
|
|
2
|
+
|
|
3
|
+
interface RememberInput {
|
|
4
|
+
text: string;
|
|
5
|
+
scope?: MemoryScope;
|
|
6
|
+
}
|
|
7
|
+
interface RememberOutput {
|
|
8
|
+
ok: true;
|
|
9
|
+
scope: MemoryScope;
|
|
10
|
+
}
|
|
11
|
+
interface ForgetInput {
|
|
12
|
+
query: string;
|
|
13
|
+
scope?: MemoryScope;
|
|
14
|
+
}
|
|
15
|
+
interface ForgetOutput {
|
|
16
|
+
removed: number;
|
|
17
|
+
scope: MemoryScope;
|
|
18
|
+
}
|
|
19
|
+
declare function rememberTool(memory: MemoryStore): Tool<RememberInput, RememberOutput>;
|
|
20
|
+
declare function forgetTool(memory: MemoryStore): Tool<ForgetInput, ForgetOutput>;
|
|
21
|
+
|
|
22
|
+
export { forgetTool, rememberTool };
|
package/dist/memory.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// src/memory.ts
|
|
2
|
+
function rememberTool(memory) {
|
|
3
|
+
return {
|
|
4
|
+
name: "remember",
|
|
5
|
+
description: "Persist a short note to project or user memory.",
|
|
6
|
+
usageHint: "Use sparingly. Only for facts that should outlive the session (project conventions, user preferences). Transient state belongs in `todo`. Scope defaults to project-memory.",
|
|
7
|
+
permission: "auto",
|
|
8
|
+
mutating: true,
|
|
9
|
+
timeoutMs: 2e3,
|
|
10
|
+
inputSchema: {
|
|
11
|
+
type: "object",
|
|
12
|
+
properties: {
|
|
13
|
+
text: { type: "string" },
|
|
14
|
+
scope: { type: "string", enum: ["project-agents", "project-memory", "user-memory"] }
|
|
15
|
+
},
|
|
16
|
+
required: ["text"]
|
|
17
|
+
},
|
|
18
|
+
async execute(input) {
|
|
19
|
+
if (!input?.text) throw new Error("remember: text is required");
|
|
20
|
+
const scope = input.scope ?? "project-memory";
|
|
21
|
+
await memory.remember(input.text, scope);
|
|
22
|
+
return { ok: true, scope };
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function forgetTool(memory) {
|
|
27
|
+
return {
|
|
28
|
+
name: "forget",
|
|
29
|
+
description: "Remove memory entries matching a substring (case-insensitive).",
|
|
30
|
+
usageHint: "Removes ALL matching bullet lines in the given scope. Use a unique substring.",
|
|
31
|
+
permission: "confirm",
|
|
32
|
+
mutating: true,
|
|
33
|
+
timeoutMs: 2e3,
|
|
34
|
+
inputSchema: {
|
|
35
|
+
type: "object",
|
|
36
|
+
properties: {
|
|
37
|
+
query: { type: "string" },
|
|
38
|
+
scope: { type: "string", enum: ["project-agents", "project-memory", "user-memory"] }
|
|
39
|
+
},
|
|
40
|
+
required: ["query"]
|
|
41
|
+
},
|
|
42
|
+
async execute(input) {
|
|
43
|
+
if (!input?.query) throw new Error("forget: query is required");
|
|
44
|
+
const scope = input.scope ?? "project-memory";
|
|
45
|
+
const removed = await memory.forget(input.query, scope);
|
|
46
|
+
return { removed, scope };
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export { forgetTool, rememberTool };
|
|
52
|
+
//# sourceMappingURL=memory.js.map
|
|
53
|
+
//# sourceMappingURL=memory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/memory.ts"],"names":[],"mappings":";AAsBO,SAAS,aAAa,MAAA,EAA0D;AACrF,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,UAAA;AAAA,IACN,WAAA,EAAa,iDAAA;AAAA,IACb,SAAA,EACE,6KAAA;AAAA,IACF,UAAA,EAAY,MAAA;AAAA,IACZ,QAAA,EAAU,IAAA;AAAA,IACV,SAAA,EAAW,GAAA;AAAA,IACX,WAAA,EAAa;AAAA,MACX,IAAA,EAAM,QAAA;AAAA,MACN,UAAA,EAAY;AAAA,QACV,IAAA,EAAM,EAAE,IAAA,EAAM,QAAA,EAAS;AAAA,QACvB,KAAA,EAAO,EAAE,IAAA,EAAM,QAAA,EAAU,MAAM,CAAC,gBAAA,EAAkB,gBAAA,EAAkB,aAAa,CAAA;AAAE,OACrF;AAAA,MACA,QAAA,EAAU,CAAC,MAAM;AAAA,KACnB;AAAA,IACA,MAAM,QAAQ,KAAA,EAAO;AACnB,MAAA,IAAI,CAAC,KAAA,EAAO,IAAA,EAAM,MAAM,IAAI,MAAM,4BAA4B,CAAA;AAC9D,MAAA,MAAM,KAAA,GAAQ,MAAM,KAAA,IAAS,gBAAA;AAC7B,MAAA,MAAM,MAAA,CAAO,QAAA,CAAS,KAAA,CAAM,IAAA,EAAM,KAAK,CAAA;AACvC,MAAA,OAAO,EAAE,EAAA,EAAI,IAAA,EAAM,KAAA,EAAM;AAAA,IAC3B;AAAA,GACF;AACF;AAEO,SAAS,WAAW,MAAA,EAAsD;AAC/E,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,QAAA;AAAA,IACN,WAAA,EAAa,gEAAA;AAAA,IACb,SAAA,EAAW,+EAAA;AAAA,IACX,UAAA,EAAY,SAAA;AAAA,IACZ,QAAA,EAAU,IAAA;AAAA,IACV,SAAA,EAAW,GAAA;AAAA,IACX,WAAA,EAAa;AAAA,MACX,IAAA,EAAM,QAAA;AAAA,MACN,UAAA,EAAY;AAAA,QACV,KAAA,EAAO,EAAE,IAAA,EAAM,QAAA,EAAS;AAAA,QACxB,KAAA,EAAO,EAAE,IAAA,EAAM,QAAA,EAAU,MAAM,CAAC,gBAAA,EAAkB,gBAAA,EAAkB,aAAa,CAAA;AAAE,OACrF;AAAA,MACA,QAAA,EAAU,CAAC,OAAO;AAAA,KACpB;AAAA,IACA,MAAM,QAAQ,KAAA,EAAO;AACnB,MAAA,IAAI,CAAC,KAAA,EAAO,KAAA,EAAO,MAAM,IAAI,MAAM,2BAA2B,CAAA;AAC9D,MAAA,MAAM,KAAA,GAAQ,MAAM,KAAA,IAAS,gBAAA;AAC7B,MAAA,MAAM,UAAU,MAAM,MAAA,CAAO,MAAA,CAAO,KAAA,CAAM,OAAO,KAAK,CAAA;AACtD,MAAA,OAAO,EAAE,SAAS,KAAA,EAAM;AAAA,IAC1B;AAAA,GACF;AACF","file":"memory.js","sourcesContent":["import type { Tool, MemoryScope, MemoryStore } from '@wrongstack/core';\n\ninterface RememberInput {\n text: string;\n scope?: MemoryScope;\n}\n\ninterface RememberOutput {\n ok: true;\n scope: MemoryScope;\n}\n\ninterface ForgetInput {\n query: string;\n scope?: MemoryScope;\n}\n\ninterface ForgetOutput {\n removed: number;\n scope: MemoryScope;\n}\n\nexport function rememberTool(memory: MemoryStore): Tool<RememberInput, RememberOutput> {\n return {\n name: 'remember',\n description: 'Persist a short note to project or user memory.',\n usageHint:\n 'Use sparingly. Only for facts that should outlive the session (project conventions, user preferences). Transient state belongs in `todo`. Scope defaults to project-memory.',\n permission: 'auto',\n mutating: true,\n timeoutMs: 2_000,\n inputSchema: {\n type: 'object',\n properties: {\n text: { type: 'string' },\n scope: { type: 'string', enum: ['project-agents', 'project-memory', 'user-memory'] },\n },\n required: ['text'],\n },\n async execute(input) {\n if (!input?.text) throw new Error('remember: text is required');\n const scope = input.scope ?? 'project-memory';\n await memory.remember(input.text, scope);\n return { ok: true, scope };\n },\n };\n}\n\nexport function forgetTool(memory: MemoryStore): Tool<ForgetInput, ForgetOutput> {\n return {\n name: 'forget',\n description: 'Remove memory entries matching a substring (case-insensitive).',\n usageHint: 'Removes ALL matching bullet lines in the given scope. Use a unique substring.',\n permission: 'confirm',\n mutating: true,\n timeoutMs: 2_000,\n inputSchema: {\n type: 'object',\n properties: {\n query: { type: 'string' },\n scope: { type: 'string', enum: ['project-agents', 'project-memory', 'user-memory'] },\n },\n required: ['query'],\n },\n async execute(input) {\n if (!input?.query) throw new Error('forget: query is required');\n const scope = input.scope ?? 'project-memory';\n const removed = await memory.forget(input.query, scope);\n return { removed, scope };\n },\n };\n}\n"]}
|
package/dist/mode.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ModeStore, Tool } from '@wrongstack/core';
|
|
2
|
+
|
|
3
|
+
interface ModeInput {
|
|
4
|
+
action: 'get' | 'list' | 'set' | 'clear';
|
|
5
|
+
mode?: string;
|
|
6
|
+
}
|
|
7
|
+
interface ModeOutput {
|
|
8
|
+
action: string;
|
|
9
|
+
currentMode?: string;
|
|
10
|
+
modes?: {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
description: string;
|
|
14
|
+
}[];
|
|
15
|
+
success: boolean;
|
|
16
|
+
message: string;
|
|
17
|
+
}
|
|
18
|
+
declare function createModeTool(modeStore: ModeStore): Tool<ModeInput, ModeOutput>;
|
|
19
|
+
|
|
20
|
+
export { createModeTool };
|
package/dist/mode.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// src/mode.ts
|
|
2
|
+
function createModeTool(modeStore) {
|
|
3
|
+
return {
|
|
4
|
+
name: "mode",
|
|
5
|
+
description: "Get, list, or switch the agent mode. Modes inject role-specific prompts into the system prompt.",
|
|
6
|
+
usageHint: "Set `action`: `get` (current mode), `list` (all modes), `set <modeId>` (switch), `clear` (reset to default).",
|
|
7
|
+
permission: "confirm",
|
|
8
|
+
mutating: true,
|
|
9
|
+
timeoutMs: 5e3,
|
|
10
|
+
inputSchema: {
|
|
11
|
+
type: "object",
|
|
12
|
+
properties: {
|
|
13
|
+
action: {
|
|
14
|
+
type: "string",
|
|
15
|
+
enum: ["get", "list", "set", "clear"],
|
|
16
|
+
description: "Action: get current, list all, set mode, or clear"
|
|
17
|
+
},
|
|
18
|
+
mode: {
|
|
19
|
+
type: "string",
|
|
20
|
+
description: "Mode ID to switch to (required for action=set)"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
required: ["action"]
|
|
24
|
+
},
|
|
25
|
+
async execute(input) {
|
|
26
|
+
switch (input.action) {
|
|
27
|
+
case "get": {
|
|
28
|
+
const mode = await modeStore.getActiveMode();
|
|
29
|
+
return {
|
|
30
|
+
action: "get",
|
|
31
|
+
currentMode: mode?.id,
|
|
32
|
+
success: true,
|
|
33
|
+
message: mode ? `Current mode: ${mode.name} \u2014 ${mode.description}` : "No mode set (using default)"
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
case "list": {
|
|
37
|
+
const modes = await modeStore.listModes();
|
|
38
|
+
const lines = modes.map((m) => ` ${m.id.padEnd(20)} ${m.name} \u2014 ${m.description}`).join("\n");
|
|
39
|
+
return {
|
|
40
|
+
action: "list",
|
|
41
|
+
modes: modes.map((m) => ({ id: m.id, name: m.name, description: m.description })),
|
|
42
|
+
success: true,
|
|
43
|
+
message: lines
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
case "set": {
|
|
47
|
+
if (!input.mode) {
|
|
48
|
+
return { action: "set", success: false, message: "mode is required for action=set" };
|
|
49
|
+
}
|
|
50
|
+
const mode = await modeStore.getMode(input.mode);
|
|
51
|
+
if (!mode) {
|
|
52
|
+
return { action: "set", success: false, message: `Mode "${input.mode}" not found` };
|
|
53
|
+
}
|
|
54
|
+
await modeStore.setActiveMode(input.mode);
|
|
55
|
+
return {
|
|
56
|
+
action: "set",
|
|
57
|
+
currentMode: mode.id,
|
|
58
|
+
success: true,
|
|
59
|
+
message: `Switched to mode: ${mode.name}
|
|
60
|
+
|
|
61
|
+
${mode.description}`
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
case "clear": {
|
|
65
|
+
await modeStore.setActiveMode(null);
|
|
66
|
+
return {
|
|
67
|
+
action: "clear",
|
|
68
|
+
success: true,
|
|
69
|
+
message: "Mode cleared \u2014 using default mode"
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
default:
|
|
73
|
+
return { action: input.action, success: false, message: `Unknown action "${input.action}"` };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export { createModeTool };
|
|
80
|
+
//# sourceMappingURL=mode.js.map
|
|
81
|
+
//# sourceMappingURL=mode.js.map
|
package/dist/mode.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/mode.ts"],"names":[],"mappings":"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","file":"mode.js","sourcesContent":["import type { Tool, ModeStore } from '@wrongstack/core';\n\ninterface ModeInput {\n action: 'get' | 'list' | 'set' | 'clear';\n mode?: string;\n}\n\ninterface ModeOutput {\n action: string;\n currentMode?: string;\n modes?: { id: string; name: string; description: string }[];\n success: boolean;\n message: string;\n}\n\nexport function createModeTool(modeStore: ModeStore): Tool<ModeInput, ModeOutput> {\n return {\n name: 'mode',\n description:\n 'Get, list, or switch the agent mode. Modes inject role-specific prompts into the system prompt.',\n usageHint:\n 'Set `action`: `get` (current mode), `list` (all modes), `set <modeId>` (switch), `clear` (reset to default).',\n permission: 'confirm',\n mutating: true,\n timeoutMs: 5_000,\n inputSchema: {\n type: 'object',\n properties: {\n action: {\n type: 'string',\n enum: ['get', 'list', 'set', 'clear'],\n description: 'Action: get current, list all, set mode, or clear',\n },\n mode: {\n type: 'string',\n description: 'Mode ID to switch to (required for action=set)',\n },\n },\n required: ['action'],\n },\n async execute(input) {\n switch (input.action) {\n case 'get': {\n const mode = await modeStore.getActiveMode();\n return {\n action: 'get',\n currentMode: mode?.id,\n success: true,\n message: mode ? `Current mode: ${mode.name} — ${mode.description}` : 'No mode set (using default)',\n };\n }\n case 'list': {\n const modes = await modeStore.listModes();\n const lines = modes.map((m) => ` ${m.id.padEnd(20)} ${m.name} — ${m.description}`).join('\\n');\n return {\n action: 'list',\n modes: modes.map((m) => ({ id: m.id, name: m.name, description: m.description })),\n success: true,\n message: lines,\n };\n }\n case 'set': {\n if (!input.mode) {\n return { action: 'set', success: false, message: 'mode is required for action=set' };\n }\n const mode = await modeStore.getMode(input.mode);\n if (!mode) {\n return { action: 'set', success: false, message: `Mode \"${input.mode}\" not found` };\n }\n await modeStore.setActiveMode(input.mode);\n return {\n action: 'set',\n currentMode: mode.id,\n success: true,\n message: `Switched to mode: ${mode.name}\\n\\n${mode.description}`,\n };\n }\n case 'clear': {\n await modeStore.setActiveMode(null);\n return {\n action: 'clear',\n success: true,\n message: 'Mode cleared — using default mode',\n };\n }\n default:\n return { action: input.action, success: false, message: `Unknown action \"${input.action}\"` };\n }\n },\n };\n}"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Tool } from '@wrongstack/core';
|
|
2
|
+
|
|
3
|
+
interface OutdatedInput {
|
|
4
|
+
cwd?: string;
|
|
5
|
+
format?: 'list' | 'table';
|
|
6
|
+
include_deprecated?: boolean;
|
|
7
|
+
check?: string | string[];
|
|
8
|
+
}
|
|
9
|
+
interface OutdatedPackage {
|
|
10
|
+
name: string;
|
|
11
|
+
current: string;
|
|
12
|
+
latest: string;
|
|
13
|
+
wanted: string;
|
|
14
|
+
type: string;
|
|
15
|
+
location: string;
|
|
16
|
+
}
|
|
17
|
+
interface OutdatedOutput {
|
|
18
|
+
exit_code: number;
|
|
19
|
+
packages: OutdatedPackage[];
|
|
20
|
+
total: number;
|
|
21
|
+
output: string;
|
|
22
|
+
truncated: boolean;
|
|
23
|
+
}
|
|
24
|
+
declare const outdatedTool: Tool<OutdatedInput, OutdatedOutput>;
|
|
25
|
+
|
|
26
|
+
export { outdatedTool };
|
package/dist/outdated.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
|
|
4
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
5
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
6
|
+
}) : x)(function(x) {
|
|
7
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
8
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
9
|
+
});
|
|
10
|
+
function resolvePath(input, ctx) {
|
|
11
|
+
return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.cwd, input);
|
|
12
|
+
}
|
|
13
|
+
function ensureInsideRoot(absPath, ctx) {
|
|
14
|
+
const root = path.resolve(ctx.projectRoot);
|
|
15
|
+
const target = path.resolve(absPath);
|
|
16
|
+
const rel = path.relative(root, target);
|
|
17
|
+
if (rel.startsWith("..") || path.isAbsolute(rel)) {
|
|
18
|
+
throw new Error(`Path "${absPath}" is outside project root "${root}"`);
|
|
19
|
+
}
|
|
20
|
+
return target;
|
|
21
|
+
}
|
|
22
|
+
function safeResolve(input, ctx) {
|
|
23
|
+
return ensureInsideRoot(resolvePath(input, ctx), ctx);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// src/outdated.ts
|
|
27
|
+
var outdatedTool = {
|
|
28
|
+
name: "outdated",
|
|
29
|
+
description: "Check for outdated npm packages. Shows current, wanted, and latest versions.",
|
|
30
|
+
usageHint: "Set `check` to filter specific packages. `format` as list or table. `include_deprecated` shows deprecated packages.",
|
|
31
|
+
permission: "auto",
|
|
32
|
+
mutating: false,
|
|
33
|
+
timeoutMs: 6e4,
|
|
34
|
+
inputSchema: {
|
|
35
|
+
type: "object",
|
|
36
|
+
properties: {
|
|
37
|
+
cwd: { type: "string", description: "Working directory (default: cwd)" },
|
|
38
|
+
format: {
|
|
39
|
+
type: "string",
|
|
40
|
+
enum: ["list", "table"],
|
|
41
|
+
description: "Output format (default: list)"
|
|
42
|
+
},
|
|
43
|
+
include_deprecated: {
|
|
44
|
+
type: "boolean",
|
|
45
|
+
description: "Include deprecated packages (default: false)"
|
|
46
|
+
},
|
|
47
|
+
check: {
|
|
48
|
+
type: "string",
|
|
49
|
+
description: "Specific package(s) to check (comma-separated)"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
async execute(input, ctx, opts) {
|
|
54
|
+
const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
|
|
55
|
+
const manager = await detectManager(cwd);
|
|
56
|
+
const args = ["outdated", "--json"];
|
|
57
|
+
if (input.format === "table") args.push("--table");
|
|
58
|
+
if (input.include_deprecated) args.push("--include", "deprecated");
|
|
59
|
+
return runOutdated(manager, args, cwd, opts.signal);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
async function detectManager(cwd) {
|
|
63
|
+
const { stat } = __require("fs/promises");
|
|
64
|
+
try {
|
|
65
|
+
await stat(`${cwd}/pnpm-lock.yaml`);
|
|
66
|
+
return "pnpm";
|
|
67
|
+
} catch {
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
await stat(`${cwd}/yarn.lock`);
|
|
71
|
+
return "yarn";
|
|
72
|
+
} catch {
|
|
73
|
+
}
|
|
74
|
+
return "npm";
|
|
75
|
+
}
|
|
76
|
+
function runOutdated(manager, args, cwd, signal) {
|
|
77
|
+
return new Promise((resolve2) => {
|
|
78
|
+
let stdout = "";
|
|
79
|
+
let stderr = "";
|
|
80
|
+
const MAX = 1e5;
|
|
81
|
+
const child = spawn(manager, args, { cwd, signal, stdio: ["ignore", "pipe", "pipe"] });
|
|
82
|
+
child.stdout?.on("data", (c) => {
|
|
83
|
+
if (stdout.length < MAX) stdout += c.toString();
|
|
84
|
+
});
|
|
85
|
+
child.stderr?.on("data", (c) => {
|
|
86
|
+
if (stderr.length < MAX) stderr += c.toString();
|
|
87
|
+
});
|
|
88
|
+
child.on("close", (code) => {
|
|
89
|
+
const result = parseOutdatedOutput(stdout, code ?? 0);
|
|
90
|
+
resolve2(result);
|
|
91
|
+
});
|
|
92
|
+
child.on("error", (e) => resolve2({
|
|
93
|
+
exit_code: 1,
|
|
94
|
+
packages: [],
|
|
95
|
+
total: 0,
|
|
96
|
+
output: e.message,
|
|
97
|
+
truncated: false
|
|
98
|
+
}));
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function parseOutdatedOutput(json, exitCode) {
|
|
102
|
+
const packages = [];
|
|
103
|
+
if (!json) {
|
|
104
|
+
return {
|
|
105
|
+
exit_code: exitCode,
|
|
106
|
+
packages: [],
|
|
107
|
+
total: 0,
|
|
108
|
+
output: exitCode === 0 ? "All packages up to date" : "Could not check outdated packages",
|
|
109
|
+
truncated: false
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
const data = JSON.parse(json);
|
|
114
|
+
for (const name of Object.keys(data)) {
|
|
115
|
+
const info = data[name];
|
|
116
|
+
packages.push({
|
|
117
|
+
name,
|
|
118
|
+
current: info.current ?? "unknown",
|
|
119
|
+
latest: info.latest ?? "unknown",
|
|
120
|
+
wanted: info.wanted ?? "unknown",
|
|
121
|
+
type: info.type ?? "unknown",
|
|
122
|
+
location: info.location ?? name
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
} catch {
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
exit_code: exitCode,
|
|
129
|
+
packages,
|
|
130
|
+
total: packages.length,
|
|
131
|
+
output: json,
|
|
132
|
+
truncated: json.length >= 1e5
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export { outdatedTool };
|
|
137
|
+
//# sourceMappingURL=outdated.js.map
|
|
138
|
+
//# sourceMappingURL=outdated.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/_util.ts","../src/outdated.ts"],"names":["resolve","spawn"],"mappings":"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","file":"outdated.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';\nimport type { Tool } from '@wrongstack/core';\nimport { safeResolve } from './_util.js';\n\ninterface OutdatedInput {\n cwd?: string;\n format?: 'list' | 'table';\n include_deprecated?: boolean;\n check?: string | string[];\n}\n\ninterface OutdatedPackage {\n name: string;\n current: string;\n latest: string;\n wanted: string;\n type: string;\n location: string;\n}\n\ninterface OutdatedOutput {\n exit_code: number;\n packages: OutdatedPackage[];\n total: number;\n output: string;\n truncated: boolean;\n}\n\nexport const outdatedTool: Tool<OutdatedInput, OutdatedOutput> = {\n name: 'outdated',\n description:\n 'Check for outdated npm packages. Shows current, wanted, and latest versions.',\n usageHint:\n 'Set `check` to filter specific packages. `format` as list or table. `include_deprecated` shows deprecated packages.',\n permission: 'auto',\n mutating: false,\n timeoutMs: 60_000,\n inputSchema: {\n type: 'object',\n properties: {\n cwd: { type: 'string', description: 'Working directory (default: cwd)' },\n format: {\n type: 'string',\n enum: ['list', 'table'],\n description: 'Output format (default: list)',\n },\n include_deprecated: {\n type: 'boolean',\n description: 'Include deprecated packages (default: false)',\n },\n check: {\n type: 'string',\n description: 'Specific package(s) to check (comma-separated)',\n },\n },\n },\n async execute(input, ctx, opts) {\n const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;\n const manager = await detectManager(cwd);\n\n const args: string[] = ['outdated', '--json'];\n if (input.format === 'table') args.push('--table');\n if (input.include_deprecated) args.push('--include', 'deprecated');\n\n return runOutdated(manager, args, cwd, opts.signal);\n },\n};\n\nasync function detectManager(cwd: string): Promise<string> {\n const { stat } = require('node:fs/promises');\n try { await stat(`${cwd}/pnpm-lock.yaml`); return 'pnpm'; } catch { /* */ }\n try { await stat(`${cwd}/yarn.lock`); return 'yarn'; } catch { /* */ }\n return 'npm';\n}\n\nfunction runOutdated(\n manager: string,\n args: string[],\n cwd: string,\n signal: AbortSignal,\n): Promise<OutdatedOutput> {\n return new Promise((resolve) => {\n let stdout = '';\n let stderr = '';\n const MAX = 100_000;\n\n const child = spawn(manager, args, { cwd, signal, stdio: ['ignore', 'pipe', 'pipe'] });\n child.stdout?.on('data', (c) => { if (stdout.length < MAX) stdout += c.toString(); });\n child.stderr?.on('data', (c) => { if (stderr.length < MAX) stderr += c.toString(); });\n child.on('close', (code) => {\n const result = parseOutdatedOutput(stdout, code ?? 0);\n resolve(result);\n });\n child.on('error', (e) => resolve({\n exit_code: 1,\n packages: [],\n total: 0,\n output: e.message,\n truncated: false,\n }));\n });\n}\n\nfunction parseOutdatedOutput(json: string, exitCode: number): OutdatedOutput {\n const packages: OutdatedPackage[] = [];\n\n if (!json) {\n return {\n exit_code: exitCode,\n packages: [],\n total: 0,\n output: exitCode === 0 ? 'All packages up to date' : 'Could not check outdated packages',\n truncated: false,\n };\n }\n\n try {\n const data = JSON.parse(json);\n for (const name of Object.keys(data)) {\n const info = data[name];\n packages.push({\n name,\n current: info.current ?? 'unknown',\n latest: info.latest ?? 'unknown',\n wanted: info.wanted ?? 'unknown',\n type: info.type ?? 'unknown',\n location: info.location ?? name,\n });\n }\n } catch {\n // JSON parse failed, return raw output\n }\n\n return {\n exit_code: exitCode,\n packages,\n total: packages.length,\n output: json,\n truncated: json.length >= 100_000,\n };\n}"]}
|
package/dist/patch.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Tool } from '@wrongstack/core';
|
|
2
|
+
|
|
3
|
+
interface PatchInput {
|
|
4
|
+
patch: string;
|
|
5
|
+
directory?: string;
|
|
6
|
+
strip?: number;
|
|
7
|
+
dry_run?: boolean;
|
|
8
|
+
}
|
|
9
|
+
interface PatchOutput {
|
|
10
|
+
applied: number;
|
|
11
|
+
rejected: number;
|
|
12
|
+
files: string[];
|
|
13
|
+
dry_run: boolean;
|
|
14
|
+
message: string;
|
|
15
|
+
}
|
|
16
|
+
declare const patchTool: Tool<PatchInput, PatchOutput>;
|
|
17
|
+
|
|
18
|
+
export { patchTool };
|