@cdoing/core 0.1.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.
- package/dist/agents/coordinator.d.ts +114 -0
- package/dist/agents/coordinator.d.ts.map +1 -0
- package/dist/agents/coordinator.js +158 -0
- package/dist/agents/coordinator.js.map +1 -0
- package/dist/context-providers/clipboard.d.ts +13 -0
- package/dist/context-providers/clipboard.d.ts.map +1 -0
- package/dist/context-providers/clipboard.js +53 -0
- package/dist/context-providers/clipboard.js.map +1 -0
- package/dist/context-providers/codebase.d.ts +46 -0
- package/dist/context-providers/codebase.d.ts.map +1 -0
- package/dist/context-providers/codebase.js +273 -0
- package/dist/context-providers/codebase.js.map +1 -0
- package/dist/context-providers/diff.d.ts +18 -0
- package/dist/context-providers/diff.d.ts.map +1 -0
- package/dist/context-providers/diff.js +63 -0
- package/dist/context-providers/diff.js.map +1 -0
- package/dist/context-providers/docs.d.ts +21 -0
- package/dist/context-providers/docs.d.ts.map +1 -0
- package/dist/context-providers/docs.js +180 -0
- package/dist/context-providers/docs.js.map +1 -0
- package/dist/context-providers/file-include.d.ts +13 -0
- package/dist/context-providers/file-include.d.ts.map +1 -0
- package/dist/context-providers/file-include.js +82 -0
- package/dist/context-providers/file-include.js.map +1 -0
- package/dist/context-providers/folder.d.ts +19 -0
- package/dist/context-providers/folder.d.ts.map +1 -0
- package/dist/context-providers/folder.js +130 -0
- package/dist/context-providers/folder.js.map +1 -0
- package/dist/context-providers/git.d.ts +19 -0
- package/dist/context-providers/git.d.ts.map +1 -0
- package/dist/context-providers/git.js +74 -0
- package/dist/context-providers/git.js.map +1 -0
- package/dist/context-providers/index.d.ts +26 -0
- package/dist/context-providers/index.d.ts.map +1 -0
- package/dist/context-providers/index.js +37 -0
- package/dist/context-providers/index.js.map +1 -0
- package/dist/context-providers/open-files.d.ts +25 -0
- package/dist/context-providers/open-files.d.ts.map +1 -0
- package/dist/context-providers/open-files.js +134 -0
- package/dist/context-providers/open-files.js.map +1 -0
- package/dist/context-providers/problems.d.ts +24 -0
- package/dist/context-providers/problems.d.ts.map +1 -0
- package/dist/context-providers/problems.js +97 -0
- package/dist/context-providers/problems.js.map +1 -0
- package/dist/context-providers/registry.d.ts +61 -0
- package/dist/context-providers/registry.d.ts.map +1 -0
- package/dist/context-providers/registry.js +92 -0
- package/dist/context-providers/registry.js.map +1 -0
- package/dist/context-providers/terminal.d.ts +25 -0
- package/dist/context-providers/terminal.d.ts.map +1 -0
- package/dist/context-providers/terminal.js +55 -0
- package/dist/context-providers/terminal.js.map +1 -0
- package/dist/context-providers/tree.d.ts +29 -0
- package/dist/context-providers/tree.d.ts.map +1 -0
- package/dist/context-providers/tree.js +172 -0
- package/dist/context-providers/tree.js.map +1 -0
- package/dist/context-providers/types.d.ts +72 -0
- package/dist/context-providers/types.d.ts.map +1 -0
- package/dist/context-providers/types.js +10 -0
- package/dist/context-providers/types.js.map +1 -0
- package/dist/context-providers/url.d.ts +27 -0
- package/dist/context-providers/url.d.ts.map +1 -0
- package/dist/context-providers/url.js +131 -0
- package/dist/context-providers/url.js.map +1 -0
- package/dist/effort/index.d.ts +78 -0
- package/dist/effort/index.d.ts.map +1 -0
- package/dist/effort/index.js +146 -0
- package/dist/effort/index.js.map +1 -0
- package/dist/hooks/index.d.ts +47 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +151 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +152 -0
- package/dist/index.js.map +1 -0
- package/dist/indexing/chunker.d.ts +25 -0
- package/dist/indexing/chunker.d.ts.map +1 -0
- package/dist/indexing/chunker.js +217 -0
- package/dist/indexing/chunker.js.map +1 -0
- package/dist/indexing/database.d.ts +49 -0
- package/dist/indexing/database.d.ts.map +1 -0
- package/dist/indexing/database.js +287 -0
- package/dist/indexing/database.js.map +1 -0
- package/dist/indexing/index.d.ts +9 -0
- package/dist/indexing/index.d.ts.map +1 -0
- package/dist/indexing/index.js +13 -0
- package/dist/indexing/index.js.map +1 -0
- package/dist/indexing/indexer.d.ts +63 -0
- package/dist/indexing/indexer.d.ts.map +1 -0
- package/dist/indexing/indexer.js +352 -0
- package/dist/indexing/indexer.js.map +1 -0
- package/dist/indexing/recent-edits-cache.d.ts +77 -0
- package/dist/indexing/recent-edits-cache.d.ts.map +1 -0
- package/dist/indexing/recent-edits-cache.js +123 -0
- package/dist/indexing/recent-edits-cache.js.map +1 -0
- package/dist/indexing/types.d.ts +39 -0
- package/dist/indexing/types.d.ts.map +1 -0
- package/dist/indexing/types.js +6 -0
- package/dist/indexing/types.js.map +1 -0
- package/dist/mcp/index.d.ts +33 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +37 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/manager.d.ts +123 -0
- package/dist/mcp/manager.d.ts.map +1 -0
- package/dist/mcp/manager.js +331 -0
- package/dist/mcp/manager.js.map +1 -0
- package/dist/oauth.d.ts +33 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +312 -0
- package/dist/oauth.js.map +1 -0
- package/dist/permissions/index.d.ts +216 -0
- package/dist/permissions/index.d.ts.map +1 -0
- package/dist/permissions/index.js +938 -0
- package/dist/permissions/index.js.map +1 -0
- package/dist/plan/index.d.ts +20 -0
- package/dist/plan/index.d.ts.map +1 -0
- package/dist/plan/index.js +24 -0
- package/dist/plan/index.js.map +1 -0
- package/dist/plan/manager.d.ts +101 -0
- package/dist/plan/manager.d.ts.map +1 -0
- package/dist/plan/manager.js +170 -0
- package/dist/plan/manager.js.map +1 -0
- package/dist/rules/index.d.ts +28 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +31 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/manager.d.ts +77 -0
- package/dist/rules/manager.d.ts.map +1 -0
- package/dist/rules/manager.js +279 -0
- package/dist/rules/manager.js.map +1 -0
- package/dist/rules/types.d.ts +34 -0
- package/dist/rules/types.d.ts.map +1 -0
- package/dist/rules/types.js +9 -0
- package/dist/rules/types.js.map +1 -0
- package/dist/sandbox/filesystem.d.ts +20 -0
- package/dist/sandbox/filesystem.d.ts.map +1 -0
- package/dist/sandbox/filesystem.js +141 -0
- package/dist/sandbox/filesystem.js.map +1 -0
- package/dist/sandbox/index.d.ts +4 -0
- package/dist/sandbox/index.d.ts.map +1 -0
- package/dist/sandbox/index.js +8 -0
- package/dist/sandbox/index.js.map +1 -0
- package/dist/sandbox/manager.d.ts +47 -0
- package/dist/sandbox/manager.d.ts.map +1 -0
- package/dist/sandbox/manager.js +220 -0
- package/dist/sandbox/manager.js.map +1 -0
- package/dist/sandbox/network.d.ts +14 -0
- package/dist/sandbox/network.d.ts.map +1 -0
- package/dist/sandbox/network.js +87 -0
- package/dist/sandbox/network.js.map +1 -0
- package/dist/sandbox/types.d.ts +42 -0
- package/dist/sandbox/types.d.ts.map +1 -0
- package/dist/sandbox/types.js +25 -0
- package/dist/sandbox/types.js.map +1 -0
- package/dist/tools/ast-edit.d.ts +57 -0
- package/dist/tools/ast-edit.d.ts.map +1 -0
- package/dist/tools/ast-edit.js +443 -0
- package/dist/tools/ast-edit.js.map +1 -0
- package/dist/tools/code-verify.d.ts +8 -0
- package/dist/tools/code-verify.d.ts.map +1 -0
- package/dist/tools/code-verify.js +159 -0
- package/dist/tools/code-verify.js.map +1 -0
- package/dist/tools/codebase-search.d.ts +17 -0
- package/dist/tools/codebase-search.d.ts.map +1 -0
- package/dist/tools/codebase-search.js +104 -0
- package/dist/tools/codebase-search.js.map +1 -0
- package/dist/tools/file-delete.d.ts +26 -0
- package/dist/tools/file-delete.d.ts.map +1 -0
- package/dist/tools/file-delete.js +179 -0
- package/dist/tools/file-delete.js.map +1 -0
- package/dist/tools/file-edit.d.ts +10 -0
- package/dist/tools/file-edit.d.ts.map +1 -0
- package/dist/tools/file-edit.js +138 -0
- package/dist/tools/file-edit.js.map +1 -0
- package/dist/tools/file-read.d.ts +12 -0
- package/dist/tools/file-read.d.ts.map +1 -0
- package/dist/tools/file-read.js +211 -0
- package/dist/tools/file-read.js.map +1 -0
- package/dist/tools/file-run.d.ts +10 -0
- package/dist/tools/file-run.d.ts.map +1 -0
- package/dist/tools/file-run.js +179 -0
- package/dist/tools/file-run.js.map +1 -0
- package/dist/tools/file-write.d.ts +10 -0
- package/dist/tools/file-write.d.ts.map +1 -0
- package/dist/tools/file-write.js +134 -0
- package/dist/tools/file-write.js.map +1 -0
- package/dist/tools/glob-search.d.ts +8 -0
- package/dist/tools/glob-search.d.ts.map +1 -0
- package/dist/tools/glob-search.js +108 -0
- package/dist/tools/glob-search.js.map +1 -0
- package/dist/tools/grep-search.d.ts +8 -0
- package/dist/tools/grep-search.d.ts.map +1 -0
- package/dist/tools/grep-search.js +139 -0
- package/dist/tools/grep-search.js.map +1 -0
- package/dist/tools/list-dir.d.ts +16 -0
- package/dist/tools/list-dir.d.ts.map +1 -0
- package/dist/tools/list-dir.js +183 -0
- package/dist/tools/list-dir.js.map +1 -0
- package/dist/tools/multi-edit.d.ts +16 -0
- package/dist/tools/multi-edit.d.ts.map +1 -0
- package/dist/tools/multi-edit.js +163 -0
- package/dist/tools/multi-edit.js.map +1 -0
- package/dist/tools/notebook-edit.d.ts +31 -0
- package/dist/tools/notebook-edit.d.ts.map +1 -0
- package/dist/tools/notebook-edit.js +321 -0
- package/dist/tools/notebook-edit.js.map +1 -0
- package/dist/tools/registry.d.ts +16 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +41 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/shell-exec.d.ts +12 -0
- package/dist/tools/shell-exec.d.ts.map +1 -0
- package/dist/tools/shell-exec.js +261 -0
- package/dist/tools/shell-exec.js.map +1 -0
- package/dist/tools/sub-agent-manager.d.ts +57 -0
- package/dist/tools/sub-agent-manager.d.ts.map +1 -0
- package/dist/tools/sub-agent-manager.js +153 -0
- package/dist/tools/sub-agent-manager.js.map +1 -0
- package/dist/tools/sub-agent-status.d.ts +12 -0
- package/dist/tools/sub-agent-status.d.ts.map +1 -0
- package/dist/tools/sub-agent-status.js +59 -0
- package/dist/tools/sub-agent-status.js.map +1 -0
- package/dist/tools/sub-agent-terminate.d.ts +12 -0
- package/dist/tools/sub-agent-terminate.d.ts.map +1 -0
- package/dist/tools/sub-agent-terminate.js +55 -0
- package/dist/tools/sub-agent-terminate.js.map +1 -0
- package/dist/tools/sub-agent.d.ts +34 -0
- package/dist/tools/sub-agent.d.ts.map +1 -0
- package/dist/tools/sub-agent.js +140 -0
- package/dist/tools/sub-agent.js.map +1 -0
- package/dist/tools/system-info.d.ts +24 -0
- package/dist/tools/system-info.d.ts.map +1 -0
- package/dist/tools/system-info.js +220 -0
- package/dist/tools/system-info.js.map +1 -0
- package/dist/tools/todo.d.ts +16 -0
- package/dist/tools/todo.d.ts.map +1 -0
- package/dist/tools/todo.js +144 -0
- package/dist/tools/todo.js.map +1 -0
- package/dist/tools/types.d.ts +20 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +3 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/tools/view-diff.d.ts +11 -0
- package/dist/tools/view-diff.d.ts.map +1 -0
- package/dist/tools/view-diff.js +88 -0
- package/dist/tools/view-diff.js.map +1 -0
- package/dist/tools/view-repo-map.d.ts +18 -0
- package/dist/tools/view-repo-map.d.ts.map +1 -0
- package/dist/tools/view-repo-map.js +245 -0
- package/dist/tools/view-repo-map.js.map +1 -0
- package/dist/tools/web-fetch.d.ts +13 -0
- package/dist/tools/web-fetch.d.ts.map +1 -0
- package/dist/tools/web-fetch.js +106 -0
- package/dist/tools/web-fetch.js.map +1 -0
- package/dist/tools/web-search.d.ts +10 -0
- package/dist/tools/web-search.d.ts.map +1 -0
- package/dist/tools/web-search.js +106 -0
- package/dist/tools/web-search.js.map +1 -0
- package/dist/utils/gitignore.d.ts +10 -0
- package/dist/utils/gitignore.d.ts.map +1 -0
- package/dist/utils/gitignore.js +104 -0
- package/dist/utils/gitignore.js.map +1 -0
- package/dist/utils/lazy-apply.d.ts +45 -0
- package/dist/utils/lazy-apply.d.ts.map +1 -0
- package/dist/utils/lazy-apply.js +164 -0
- package/dist/utils/lazy-apply.js.map +1 -0
- package/dist/utils/memory.d.ts +36 -0
- package/dist/utils/memory.d.ts.map +1 -0
- package/dist/utils/memory.js +136 -0
- package/dist/utils/memory.js.map +1 -0
- package/dist/utils/path-matching.d.ts +24 -0
- package/dist/utils/path-matching.d.ts.map +1 -0
- package/dist/utils/path-matching.js +116 -0
- package/dist/utils/path-matching.js.map +1 -0
- package/dist/utils/path-safety.d.ts +13 -0
- package/dist/utils/path-safety.d.ts.map +1 -0
- package/dist/utils/path-safety.js +54 -0
- package/dist/utils/path-safety.js.map +1 -0
- package/dist/utils/project-config.d.ts +18 -0
- package/dist/utils/project-config.d.ts.map +1 -0
- package/dist/utils/project-config.js +76 -0
- package/dist/utils/project-config.js.map +1 -0
- package/dist/utils/search-match.d.ts +63 -0
- package/dist/utils/search-match.d.ts.map +1 -0
- package/dist/utils/search-match.js +426 -0
- package/dist/utils/search-match.js.map +1 -0
- package/dist/utils/shell-paths.d.ts +17 -0
- package/dist/utils/shell-paths.d.ts.map +1 -0
- package/dist/utils/shell-paths.js +107 -0
- package/dist/utils/shell-paths.js.map +1 -0
- package/dist/utils/streaming-diff.d.ts +45 -0
- package/dist/utils/streaming-diff.d.ts.map +1 -0
- package/dist/utils/streaming-diff.js +230 -0
- package/dist/utils/streaming-diff.js.map +1 -0
- package/dist/utils/todo.d.ts +47 -0
- package/dist/utils/todo.d.ts.map +1 -0
- package/dist/utils/todo.js +102 -0
- package/dist/utils/todo.js.map +1 -0
- package/package.json +23 -0
- package/src/agents/coordinator.ts +240 -0
- package/src/context-providers/clipboard.ts +48 -0
- package/src/context-providers/codebase.ts +274 -0
- package/src/context-providers/diff.ts +66 -0
- package/src/context-providers/docs.ts +160 -0
- package/src/context-providers/file-include.ts +54 -0
- package/src/context-providers/folder.ts +106 -0
- package/src/context-providers/git.ts +72 -0
- package/src/context-providers/index.ts +26 -0
- package/src/context-providers/open-files.ts +113 -0
- package/src/context-providers/problems.ts +100 -0
- package/src/context-providers/registry.ts +99 -0
- package/src/context-providers/terminal.ts +58 -0
- package/src/context-providers/tree.ts +161 -0
- package/src/context-providers/types.ts +84 -0
- package/src/context-providers/url.ts +138 -0
- package/src/effort/index.ts +177 -0
- package/src/hooks/index.ts +148 -0
- package/src/index.ts +114 -0
- package/src/indexing/README.md +267 -0
- package/src/indexing/chunker.ts +206 -0
- package/src/indexing/database.ts +299 -0
- package/src/indexing/index.ts +15 -0
- package/src/indexing/indexer.ts +383 -0
- package/src/indexing/recent-edits-cache.ts +150 -0
- package/src/indexing/types.ts +44 -0
- package/src/mcp/index.ts +33 -0
- package/src/mcp/manager.ts +385 -0
- package/src/oauth.ts +330 -0
- package/src/permissions/index.ts +1011 -0
- package/src/plan/index.ts +20 -0
- package/src/plan/manager.ts +233 -0
- package/src/rules/index.ts +28 -0
- package/src/rules/manager.ts +276 -0
- package/src/rules/types.ts +40 -0
- package/src/sandbox/filesystem.ts +135 -0
- package/src/sandbox/index.ts +9 -0
- package/src/sandbox/manager.ts +213 -0
- package/src/sandbox/network.ts +101 -0
- package/src/sandbox/types.ts +63 -0
- package/src/tools/ast-edit.ts +493 -0
- package/src/tools/code-verify.ts +143 -0
- package/src/tools/codebase-search.ts +117 -0
- package/src/tools/file-delete.ts +155 -0
- package/src/tools/file-edit.ts +115 -0
- package/src/tools/file-read.ts +195 -0
- package/src/tools/file-run.ts +158 -0
- package/src/tools/file-write.ts +104 -0
- package/src/tools/glob-search.ts +80 -0
- package/src/tools/grep-search.ts +120 -0
- package/src/tools/list-dir.ts +172 -0
- package/src/tools/multi-edit.ts +138 -0
- package/src/tools/notebook-edit.ts +342 -0
- package/src/tools/registry.ts +43 -0
- package/src/tools/shell-exec.ts +251 -0
- package/src/tools/sub-agent-manager.ts +183 -0
- package/src/tools/sub-agent-status.ts +67 -0
- package/src/tools/sub-agent-terminate.ts +62 -0
- package/src/tools/sub-agent.ts +162 -0
- package/src/tools/system-info.ts +248 -0
- package/src/tools/todo.ts +149 -0
- package/src/tools/types.ts +21 -0
- package/src/tools/view-diff.ts +99 -0
- package/src/tools/view-repo-map.ts +249 -0
- package/src/tools/web-fetch.ts +118 -0
- package/src/tools/web-search.ts +129 -0
- package/src/utils/gitignore.ts +73 -0
- package/src/utils/lazy-apply.ts +189 -0
- package/src/utils/memory.ts +124 -0
- package/src/utils/path-matching.ts +84 -0
- package/src/utils/path-safety.ts +19 -0
- package/src/utils/project-config.ts +41 -0
- package/src/utils/search-match.ts +495 -0
- package/src/utils/shell-paths.ts +79 -0
- package/src/utils/streaming-diff.ts +260 -0
- package/src/utils/todo.ts +115 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import * as os from "os";
|
|
4
|
+
import { exec } from "child_process";
|
|
5
|
+
import type { BaseTool, ToolDefinition, ToolResult } from "./types";
|
|
6
|
+
import { safePath } from "../utils/path-safety";
|
|
7
|
+
import type { SandboxManager } from "../sandbox";
|
|
8
|
+
|
|
9
|
+
const IS_WINDOWS = os.platform() === "win32";
|
|
10
|
+
const SHELL = IS_WINDOWS ? "powershell.exe" : process.env.SHELL || "/bin/sh";
|
|
11
|
+
|
|
12
|
+
/** Map file extensions to the command that runs them */
|
|
13
|
+
const RUNNERS: Record<string, string> = {
|
|
14
|
+
".js": "node", ".mjs": "node", ".cjs": "node",
|
|
15
|
+
".ts": "npx ts-node", ".tsx": "npx ts-node",
|
|
16
|
+
".py": IS_WINDOWS ? "python" : "python3", ".rb": "ruby",
|
|
17
|
+
".sh": "bash", ".bash": "bash", ".zsh": "zsh",
|
|
18
|
+
".bat": "cmd /c", ".cmd": "cmd /c", ".ps1": "powershell -File",
|
|
19
|
+
".go": "go run", ".swift": "swift", ".lua": "lua",
|
|
20
|
+
".php": "php", ".pl": "perl",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export class FileRunTool implements BaseTool {
|
|
24
|
+
definition: ToolDefinition = {
|
|
25
|
+
name: "file_run",
|
|
26
|
+
description:
|
|
27
|
+
"Run a file. Auto-detects language from extension (node for .js, python3 for .py, etc). Use after writing a program to test it. Requires user permission. Subject to sandbox restrictions.",
|
|
28
|
+
inputSchema: {
|
|
29
|
+
type: "object",
|
|
30
|
+
properties: {
|
|
31
|
+
file_path: { type: "string", description: "Path to the file to run" },
|
|
32
|
+
args: { type: "string", description: "Command-line arguments (optional)" },
|
|
33
|
+
timeout: { type: "number", description: "Timeout in ms. Default: 30000" },
|
|
34
|
+
env_vars: {
|
|
35
|
+
type: "object",
|
|
36
|
+
description: "Environment variables to set (e.g., {\"PORT\": \"3001\", \"NODE_ENV\": \"test\"})",
|
|
37
|
+
additionalProperties: { type: "string" },
|
|
38
|
+
},
|
|
39
|
+
debug: {
|
|
40
|
+
type: "boolean",
|
|
41
|
+
description: "Enable debug/verbose mode. Sets DEBUG=*, NODE_DEBUG=*, RUST_BACKTRACE=1, PYTHONTRACEBACK=1. Default: false",
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
required: ["file_path"],
|
|
45
|
+
},
|
|
46
|
+
requiresPermission: true,
|
|
47
|
+
permissionMessage: (input) => `Run file: ${input.file_path}`,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
private workingDir: string;
|
|
51
|
+
private sandboxManager?: SandboxManager;
|
|
52
|
+
|
|
53
|
+
constructor(workingDir: string, sandboxManager?: SandboxManager) {
|
|
54
|
+
this.workingDir = workingDir;
|
|
55
|
+
this.sandboxManager = sandboxManager;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async execute(input: Record<string, unknown>): Promise<ToolResult> {
|
|
59
|
+
let filePath: string;
|
|
60
|
+
try {
|
|
61
|
+
filePath = safePath(input.file_path as string, this.workingDir);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
return { success: false, output: "", error: (err as Error).message };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const args = (input.args as string) || "";
|
|
67
|
+
const timeout = (input.timeout as number) || 30000;
|
|
68
|
+
|
|
69
|
+
// Sandbox read check
|
|
70
|
+
if (this.sandboxManager) {
|
|
71
|
+
const check = this.sandboxManager.checkFileRead(filePath);
|
|
72
|
+
if (!check.allowed) {
|
|
73
|
+
return { success: false, output: "", error: check.reason || "Sandbox: read access denied" };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!fs.existsSync(filePath))
|
|
78
|
+
return { success: false, output: "", error: `File not found: ${filePath}` };
|
|
79
|
+
|
|
80
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
81
|
+
const runner = RUNNERS[ext];
|
|
82
|
+
if (!runner)
|
|
83
|
+
return { success: false, output: "", error: `Can't run ${ext} files. Supported: ${Object.keys(RUNNERS).join(", ")}` };
|
|
84
|
+
|
|
85
|
+
const command = `${runner} "${filePath}"${args ? ` ${args}` : ""}`;
|
|
86
|
+
|
|
87
|
+
// Build environment
|
|
88
|
+
const env = this.sandboxManager ? this.sandboxManager.getShellEnv() : { ...process.env };
|
|
89
|
+
|
|
90
|
+
// Merge user-provided env vars
|
|
91
|
+
const envVars = input.env_vars as Record<string, string> | undefined;
|
|
92
|
+
if (envVars && typeof envVars === "object") {
|
|
93
|
+
for (const [key, val] of Object.entries(envVars)) {
|
|
94
|
+
env[key] = String(val);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Debug mode — set common debug env vars
|
|
99
|
+
if (input.debug) {
|
|
100
|
+
env.DEBUG = env.DEBUG || "*";
|
|
101
|
+
env.NODE_DEBUG = env.NODE_DEBUG || "*";
|
|
102
|
+
env.RUST_BACKTRACE = env.RUST_BACKTRACE || "1";
|
|
103
|
+
env.PYTHONTRACEBACK = env.PYTHONTRACEBACK || "1";
|
|
104
|
+
env.PYTHONFAULTHANDLER = env.PYTHONFAULTHANDLER || "1";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return new Promise((resolve) => {
|
|
108
|
+
const child = exec(command, { cwd: this.workingDir, timeout, maxBuffer: 10 * 1024 * 1024, env, shell: SHELL },
|
|
109
|
+
(error, stdout, stderr) => {
|
|
110
|
+
const outputParts: string[] = [];
|
|
111
|
+
if (stdout) outputParts.push(stdout.trimEnd());
|
|
112
|
+
if (stderr) {
|
|
113
|
+
// Filter out common non-error warnings
|
|
114
|
+
const filteredStderr = stderr.split("\n")
|
|
115
|
+
.filter((l) => !l.includes("DeprecationWarning") && !l.includes("ExperimentalWarning"))
|
|
116
|
+
.join("\n").trim();
|
|
117
|
+
if (filteredStderr) outputParts.push(`STDERR:\n${filteredStderr}`);
|
|
118
|
+
}
|
|
119
|
+
const output = outputParts.join("\n\n") || "(no output)";
|
|
120
|
+
|
|
121
|
+
if (error?.killed) {
|
|
122
|
+
// Timed out — but if we got output, it's likely a server/long-running process
|
|
123
|
+
// Treat as success with note, not as an error
|
|
124
|
+
if (output && output !== "(no output)") {
|
|
125
|
+
resolve({
|
|
126
|
+
success: true,
|
|
127
|
+
output: output + `\n\n[Process timed out after ${timeout / 1000}s — this is normal for servers and long-running processes. The output above was captured before timeout. Use shell_exec instead if you need to start a server in the background.]`,
|
|
128
|
+
});
|
|
129
|
+
} else {
|
|
130
|
+
resolve({ success: false, output, error: `Timed out after ${timeout}ms with no output` });
|
|
131
|
+
}
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (error) {
|
|
136
|
+
resolve({ success: false, output, error: `Exit code: ${error.code}` });
|
|
137
|
+
} else {
|
|
138
|
+
resolve({ success: true, output });
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// For server-like processes: if we get output early, don't wait for timeout
|
|
143
|
+
// Kill after 5s if stdout has content (server started successfully)
|
|
144
|
+
let earlyOutput = "";
|
|
145
|
+
child.stdout?.on("data", (chunk: string) => {
|
|
146
|
+
earlyOutput += chunk;
|
|
147
|
+
// If output contains typical server-started messages, kill early
|
|
148
|
+
if (/listening|started|running|ready|server/i.test(earlyOutput)) {
|
|
149
|
+
setTimeout(() => {
|
|
150
|
+
if (!child.killed) {
|
|
151
|
+
child.kill("SIGTERM");
|
|
152
|
+
}
|
|
153
|
+
}, 2000); // Give 2s for any additional startup output
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import type { BaseTool, ToolDefinition, ToolResult } from "./types";
|
|
4
|
+
import { safePath } from "../utils/path-safety";
|
|
5
|
+
import { hasPlaceholders, expandPlaceholders } from "../utils/lazy-apply";
|
|
6
|
+
import type { SandboxManager } from "../sandbox";
|
|
7
|
+
|
|
8
|
+
export class FileWriteTool implements BaseTool {
|
|
9
|
+
definition: ToolDefinition = {
|
|
10
|
+
name: "file_write",
|
|
11
|
+
description:
|
|
12
|
+
`Create a new file or completely overwrite an existing file. Creates parent directories if needed. Requires user permission.
|
|
13
|
+
|
|
14
|
+
Use this tool ONLY for:
|
|
15
|
+
- Creating new files that don't exist yet
|
|
16
|
+
- Complete file rewrites where the entire content changes
|
|
17
|
+
|
|
18
|
+
Do NOT use this for partial edits — use file_edit or multi_edit instead, which preserve unchanged code and provide diffs.`,
|
|
19
|
+
inputSchema: {
|
|
20
|
+
type: "object",
|
|
21
|
+
properties: {
|
|
22
|
+
file_path: {
|
|
23
|
+
type: "string",
|
|
24
|
+
description: "Path to the file to write",
|
|
25
|
+
},
|
|
26
|
+
content: {
|
|
27
|
+
type: "string",
|
|
28
|
+
description: "Content to write to the file",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
required: ["file_path", "content"],
|
|
32
|
+
},
|
|
33
|
+
requiresPermission: true,
|
|
34
|
+
permissionMessage: (input) => `Write to file: ${input.file_path}`,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
private workingDir: string;
|
|
38
|
+
private sandboxManager?: SandboxManager;
|
|
39
|
+
|
|
40
|
+
constructor(workingDir: string, sandboxManager?: SandboxManager) {
|
|
41
|
+
this.workingDir = workingDir;
|
|
42
|
+
this.sandboxManager = sandboxManager;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async execute(input: Record<string, unknown>): Promise<ToolResult> {
|
|
46
|
+
let filePath: string;
|
|
47
|
+
try {
|
|
48
|
+
filePath = safePath(input.file_path as string, this.workingDir);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
return { success: false, output: "", error: (err as Error).message };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Sandbox write check
|
|
54
|
+
if (this.sandboxManager) {
|
|
55
|
+
const check = this.sandboxManager.checkFileWrite(filePath);
|
|
56
|
+
if (!check.allowed) {
|
|
57
|
+
return { success: false, output: "", error: check.reason || "Sandbox: write access denied" };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let content = input.content as string;
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const dir = path.dirname(filePath);
|
|
65
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
66
|
+
|
|
67
|
+
const existed = fs.existsSync(filePath);
|
|
68
|
+
const oldContent = existed ? fs.readFileSync(filePath, "utf-8") : "";
|
|
69
|
+
|
|
70
|
+
// Lazy apply: if content has placeholders and file exists, expand them
|
|
71
|
+
if (existed && hasPlaceholders(content)) {
|
|
72
|
+
const { content: expanded, placeholdersExpanded } = expandPlaceholders(oldContent, content);
|
|
73
|
+
if (placeholdersExpanded > 0) {
|
|
74
|
+
content = expanded;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
79
|
+
const lines = content.split("\n").length;
|
|
80
|
+
|
|
81
|
+
// Generate diff for the extension to render
|
|
82
|
+
let diff = "";
|
|
83
|
+
if (existed && oldContent !== content) {
|
|
84
|
+
const oldLines = oldContent.split("\n");
|
|
85
|
+
const newLines = content.split("\n");
|
|
86
|
+
diff = `--- a/${input.file_path}\n+++ b/${input.file_path}\n@@ -1,${oldLines.length} +1,${newLines.length} @@\n`;
|
|
87
|
+
for (const line of oldLines) diff += `- ${line}\n`;
|
|
88
|
+
for (const line of newLines) diff += `+ ${line}\n`;
|
|
89
|
+
} else if (!existed) {
|
|
90
|
+
const newLines = content.split("\n");
|
|
91
|
+
diff = `--- /dev/null\n+++ b/${input.file_path}\n@@ -0,0 +1,${newLines.length} @@\n`;
|
|
92
|
+
for (const line of newLines) diff += `+ ${line}\n`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const summary = `${existed ? "Updated" : "Created"} file: ${filePath} (${lines} lines)`;
|
|
96
|
+
return {
|
|
97
|
+
success: true,
|
|
98
|
+
output: diff ? `${summary}\n\n${diff}` : summary,
|
|
99
|
+
};
|
|
100
|
+
} catch (err) {
|
|
101
|
+
return { success: false, output: "", error: `Failed to write: ${err}` };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { glob } from "glob";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import type { BaseTool, ToolDefinition, ToolResult } from "./types";
|
|
4
|
+
import { loadIgnorePatterns } from "../utils/gitignore";
|
|
5
|
+
|
|
6
|
+
export class GlobSearchTool implements BaseTool {
|
|
7
|
+
definition: ToolDefinition = {
|
|
8
|
+
name: "glob_search",
|
|
9
|
+
description:
|
|
10
|
+
"Search for files matching a glob pattern. Returns matching file paths sorted by modification time. Use patterns like '**/*.ts', 'src/**/*.js', '*.json'. Respects .gitignore. The search always starts from the project working directory — do NOT pass directory unless you specifically need a subdirectory (e.g. 'src/components'). Never pass '/' or an absolute path.",
|
|
11
|
+
inputSchema: {
|
|
12
|
+
type: "object",
|
|
13
|
+
properties: {
|
|
14
|
+
pattern: {
|
|
15
|
+
type: "string",
|
|
16
|
+
description: "Glob pattern to match files (e.g., '**/*.ts', 'src/**/*.js')",
|
|
17
|
+
},
|
|
18
|
+
directory: {
|
|
19
|
+
type: "string",
|
|
20
|
+
description: "Optional subdirectory to narrow the search (e.g. 'src/components'). Omit to search the entire project. Never use '/' or absolute paths.",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
required: ["pattern"],
|
|
24
|
+
},
|
|
25
|
+
requiresPermission: false,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
private workingDir: string;
|
|
29
|
+
|
|
30
|
+
constructor(workingDir: string) {
|
|
31
|
+
this.workingDir = workingDir;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async execute(input: Record<string, unknown>): Promise<ToolResult> {
|
|
35
|
+
const pattern = input.pattern as string;
|
|
36
|
+
const rawDir = (input.directory as string | undefined) || "";
|
|
37
|
+
// Clamp to workingDir: reject "/" or paths outside the project
|
|
38
|
+
let searchDir: string;
|
|
39
|
+
if (!rawDir || rawDir === "/") {
|
|
40
|
+
searchDir = this.workingDir;
|
|
41
|
+
} else {
|
|
42
|
+
const resolved = path.isAbsolute(rawDir)
|
|
43
|
+
? rawDir
|
|
44
|
+
: path.resolve(this.workingDir, rawDir);
|
|
45
|
+
// Don't allow searching outside the working directory
|
|
46
|
+
searchDir = resolved.startsWith(this.workingDir) ? resolved : this.workingDir;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const ignorePatterns = loadIgnorePatterns(this.workingDir);
|
|
51
|
+
const files = await glob(pattern, {
|
|
52
|
+
cwd: searchDir,
|
|
53
|
+
nodir: true,
|
|
54
|
+
ignore: ignorePatterns,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
if (files.length === 0) {
|
|
58
|
+
return { success: true, output: "No files found matching the pattern." };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const result = files
|
|
62
|
+
.slice(0, 100)
|
|
63
|
+
.map((f) => path.join(searchDir, f))
|
|
64
|
+
.join("\n");
|
|
65
|
+
|
|
66
|
+
const summary =
|
|
67
|
+
files.length > 100
|
|
68
|
+
? `\n\n... and ${files.length - 100} more files (showing first 100)`
|
|
69
|
+
: "";
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
success: true,
|
|
73
|
+
output: `Found ${files.length} file(s):\n${result}${summary}`,
|
|
74
|
+
};
|
|
75
|
+
} catch (err) {
|
|
76
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
77
|
+
return { success: false, output: "", error: `Glob search failed: ${message}` };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { glob } from "glob";
|
|
4
|
+
import type { BaseTool, ToolDefinition, ToolResult } from "./types";
|
|
5
|
+
import { loadIgnorePatterns } from "../utils/gitignore";
|
|
6
|
+
|
|
7
|
+
interface GrepMatch {
|
|
8
|
+
file: string;
|
|
9
|
+
line: number;
|
|
10
|
+
content: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class GrepSearchTool implements BaseTool {
|
|
14
|
+
definition: ToolDefinition = {
|
|
15
|
+
name: "grep_search",
|
|
16
|
+
description:
|
|
17
|
+
"Search file contents using a regex pattern. Returns matching lines with file paths and line numbers. Use this to find code patterns, function definitions, variable usages, etc. Respects .gitignore.",
|
|
18
|
+
inputSchema: {
|
|
19
|
+
type: "object",
|
|
20
|
+
properties: {
|
|
21
|
+
pattern: {
|
|
22
|
+
type: "string",
|
|
23
|
+
description: "Regex pattern to search for in file contents",
|
|
24
|
+
},
|
|
25
|
+
directory: {
|
|
26
|
+
type: "string",
|
|
27
|
+
description: "Directory to search in. Defaults to working directory.",
|
|
28
|
+
},
|
|
29
|
+
file_pattern: {
|
|
30
|
+
type: "string",
|
|
31
|
+
description: "Glob pattern to filter files (e.g., '*.ts', '*.py'). Optional.",
|
|
32
|
+
},
|
|
33
|
+
case_insensitive: {
|
|
34
|
+
type: "boolean",
|
|
35
|
+
description: "Case insensitive search. Default: false.",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
required: ["pattern"],
|
|
39
|
+
},
|
|
40
|
+
requiresPermission: false,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
private workingDir: string;
|
|
44
|
+
|
|
45
|
+
constructor(workingDir: string) {
|
|
46
|
+
this.workingDir = workingDir;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async execute(input: Record<string, unknown>): Promise<ToolResult> {
|
|
50
|
+
const pattern = input.pattern as string;
|
|
51
|
+
const directory = (input.directory as string) || this.workingDir;
|
|
52
|
+
const filePattern = (input.file_pattern as string) || "**/*";
|
|
53
|
+
const caseInsensitive = (input.case_insensitive as boolean) || false;
|
|
54
|
+
|
|
55
|
+
const searchDir = path.isAbsolute(directory)
|
|
56
|
+
? directory
|
|
57
|
+
: path.resolve(this.workingDir, directory);
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const regex = new RegExp(pattern, caseInsensitive ? "gi" : "g");
|
|
61
|
+
const ignorePatterns = loadIgnorePatterns(this.workingDir);
|
|
62
|
+
const files = await glob(filePattern, {
|
|
63
|
+
cwd: searchDir,
|
|
64
|
+
nodir: true,
|
|
65
|
+
ignore: ignorePatterns,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const matches: GrepMatch[] = [];
|
|
69
|
+
const maxMatches = 200;
|
|
70
|
+
|
|
71
|
+
for (const file of files) {
|
|
72
|
+
if (matches.length >= maxMatches) break;
|
|
73
|
+
|
|
74
|
+
const fullPath = path.join(searchDir, file);
|
|
75
|
+
try {
|
|
76
|
+
const stat = fs.statSync(fullPath);
|
|
77
|
+
if (stat.size > 1024 * 1024) continue; // skip files > 1MB
|
|
78
|
+
|
|
79
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
80
|
+
const lines = content.split("\n");
|
|
81
|
+
|
|
82
|
+
for (let i = 0; i < lines.length; i++) {
|
|
83
|
+
if (matches.length >= maxMatches) break;
|
|
84
|
+
if (regex.test(lines[i])) {
|
|
85
|
+
matches.push({
|
|
86
|
+
file: fullPath,
|
|
87
|
+
line: i + 1,
|
|
88
|
+
content: lines[i].trim(),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
regex.lastIndex = 0;
|
|
92
|
+
}
|
|
93
|
+
} catch {
|
|
94
|
+
// skip unreadable files
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (matches.length === 0) {
|
|
99
|
+
return { success: true, output: "No matches found." };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const result = matches
|
|
103
|
+
.map((m) => `${m.file}:${m.line}: ${m.content}`)
|
|
104
|
+
.join("\n");
|
|
105
|
+
|
|
106
|
+
const summary =
|
|
107
|
+
matches.length >= maxMatches
|
|
108
|
+
? `\n\n... results truncated at ${maxMatches} matches`
|
|
109
|
+
: "";
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
success: true,
|
|
113
|
+
output: `Found ${matches.length} match(es):\n${result}${summary}`,
|
|
114
|
+
};
|
|
115
|
+
} catch (err) {
|
|
116
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
117
|
+
return { success: false, output: "", error: `Grep search failed: ${message}` };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List Directory Tool — list directory contents with optional recursion.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as fs from "fs";
|
|
6
|
+
import * as path from "path";
|
|
7
|
+
import type { BaseTool, ToolDefinition, ToolResult } from "./types";
|
|
8
|
+
import { safePath } from "../utils/path-safety";
|
|
9
|
+
import { loadIgnorePatterns } from "../utils/gitignore";
|
|
10
|
+
import type { SandboxManager } from "../sandbox";
|
|
11
|
+
|
|
12
|
+
const DEFAULT_MAX_ENTRIES = 500;
|
|
13
|
+
|
|
14
|
+
export class ListDirTool implements BaseTool {
|
|
15
|
+
definition: ToolDefinition = {
|
|
16
|
+
name: "list_dir",
|
|
17
|
+
description:
|
|
18
|
+
`List directory contents. Shows files and subdirectories with type indicators. Respects .gitignore patterns.
|
|
19
|
+
|
|
20
|
+
Use this to understand directory structure before reading or editing files. Supports optional recursive listing with depth control.`,
|
|
21
|
+
inputSchema: {
|
|
22
|
+
type: "object",
|
|
23
|
+
properties: {
|
|
24
|
+
directory: {
|
|
25
|
+
type: "string",
|
|
26
|
+
description: "Directory path to list. Defaults to working directory.",
|
|
27
|
+
},
|
|
28
|
+
recursive: {
|
|
29
|
+
type: "boolean",
|
|
30
|
+
description: "List recursively. Default: false.",
|
|
31
|
+
},
|
|
32
|
+
max_depth: {
|
|
33
|
+
type: "number",
|
|
34
|
+
description: "Maximum depth for recursive listing. Default: 3.",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
required: [],
|
|
38
|
+
},
|
|
39
|
+
requiresPermission: false,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
private workingDir: string;
|
|
43
|
+
private sandboxManager?: SandboxManager;
|
|
44
|
+
|
|
45
|
+
constructor(workingDir: string, sandboxManager?: SandboxManager) {
|
|
46
|
+
this.workingDir = workingDir;
|
|
47
|
+
this.sandboxManager = sandboxManager;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async execute(input: Record<string, unknown>): Promise<ToolResult> {
|
|
51
|
+
const dirInput = (input.directory as string) || ".";
|
|
52
|
+
const recursive = (input.recursive as boolean) || false;
|
|
53
|
+
const maxDepth = (input.max_depth as number) || 3;
|
|
54
|
+
|
|
55
|
+
let dirPath: string;
|
|
56
|
+
try {
|
|
57
|
+
dirPath = safePath(dirInput, this.workingDir);
|
|
58
|
+
} catch (err) {
|
|
59
|
+
return { success: false, output: "", error: (err as Error).message };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Sandbox read check
|
|
63
|
+
if (this.sandboxManager) {
|
|
64
|
+
const check = this.sandboxManager.checkFileRead(dirPath);
|
|
65
|
+
if (!check.allowed) {
|
|
66
|
+
return { success: false, output: "", error: check.reason || "Sandbox: read access denied" };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!fs.existsSync(dirPath)) {
|
|
71
|
+
return { success: false, output: "", error: `Directory not found: ${dirPath}` };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!fs.statSync(dirPath).isDirectory()) {
|
|
75
|
+
return { success: false, output: "", error: `Not a directory: ${dirPath}` };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const ignorePatterns = loadIgnorePatterns(this.workingDir);
|
|
79
|
+
const entries: string[] = [];
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
this.listEntries(dirPath, "", recursive ? maxDepth : 0, 0, entries, ignorePatterns);
|
|
83
|
+
} catch (err) {
|
|
84
|
+
return { success: false, output: "", error: `Failed to list: ${(err as Error).message}` };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (entries.length === 0) {
|
|
88
|
+
return { success: true, output: `${dirPath}: (empty directory)` };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const truncated = entries.length > DEFAULT_MAX_ENTRIES;
|
|
92
|
+
const shown = truncated ? entries.slice(0, DEFAULT_MAX_ENTRIES) : entries;
|
|
93
|
+
const suffix = truncated ? `\n\n... and ${entries.length - DEFAULT_MAX_ENTRIES} more entries (truncated)` : "";
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
success: true,
|
|
97
|
+
output: `${dirPath}:\n${shown.join("\n")}${suffix}`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private listEntries(
|
|
102
|
+
basePath: string,
|
|
103
|
+
prefix: string,
|
|
104
|
+
maxDepth: number,
|
|
105
|
+
currentDepth: number,
|
|
106
|
+
result: string[],
|
|
107
|
+
ignorePatterns: string[],
|
|
108
|
+
): void {
|
|
109
|
+
let entries: fs.Dirent[];
|
|
110
|
+
try {
|
|
111
|
+
entries = fs.readdirSync(basePath, { withFileTypes: true });
|
|
112
|
+
} catch {
|
|
113
|
+
return; // Permission denied
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Sort: directories first, then alphabetical
|
|
117
|
+
entries.sort((a, b) => {
|
|
118
|
+
if (a.isDirectory() && !b.isDirectory()) return -1;
|
|
119
|
+
if (!a.isDirectory() && b.isDirectory()) return 1;
|
|
120
|
+
return a.name.localeCompare(b.name);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
for (const entry of entries) {
|
|
124
|
+
if (result.length >= DEFAULT_MAX_ENTRIES * 2) return;
|
|
125
|
+
|
|
126
|
+
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
127
|
+
|
|
128
|
+
// Skip ignored patterns
|
|
129
|
+
if (this.shouldIgnore(entry.name, relativePath, ignorePatterns)) continue;
|
|
130
|
+
|
|
131
|
+
const indent = " ".repeat(currentDepth);
|
|
132
|
+
if (entry.isDirectory()) {
|
|
133
|
+
result.push(`${indent}${entry.name}/`);
|
|
134
|
+
if (currentDepth < maxDepth) {
|
|
135
|
+
this.listEntries(
|
|
136
|
+
path.join(basePath, entry.name),
|
|
137
|
+
relativePath,
|
|
138
|
+
maxDepth,
|
|
139
|
+
currentDepth + 1,
|
|
140
|
+
result,
|
|
141
|
+
ignorePatterns,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
const size = this.getFileSize(path.join(basePath, entry.name));
|
|
146
|
+
result.push(`${indent}${entry.name}${size ? ` (${size})` : ""}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private shouldIgnore(name: string, relativePath: string, patterns: string[]): boolean {
|
|
152
|
+
// Always ignore these
|
|
153
|
+
if (name === ".git" || name === "node_modules" || name === ".DS_Store") return true;
|
|
154
|
+
// Check gitignore patterns (simple prefix/suffix matching)
|
|
155
|
+
for (const p of patterns) {
|
|
156
|
+
if (p === name || relativePath.startsWith(p) || relativePath.endsWith(p)) return true;
|
|
157
|
+
}
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private getFileSize(filePath: string): string {
|
|
162
|
+
try {
|
|
163
|
+
const stat = fs.statSync(filePath);
|
|
164
|
+
const bytes = stat.size;
|
|
165
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
166
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)}KB`;
|
|
167
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
168
|
+
} catch {
|
|
169
|
+
return "";
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|