@thejeetsingh/kalcode 2.0.0

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.
@@ -0,0 +1,128 @@
1
+ import { API_URL } from "../constants.js";
2
+
3
+ const PORT = parseInt(process.env.PORT || "8787", 10);
4
+ const HOST = process.env.HOST || "0.0.0.0";
5
+ const PROXY_TOKEN = process.env.KALCODE_PROXY_TOKEN || "";
6
+ const NVIDIA_NIM_KEY = process.env.NVIDIA_NIM_KEY || "";
7
+ const RATE_LIMIT_PER_MIN = Math.max(
8
+ 1,
9
+ parseInt(process.env.KALCODE_PROXY_RATE_LIMIT_PER_MIN || "120", 10),
10
+ );
11
+
12
+ const requestTimestampsByClient = new Map<string, number[]>();
13
+
14
+ function json(status: number, payload: Record<string, unknown>): Response {
15
+ return new Response(JSON.stringify(payload), {
16
+ status,
17
+ headers: {
18
+ "Content-Type": "application/json",
19
+ },
20
+ });
21
+ }
22
+
23
+ function getClientId(req: Request): string {
24
+ const forwardedFor = req.headers.get("x-forwarded-for");
25
+ if (forwardedFor) return forwardedFor.split(",")[0]!.trim();
26
+ return "unknown";
27
+ }
28
+
29
+ function isRateLimited(clientId: string): boolean {
30
+ const now = Date.now();
31
+ const windowStart = now - 60_000;
32
+ const timestamps = requestTimestampsByClient.get(clientId) || [];
33
+ const filtered = timestamps.filter((ts) => ts >= windowStart);
34
+
35
+ if (filtered.length >= RATE_LIMIT_PER_MIN) {
36
+ requestTimestampsByClient.set(clientId, filtered);
37
+ return true;
38
+ }
39
+
40
+ filtered.push(now);
41
+ requestTimestampsByClient.set(clientId, filtered);
42
+ return false;
43
+ }
44
+
45
+ function getBearerToken(req: Request): string {
46
+ const auth = req.headers.get("authorization") || "";
47
+ const m = auth.match(/^Bearer\s+(.+)$/i);
48
+ return m?.[1]?.trim() || "";
49
+ }
50
+
51
+ async function proxyCompletions(req: Request): Promise<Response> {
52
+ if (!NVIDIA_NIM_KEY) {
53
+ return json(500, {
54
+ error: {
55
+ message: "Server missing NVIDIA_NIM_KEY.",
56
+ },
57
+ });
58
+ }
59
+
60
+ if (PROXY_TOKEN) {
61
+ const incomingToken = getBearerToken(req);
62
+ if (!incomingToken || incomingToken !== PROXY_TOKEN) {
63
+ return json(401, {
64
+ error: {
65
+ message: "Unauthorized. Invalid proxy token.",
66
+ },
67
+ });
68
+ }
69
+ }
70
+
71
+ const clientId = getClientId(req);
72
+ if (isRateLimited(clientId)) {
73
+ return json(429, {
74
+ error: {
75
+ message: "Rate limit exceeded. Please retry shortly.",
76
+ },
77
+ });
78
+ }
79
+
80
+ const body = await req.text();
81
+ const upstream = await fetch(API_URL, {
82
+ method: "POST",
83
+ headers: {
84
+ "Content-Type": "application/json",
85
+ Authorization: `Bearer ${NVIDIA_NIM_KEY}`,
86
+ },
87
+ body,
88
+ });
89
+
90
+ const headers = new Headers();
91
+ const contentType = upstream.headers.get("content-type");
92
+ if (contentType) headers.set("Content-Type", contentType);
93
+ const cacheControl = upstream.headers.get("cache-control");
94
+ if (cacheControl) headers.set("Cache-Control", cacheControl);
95
+
96
+ return new Response(upstream.body, {
97
+ status: upstream.status,
98
+ headers,
99
+ });
100
+ }
101
+
102
+ const server = Bun.serve({
103
+ hostname: HOST,
104
+ port: PORT,
105
+ async fetch(req: Request): Promise<Response> {
106
+ const url = new URL(req.url);
107
+
108
+ if (req.method === "GET" && url.pathname === "/health") {
109
+ return json(200, {
110
+ ok: true,
111
+ });
112
+ }
113
+
114
+ if (req.method === "POST" && url.pathname === "/v1/chat/completions") {
115
+ return proxyCompletions(req);
116
+ }
117
+
118
+ return json(404, {
119
+ error: {
120
+ message: "Not found.",
121
+ },
122
+ });
123
+ },
124
+ });
125
+
126
+ console.log(
127
+ `kalcode proxy listening on http://${server.hostname}:${server.port} (rate ${RATE_LIMIT_PER_MIN}/min/client)`,
128
+ );
@@ -0,0 +1,97 @@
1
+ import { readFileSync, writeFileSync, existsSync } from "fs";
2
+ import { resolve } from "path";
3
+ import type { ToolHandler } from "../types.js";
4
+
5
+ export const editFileTool: ToolHandler = {
6
+ definition: {
7
+ type: "function",
8
+ function: {
9
+ name: "editFile",
10
+ description:
11
+ "Make surgical edits to a file using search/replace pairs. Each edit finds an exact text match and replaces it. Read the file first to get the exact text to search for.",
12
+ parameters: {
13
+ type: "object",
14
+ properties: {
15
+ filePath: {
16
+ type: "string",
17
+ description: "Path to the file to edit",
18
+ },
19
+ edits: {
20
+ type: "array",
21
+ description: "Array of search/replace edit operations",
22
+ items: {
23
+ type: "object",
24
+ properties: {
25
+ search: {
26
+ type: "string",
27
+ description: "Exact text to search for in the file",
28
+ },
29
+ replace: {
30
+ type: "string",
31
+ description: "Text to replace the search text with",
32
+ },
33
+ },
34
+ required: ["search", "replace"],
35
+ },
36
+ },
37
+ },
38
+ required: ["filePath", "edits"],
39
+ },
40
+ },
41
+ },
42
+
43
+ async execute(args) {
44
+ const filePath = resolve(String(args.filePath));
45
+ const edits = args.edits as Array<{ search: string; replace: string }>;
46
+
47
+ if (!existsSync(filePath)) {
48
+ return `Error: File not found: ${filePath}`;
49
+ }
50
+
51
+ if (!Array.isArray(edits) || edits.length === 0) {
52
+ return "Error: edits must be a non-empty array of {search, replace} pairs";
53
+ }
54
+
55
+ try {
56
+ let content = readFileSync(filePath, "utf-8");
57
+ const results: string[] = [];
58
+
59
+ for (let i = 0; i < edits.length; i++) {
60
+ const edit = edits[i]!;
61
+ const search = String(edit.search);
62
+ const replace = String(edit.replace);
63
+
64
+ let idx = content.indexOf(search);
65
+ // Try trimming trailing whitespace per line if exact match fails
66
+ if (idx === -1) {
67
+ const searchNorm = search.split("\n").map(l => l.trimEnd()).join("\n");
68
+ const contentNorm = content.split("\n").map(l => l.trimEnd()).join("\n");
69
+ idx = contentNorm.indexOf(searchNorm);
70
+ if (idx !== -1) {
71
+ // Find the real position accounting for trailing whitespace diffs
72
+ const before = contentNorm.slice(0, idx);
73
+ const linesBefore = before.split("\n").length - 1;
74
+ const linesInSearch = searchNorm.split("\n").length;
75
+ const contentLines = content.split("\n");
76
+ const realBefore = contentLines.slice(0, linesBefore).join("\n") + (linesBefore > 0 ? "\n" : "");
77
+ const realMatch = contentLines.slice(linesBefore, linesBefore + linesInSearch).join("\n");
78
+ content = realBefore + replace + content.slice(realBefore.length + realMatch.length);
79
+ results.push(`Edit ${i + 1}: OK (fuzzy whitespace match)`);
80
+ continue;
81
+ }
82
+ results.push(`Edit ${i + 1}: FAILED - search text not found`);
83
+ continue;
84
+ }
85
+
86
+ // Only replace first occurrence
87
+ content = content.slice(0, idx) + replace + content.slice(idx + search.length);
88
+ results.push(`Edit ${i + 1}: OK`);
89
+ }
90
+
91
+ writeFileSync(filePath, content);
92
+ return `Edited ${filePath}:\n${results.join("\n")}`;
93
+ } catch (err: unknown) {
94
+ return `Error editing file: ${err instanceof Error ? err.message : String(err)}`;
95
+ }
96
+ },
97
+ };
@@ -0,0 +1,59 @@
1
+ import { resolve } from "path";
2
+ import type { ToolHandler } from "../types.js";
3
+ import { MAX_GLOB_RESULTS } from "../constants.js";
4
+
5
+ export const globTool: ToolHandler = {
6
+ definition: {
7
+ type: "function",
8
+ function: {
9
+ name: "glob",
10
+ description:
11
+ "Find files matching a glob pattern. Returns matching file paths sorted by modification time.",
12
+ parameters: {
13
+ type: "object",
14
+ properties: {
15
+ pattern: {
16
+ type: "string",
17
+ description: "Glob pattern (e.g., '**/*.ts', 'src/**/*.json')",
18
+ },
19
+ path: {
20
+ type: "string",
21
+ description: "Base directory to search from. Default: current directory",
22
+ },
23
+ },
24
+ required: ["pattern"],
25
+ },
26
+ },
27
+ },
28
+
29
+ async execute(args) {
30
+ const pattern = String(args.pattern);
31
+ const basePath = resolve(String(args.path || "."));
32
+
33
+ try {
34
+ const glob = new Bun.Glob(pattern);
35
+ const matches: string[] = [];
36
+
37
+ for await (const entry of glob.scan({ cwd: basePath, dot: false })) {
38
+ // Skip common noise directories
39
+ if (entry.startsWith("node_modules/") || entry.startsWith(".git/")) continue;
40
+ matches.push(entry);
41
+ if (matches.length >= MAX_GLOB_RESULTS) break;
42
+ }
43
+
44
+ if (matches.length === 0) {
45
+ return `No files matching pattern: ${pattern} in ${basePath}`;
46
+ }
47
+
48
+ matches.sort();
49
+ let result = `Found ${matches.length} file(s) matching "${pattern}":\n`;
50
+ result += matches.join("\n");
51
+ if (matches.length >= MAX_GLOB_RESULTS) {
52
+ result += `\n... (results limited to ${MAX_GLOB_RESULTS})`;
53
+ }
54
+ return result;
55
+ } catch (err: unknown) {
56
+ return `Error searching for files: ${err instanceof Error ? err.message : String(err)}`;
57
+ }
58
+ },
59
+ };
@@ -0,0 +1,96 @@
1
+ import type { ToolHandler } from "../types.js";
2
+ import { MAX_GREP_RESULTS } from "../constants.js";
3
+
4
+ export const grepTool: ToolHandler = {
5
+ definition: {
6
+ type: "function",
7
+ function: {
8
+ name: "grep",
9
+ description:
10
+ "Search file contents for a pattern. Uses ripgrep if available, otherwise falls back to grep. Returns matching lines with file paths and line numbers.",
11
+ parameters: {
12
+ type: "object",
13
+ properties: {
14
+ pattern: {
15
+ type: "string",
16
+ description: "Search pattern (regex supported)",
17
+ },
18
+ path: {
19
+ type: "string",
20
+ description: "Directory or file to search in. Default: current directory",
21
+ },
22
+ include: {
23
+ type: "string",
24
+ description: "File glob pattern to include (e.g., '*.ts')",
25
+ },
26
+ maxResults: {
27
+ type: "number",
28
+ description: `Maximum number of results. Default: ${MAX_GREP_RESULTS}`,
29
+ },
30
+ },
31
+ required: ["pattern"],
32
+ },
33
+ },
34
+ },
35
+
36
+ async execute(args) {
37
+ const pattern = String(args.pattern);
38
+ const searchPath = String(args.path || ".");
39
+ const include = args.include ? String(args.include) : null;
40
+ const maxResults = Number(args.maxResults) || MAX_GREP_RESULTS;
41
+
42
+ // Try ripgrep first, fall back to grep
43
+ const hasRg = await checkCommand("rg");
44
+
45
+ let cmd: string;
46
+ if (hasRg) {
47
+ cmd = `rg --line-number --max-count ${maxResults} --no-heading --glob '!node_modules' --glob '!.git'`;
48
+ if (include) cmd += ` --glob '${include}'`;
49
+ cmd += ` -- ${escapeShellArg(pattern)} ${escapeShellArg(searchPath)}`;
50
+ } else {
51
+ cmd = `grep -rn --max-count=${maxResults} --exclude-dir=node_modules --exclude-dir=.git`;
52
+ if (include) cmd += ` --include='${include}'`;
53
+ cmd += ` -- ${escapeShellArg(pattern)} ${escapeShellArg(searchPath)}`;
54
+ }
55
+
56
+ try {
57
+ const proc = Bun.spawn(["sh", "-c", cmd], {
58
+ stdout: "pipe",
59
+ stderr: "pipe",
60
+ });
61
+
62
+ const stdout = await new Response(proc.stdout).text();
63
+ const stderr = await new Response(proc.stderr).text();
64
+ await proc.exited;
65
+
66
+ if (!stdout.trim()) {
67
+ return `No matches found for pattern: ${pattern}`;
68
+ }
69
+
70
+ // Limit results
71
+ const lines = stdout.trim().split("\n");
72
+ const limited = lines.slice(0, maxResults);
73
+ let result = limited.join("\n");
74
+ if (lines.length > maxResults) {
75
+ result += `\n... (${lines.length - maxResults} more matches)`;
76
+ }
77
+ return result;
78
+ } catch (err: unknown) {
79
+ return `Error searching: ${err instanceof Error ? err.message : String(err)}`;
80
+ }
81
+ },
82
+ };
83
+
84
+ async function checkCommand(cmd: string): Promise<boolean> {
85
+ try {
86
+ const proc = Bun.spawn(["which", cmd], { stdout: "pipe", stderr: "pipe" });
87
+ await proc.exited;
88
+ return proc.exitCode === 0;
89
+ } catch {
90
+ return false;
91
+ }
92
+ }
93
+
94
+ function escapeShellArg(arg: string): string {
95
+ return `'${arg.replace(/'/g, "'\\''")}'`;
96
+ }
@@ -0,0 +1,101 @@
1
+ import { readdirSync, statSync, existsSync } from "fs";
2
+ import { resolve, join } from "path";
3
+ import type { ToolHandler } from "../types.js";
4
+ import { MAX_DIR_ENTRIES, MAX_DIR_DEPTH } from "../constants.js";
5
+
6
+ export const listDirectoryTool: ToolHandler = {
7
+ definition: {
8
+ type: "function",
9
+ function: {
10
+ name: "listDirectory",
11
+ description:
12
+ "List the contents of a directory. Shows file names, types, and sizes.",
13
+ parameters: {
14
+ type: "object",
15
+ properties: {
16
+ dirPath: {
17
+ type: "string",
18
+ description: "Path to the directory to list. Default: current directory",
19
+ },
20
+ recursive: {
21
+ type: "boolean",
22
+ description: "List recursively (max depth 3). Default: false",
23
+ },
24
+ },
25
+ },
26
+ },
27
+ },
28
+
29
+ async execute(args) {
30
+ const dirPath = resolve(String(args.dirPath || "."));
31
+ const recursive = Boolean(args.recursive);
32
+
33
+ if (!existsSync(dirPath)) {
34
+ return `Error: Directory not found: ${dirPath}`;
35
+ }
36
+
37
+ try {
38
+ const entries: string[] = [];
39
+ listDir(dirPath, "", recursive ? MAX_DIR_DEPTH : 0, entries);
40
+
41
+ if (entries.length === 0) {
42
+ return `Directory is empty: ${dirPath}`;
43
+ }
44
+
45
+ let result = `Contents of ${dirPath} (${entries.length} entries):\n`;
46
+ result += entries.join("\n");
47
+ if (entries.length >= MAX_DIR_ENTRIES) {
48
+ result += `\n... (limited to ${MAX_DIR_ENTRIES} entries)`;
49
+ }
50
+ return result;
51
+ } catch (err: unknown) {
52
+ return `Error listing directory: ${err instanceof Error ? err.message : String(err)}`;
53
+ }
54
+ },
55
+ };
56
+
57
+ function listDir(
58
+ basePath: string,
59
+ prefix: string,
60
+ depth: number,
61
+ entries: string[]
62
+ ): void {
63
+ if (entries.length >= MAX_DIR_ENTRIES) return;
64
+
65
+ let items: string[];
66
+ try {
67
+ items = readdirSync(join(basePath, prefix));
68
+ } catch {
69
+ return;
70
+ }
71
+
72
+ items.sort();
73
+
74
+ for (const item of items) {
75
+ if (entries.length >= MAX_DIR_ENTRIES) return;
76
+
77
+ const relPath = prefix ? `${prefix}/${item}` : item;
78
+ const fullPath = join(basePath, relPath);
79
+
80
+ try {
81
+ const stat = statSync(fullPath);
82
+ if (stat.isDirectory()) {
83
+ entries.push(`${relPath}/`);
84
+ if (depth > 0) {
85
+ listDir(basePath, relPath, depth - 1, entries);
86
+ }
87
+ } else {
88
+ const size = formatSize(stat.size);
89
+ entries.push(`${relPath} (${size})`);
90
+ }
91
+ } catch {
92
+ entries.push(`${relPath} (unreadable)`);
93
+ }
94
+ }
95
+ }
96
+
97
+ function formatSize(bytes: number): string {
98
+ if (bytes < 1024) return `${bytes}B`;
99
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
100
+ return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
101
+ }
@@ -0,0 +1,71 @@
1
+ import { readFileSync, statSync, existsSync } from "fs";
2
+ import { resolve } from "path";
3
+ import type { ToolHandler } from "../types.js";
4
+ import { MAX_FILE_SIZE, DEFAULT_LINE_LIMIT } from "../constants.js";
5
+
6
+ export const readFileTool: ToolHandler = {
7
+ definition: {
8
+ type: "function",
9
+ function: {
10
+ name: "readFile",
11
+ description:
12
+ "Read the contents of a file with line numbers. Returns line-numbered output. Use offset and limit to read specific portions of large files.",
13
+ parameters: {
14
+ type: "object",
15
+ properties: {
16
+ filePath: {
17
+ type: "string",
18
+ description: "Path to the file to read (absolute or relative to cwd)",
19
+ },
20
+ offset: {
21
+ type: "number",
22
+ description: "Line number to start reading from (1-based). Default: 1",
23
+ },
24
+ limit: {
25
+ type: "number",
26
+ description: `Maximum number of lines to read. Default: ${DEFAULT_LINE_LIMIT}`,
27
+ },
28
+ },
29
+ required: ["filePath"],
30
+ },
31
+ },
32
+ },
33
+
34
+ async execute(args) {
35
+ const filePath = resolve(String(args.filePath));
36
+ const offset = Math.max(1, Number(args.offset) || 1);
37
+ const limit = Number(args.limit) || DEFAULT_LINE_LIMIT;
38
+
39
+ if (!existsSync(filePath)) {
40
+ return `Error: File not found: ${filePath}`;
41
+ }
42
+
43
+ try {
44
+ const stat = statSync(filePath);
45
+ if (stat.isDirectory()) {
46
+ return `Error: Path is a directory, not a file: ${filePath}`;
47
+ }
48
+ if (stat.size > MAX_FILE_SIZE) {
49
+ return `Error: File too large (${(stat.size / 1024 / 1024).toFixed(1)}MB). Use offset/limit to read portions.`;
50
+ }
51
+
52
+ const content = readFileSync(filePath, "utf-8");
53
+ const lines = content.split("\n");
54
+ const startIdx = offset - 1;
55
+ const endIdx = Math.min(startIdx + limit, lines.length);
56
+ const slice = lines.slice(startIdx, endIdx);
57
+
58
+ const numbered = slice
59
+ .map((line, i) => {
60
+ const lineNum = String(startIdx + i + 1).padStart(4, " ");
61
+ return `${lineNum} | ${line}`;
62
+ })
63
+ .join("\n");
64
+
65
+ const header = `File: ${filePath} (${lines.length} lines total, showing ${offset}-${endIdx})`;
66
+ return `${header}\n${numbered}`;
67
+ } catch (err: unknown) {
68
+ return `Error reading file: ${err instanceof Error ? err.message : String(err)}`;
69
+ }
70
+ },
71
+ };
@@ -0,0 +1,41 @@
1
+ import type { ToolDefinition, ToolHandler } from "../types.js";
2
+ import { readFileTool } from "./read-file.js";
3
+ import { writeFileTool } from "./write-file.js";
4
+ import { editFileTool } from "./edit-file.js";
5
+ import { runCommandTool } from "./run-command.js";
6
+ import { grepTool } from "./grep.js";
7
+ import { globTool } from "./glob-tool.js";
8
+ import { listDirectoryTool } from "./list-directory.js";
9
+
10
+ const tools: Map<string, ToolHandler> = new Map();
11
+
12
+ function register(handler: ToolHandler): void {
13
+ tools.set(handler.definition.function.name, handler);
14
+ }
15
+
16
+ register(readFileTool);
17
+ register(writeFileTool);
18
+ register(editFileTool);
19
+ register(runCommandTool);
20
+ register(grepTool);
21
+ register(globTool);
22
+ register(listDirectoryTool);
23
+
24
+ export function getToolHandler(name: string): ToolHandler | undefined {
25
+ return tools.get(name);
26
+ }
27
+
28
+ export function getAllDefinitions(): ToolDefinition[] {
29
+ return Array.from(tools.values()).map((t) => t.definition);
30
+ }
31
+
32
+ export function executeToolCall(
33
+ name: string,
34
+ args: Record<string, unknown>
35
+ ): Promise<string> {
36
+ const handler = tools.get(name);
37
+ if (!handler) {
38
+ return Promise.resolve(`Error: Unknown tool "${name}"`);
39
+ }
40
+ return handler.execute(args);
41
+ }
@@ -0,0 +1,99 @@
1
+ import { resolve } from "path";
2
+ import type { ToolHandler } from "../types.js";
3
+ import { DEFAULT_COMMAND_TIMEOUT, MAX_COMMAND_TIMEOUT, MAX_OUTPUT_BYTES } from "../constants.js";
4
+
5
+ export const runCommandTool: ToolHandler = {
6
+ definition: {
7
+ type: "function",
8
+ function: {
9
+ name: "runCommand",
10
+ description:
11
+ "Execute a shell command and return its output. Default timeout is 30s and hard-capped at 45s. Output is truncated at 50KB.",
12
+ parameters: {
13
+ type: "object",
14
+ properties: {
15
+ command: {
16
+ type: "string",
17
+ description: "The shell command to execute",
18
+ },
19
+ cwd: {
20
+ type: "string",
21
+ description: "Working directory for the command. Default: current directory",
22
+ },
23
+ timeout: {
24
+ type: "number",
25
+ description: "Timeout in milliseconds. Default: 30000, max: 45000",
26
+ },
27
+ },
28
+ required: ["command"],
29
+ },
30
+ },
31
+ },
32
+
33
+ async execute(args) {
34
+ const command = String(args.command);
35
+ const cwd = args.cwd ? resolve(String(args.cwd)) : process.cwd();
36
+ const requestedTimeout = Number(args.timeout) || DEFAULT_COMMAND_TIMEOUT;
37
+ const timeout = Math.min(Math.max(1_000, requestedTimeout), MAX_COMMAND_TIMEOUT);
38
+
39
+ try {
40
+ const proc = Bun.spawn(["sh", "-c", command], {
41
+ stdout: "pipe",
42
+ stderr: "pipe",
43
+ cwd,
44
+ env: { ...process.env },
45
+ });
46
+
47
+ let timedOut = false;
48
+ const timer = setTimeout(() => {
49
+ timedOut = true;
50
+ try {
51
+ proc.kill(9);
52
+ } catch {
53
+ // Process may already have exited.
54
+ }
55
+ }, timeout);
56
+
57
+ const exitCode = await Promise.race([
58
+ proc.exited,
59
+ new Promise<number>((resolveTimeout) => {
60
+ setTimeout(() => resolveTimeout(-1), timeout + 250);
61
+ }),
62
+ ]);
63
+
64
+ clearTimeout(timer);
65
+
66
+ if (timedOut || exitCode === -1) {
67
+ return [
68
+ `Exit code: 124`,
69
+ `STDERR:`,
70
+ `Command timed out after ${Math.round(timeout / 1000)}s.`,
71
+ `Tip: use a shorter command, reduce scope, or run background/dev servers manually outside kalcode.`,
72
+ ].join("\n\n");
73
+ }
74
+
75
+ const [stdoutBuf, stderrBuf] = await Promise.all([
76
+ new Response(proc.stdout).arrayBuffer(),
77
+ new Response(proc.stderr).arrayBuffer(),
78
+ ]);
79
+
80
+ let stdout = new TextDecoder().decode(stdoutBuf);
81
+ let stderr = new TextDecoder().decode(stderrBuf);
82
+
83
+ if (stdout.length > MAX_OUTPUT_BYTES) {
84
+ stdout = stdout.slice(0, MAX_OUTPUT_BYTES) + "\n... (output truncated)";
85
+ }
86
+ if (stderr.length > MAX_OUTPUT_BYTES) {
87
+ stderr = stderr.slice(0, MAX_OUTPUT_BYTES) + "\n... (output truncated)";
88
+ }
89
+
90
+ let result = `Exit code: ${exitCode}`;
91
+ if (stdout.trim()) result += `\n\nSTDOUT:\n${stdout.trim()}`;
92
+ if (stderr.trim()) result += `\n\nSTDERR:\n${stderr.trim()}`;
93
+
94
+ return result;
95
+ } catch (err: unknown) {
96
+ return `Error running command: ${err instanceof Error ? err.message : String(err)}`;
97
+ }
98
+ },
99
+ };