@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,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi Edit Tool — apply multiple find-and-replace edits to a single file atomically.
|
|
3
|
+
*
|
|
4
|
+
* Edits are applied sequentially (each operates on the result of the previous).
|
|
5
|
+
* If any edit fails, none are applied. Uses multi-strategy matching.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from "fs";
|
|
9
|
+
import type { BaseTool, ToolDefinition, ToolResult } from "./types";
|
|
10
|
+
import { safePath } from "../utils/path-safety";
|
|
11
|
+
import { executeMultiFindAndReplace } from "../utils/search-match";
|
|
12
|
+
import type { SandboxManager } from "../sandbox";
|
|
13
|
+
|
|
14
|
+
export class MultiEditTool implements BaseTool {
|
|
15
|
+
definition: ToolDefinition = {
|
|
16
|
+
name: "multi_edit",
|
|
17
|
+
description:
|
|
18
|
+
`Apply multiple find-and-replace edits to a single file in one atomic operation. Uses multi-strategy matching (exact → trimmed → case-insensitive → whitespace-ignored). Requires user permission.
|
|
19
|
+
|
|
20
|
+
Use this when you need to make several changes to different parts of the same file. This is more efficient than multiple file_edit calls and ensures all changes are applied together.
|
|
21
|
+
|
|
22
|
+
IMPORTANT:
|
|
23
|
+
- Always read the file first to see its current contents.
|
|
24
|
+
- All edits are applied sequentially — each edit operates on the result of the previous one.
|
|
25
|
+
- Edits are ATOMIC: if any edit fails, none are applied.
|
|
26
|
+
- old_string and new_string in each edit MUST be different.
|
|
27
|
+
- Plan edit order carefully — earlier edits change the text that later edits search in.
|
|
28
|
+
- This tool CANNOT be called in parallel with itself or file_edit on the SAME file.`,
|
|
29
|
+
inputSchema: {
|
|
30
|
+
type: "object",
|
|
31
|
+
properties: {
|
|
32
|
+
file_path: {
|
|
33
|
+
type: "string",
|
|
34
|
+
description: "Path to the file to edit",
|
|
35
|
+
},
|
|
36
|
+
edits: {
|
|
37
|
+
type: "array",
|
|
38
|
+
description: "Array of edit operations to apply sequentially",
|
|
39
|
+
items: {
|
|
40
|
+
type: "object",
|
|
41
|
+
properties: {
|
|
42
|
+
old_string: {
|
|
43
|
+
type: "string",
|
|
44
|
+
description: "The text to replace (matches with tolerance for whitespace/case differences)",
|
|
45
|
+
},
|
|
46
|
+
new_string: {
|
|
47
|
+
type: "string",
|
|
48
|
+
description: "The replacement text (MUST be different from old_string)",
|
|
49
|
+
},
|
|
50
|
+
replace_all: {
|
|
51
|
+
type: "boolean",
|
|
52
|
+
description: "Replace all occurrences of old_string (default: false)",
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
required: ["old_string", "new_string"],
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
required: ["file_path", "edits"],
|
|
60
|
+
},
|
|
61
|
+
requiresPermission: true,
|
|
62
|
+
permissionMessage: (input) => {
|
|
63
|
+
const edits = input.edits as unknown[];
|
|
64
|
+
return `Multi-edit file: ${input.file_path} (${Array.isArray(edits) ? edits.length : "?"} edits)`;
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
private workingDir: string;
|
|
69
|
+
private sandboxManager?: SandboxManager;
|
|
70
|
+
|
|
71
|
+
constructor(workingDir: string, sandboxManager?: SandboxManager) {
|
|
72
|
+
this.workingDir = workingDir;
|
|
73
|
+
this.sandboxManager = sandboxManager;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async execute(input: Record<string, unknown>): Promise<ToolResult> {
|
|
77
|
+
let filePath: string;
|
|
78
|
+
try {
|
|
79
|
+
filePath = safePath(input.file_path as string, this.workingDir);
|
|
80
|
+
} catch (err) {
|
|
81
|
+
return { success: false, output: "", error: (err as Error).message };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Sandbox write check
|
|
85
|
+
if (this.sandboxManager) {
|
|
86
|
+
const check = this.sandboxManager.checkFileWrite(filePath);
|
|
87
|
+
if (!check.allowed) {
|
|
88
|
+
return { success: false, output: "", error: check.reason || "Sandbox: write access denied" };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const edits = input.edits as Array<{ old_string: string; new_string: string; replace_all?: boolean }>;
|
|
93
|
+
|
|
94
|
+
// Validation
|
|
95
|
+
if (!Array.isArray(edits) || edits.length === 0) {
|
|
96
|
+
return { success: false, output: "", error: "edits must be a non-empty array" };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
for (let i = 0; i < edits.length; i++) {
|
|
100
|
+
const e = edits[i];
|
|
101
|
+
if (!e.old_string && i > 0) {
|
|
102
|
+
return { success: false, output: "", error: `Edit ${i + 1}: only the first edit may have an empty old_string (insertion at beginning)` };
|
|
103
|
+
}
|
|
104
|
+
if (e.old_string === e.new_string) {
|
|
105
|
+
return { success: false, output: "", error: `Edit ${i + 1}: old_string and new_string are identical` };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!fs.existsSync(filePath))
|
|
110
|
+
return { success: false, output: "", error: `File not found: ${filePath}` };
|
|
111
|
+
|
|
112
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const { result, totalCount } = executeMultiFindAndReplace(content, edits);
|
|
116
|
+
fs.writeFileSync(filePath, result, "utf-8");
|
|
117
|
+
|
|
118
|
+
// Generate summary diff
|
|
119
|
+
const diffLines: string[] = [
|
|
120
|
+
`--- a/${filePath}`,
|
|
121
|
+
`+++ b/${filePath}`,
|
|
122
|
+
];
|
|
123
|
+
for (let i = 0; i < edits.length; i++) {
|
|
124
|
+
const e = edits[i];
|
|
125
|
+
diffLines.push(`@@ Edit ${i + 1}/${edits.length} @@`);
|
|
126
|
+
for (const line of e.old_string.split("\n")) diffLines.push(`- ${line}`);
|
|
127
|
+
for (const line of e.new_string.split("\n")) diffLines.push(`+ ${line}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
success: true,
|
|
132
|
+
output: `Multi-edited ${filePath}: ${edits.length} edit(s), ${totalCount} total replacement(s)\n\n${diffLines.join("\n")}`,
|
|
133
|
+
};
|
|
134
|
+
} catch (err) {
|
|
135
|
+
return { success: false, output: "", error: (err as Error).message };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notebook Edit Tool — Read and edit Jupyter notebooks (.ipynb files).
|
|
3
|
+
*
|
|
4
|
+
* Supports:
|
|
5
|
+
* - Reading cells (code, markdown, raw)
|
|
6
|
+
* - Editing cell content by index
|
|
7
|
+
* - Inserting new cells
|
|
8
|
+
* - Deleting cells
|
|
9
|
+
* - Changing cell type
|
|
10
|
+
* - Clearing cell outputs
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import * as fs from "fs";
|
|
14
|
+
import type { BaseTool, ToolDefinition, ToolResult } from "./types";
|
|
15
|
+
import { safePath } from "../utils/path-safety";
|
|
16
|
+
import type { SandboxManager } from "../sandbox";
|
|
17
|
+
|
|
18
|
+
interface NotebookCell {
|
|
19
|
+
cell_type: "code" | "markdown" | "raw";
|
|
20
|
+
source: string[];
|
|
21
|
+
metadata: Record<string, unknown>;
|
|
22
|
+
outputs?: unknown[];
|
|
23
|
+
execution_count?: number | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface Notebook {
|
|
27
|
+
nbformat: number;
|
|
28
|
+
nbformat_minor: number;
|
|
29
|
+
metadata: Record<string, unknown>;
|
|
30
|
+
cells: NotebookCell[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class NotebookEditTool implements BaseTool {
|
|
34
|
+
definition: ToolDefinition = {
|
|
35
|
+
name: "notebook_edit",
|
|
36
|
+
description:
|
|
37
|
+
`Read and edit Jupyter notebook (.ipynb) files. Supports viewing cells, editing cell content, inserting/deleting cells, and changing cell types.
|
|
38
|
+
|
|
39
|
+
Actions:
|
|
40
|
+
- read: View all cells with their indices, types, and content
|
|
41
|
+
- edit_cell: Replace the content of a cell by index
|
|
42
|
+
- insert_cell: Insert a new cell at a position
|
|
43
|
+
- delete_cell: Remove a cell by index
|
|
44
|
+
- change_type: Change a cell's type (code/markdown/raw)
|
|
45
|
+
- clear_outputs: Clear all cell outputs
|
|
46
|
+
|
|
47
|
+
Use this for working with Jupyter notebooks without corrupting the JSON structure.`,
|
|
48
|
+
inputSchema: {
|
|
49
|
+
type: "object",
|
|
50
|
+
properties: {
|
|
51
|
+
file_path: {
|
|
52
|
+
type: "string",
|
|
53
|
+
description: "Path to the .ipynb notebook file",
|
|
54
|
+
},
|
|
55
|
+
action: {
|
|
56
|
+
type: "string",
|
|
57
|
+
enum: ["read", "edit_cell", "insert_cell", "delete_cell", "change_type", "clear_outputs"],
|
|
58
|
+
description: "Action to perform on the notebook",
|
|
59
|
+
},
|
|
60
|
+
cell_index: {
|
|
61
|
+
type: "number",
|
|
62
|
+
description: "Cell index (0-based) for edit_cell, delete_cell, change_type",
|
|
63
|
+
},
|
|
64
|
+
content: {
|
|
65
|
+
type: "string",
|
|
66
|
+
description: "New cell content (for edit_cell, insert_cell)",
|
|
67
|
+
},
|
|
68
|
+
cell_type: {
|
|
69
|
+
type: "string",
|
|
70
|
+
enum: ["code", "markdown", "raw"],
|
|
71
|
+
description: "Cell type (for insert_cell, change_type). Default: code",
|
|
72
|
+
},
|
|
73
|
+
insert_position: {
|
|
74
|
+
type: "number",
|
|
75
|
+
description: "Position to insert new cell (for insert_cell). Default: end of notebook",
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
required: ["file_path", "action"],
|
|
79
|
+
},
|
|
80
|
+
requiresPermission: true,
|
|
81
|
+
permissionMessage: (input) => `Notebook ${input.action}: ${input.file_path}`,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
private workingDir: string;
|
|
85
|
+
private sandboxManager?: SandboxManager;
|
|
86
|
+
|
|
87
|
+
constructor(workingDir: string, sandboxManager?: SandboxManager) {
|
|
88
|
+
this.workingDir = workingDir;
|
|
89
|
+
this.sandboxManager = sandboxManager;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async execute(input: Record<string, unknown>): Promise<ToolResult> {
|
|
93
|
+
let filePath: string;
|
|
94
|
+
try {
|
|
95
|
+
filePath = safePath(input.file_path as string, this.workingDir);
|
|
96
|
+
} catch (err) {
|
|
97
|
+
return { success: false, output: "", error: (err as Error).message };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const action = input.action as string;
|
|
101
|
+
|
|
102
|
+
// Read action doesn't need write permission
|
|
103
|
+
if (action !== "read") {
|
|
104
|
+
if (this.sandboxManager) {
|
|
105
|
+
const check = this.sandboxManager.checkFileWrite(filePath);
|
|
106
|
+
if (!check.allowed) {
|
|
107
|
+
return { success: false, output: "", error: check.reason || "Sandbox: write access denied" };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!fs.existsSync(filePath)) {
|
|
113
|
+
if (action === "read") {
|
|
114
|
+
return { success: false, output: "", error: `Notebook not found: ${filePath}` };
|
|
115
|
+
}
|
|
116
|
+
// For write actions on non-existent file, create a new empty notebook
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
switch (action) {
|
|
121
|
+
case "read":
|
|
122
|
+
return this.readNotebook(filePath);
|
|
123
|
+
case "edit_cell":
|
|
124
|
+
return this.editCell(filePath, input);
|
|
125
|
+
case "insert_cell":
|
|
126
|
+
return this.insertCell(filePath, input);
|
|
127
|
+
case "delete_cell":
|
|
128
|
+
return this.deleteCell(filePath, input);
|
|
129
|
+
case "change_type":
|
|
130
|
+
return this.changeType(filePath, input);
|
|
131
|
+
case "clear_outputs":
|
|
132
|
+
return this.clearOutputs(filePath);
|
|
133
|
+
default:
|
|
134
|
+
return { success: false, output: "", error: `Unknown action: ${action}` };
|
|
135
|
+
}
|
|
136
|
+
} catch (err) {
|
|
137
|
+
return { success: false, output: "", error: (err as Error).message };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private loadNotebook(filePath: string): Notebook {
|
|
142
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
143
|
+
const nb = JSON.parse(raw) as Notebook;
|
|
144
|
+
if (!nb.cells || !Array.isArray(nb.cells)) {
|
|
145
|
+
throw new Error("Invalid notebook: missing cells array");
|
|
146
|
+
}
|
|
147
|
+
return nb;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private saveNotebook(filePath: string, nb: Notebook): void {
|
|
151
|
+
fs.writeFileSync(filePath, JSON.stringify(nb, null, 1) + "\n", "utf-8");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private createEmptyNotebook(): Notebook {
|
|
155
|
+
return {
|
|
156
|
+
nbformat: 4,
|
|
157
|
+
nbformat_minor: 5,
|
|
158
|
+
metadata: {
|
|
159
|
+
kernelspec: { display_name: "Python 3", language: "python", name: "python3" },
|
|
160
|
+
language_info: { name: "python", version: "3.10.0" },
|
|
161
|
+
},
|
|
162
|
+
cells: [],
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private formatCell(cell: NotebookCell, index: number): string {
|
|
167
|
+
const source = Array.isArray(cell.source) ? cell.source.join("") : String(cell.source);
|
|
168
|
+
const lines = source.split("\n");
|
|
169
|
+
const preview = lines.length > 10
|
|
170
|
+
? lines.slice(0, 10).join("\n") + `\n... (${lines.length - 10} more lines)`
|
|
171
|
+
: source;
|
|
172
|
+
|
|
173
|
+
const outputInfo = cell.cell_type === "code" && cell.outputs && Array.isArray(cell.outputs)
|
|
174
|
+
? ` [${cell.outputs.length} output(s)]`
|
|
175
|
+
: "";
|
|
176
|
+
const execCount = cell.execution_count != null ? ` In[${cell.execution_count}]` : "";
|
|
177
|
+
|
|
178
|
+
return `[${index}] ${cell.cell_type}${execCount}${outputInfo}\n${preview}`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private readNotebook(filePath: string): ToolResult {
|
|
182
|
+
const nb = this.loadNotebook(filePath);
|
|
183
|
+
const parts: string[] = [
|
|
184
|
+
`Notebook: ${filePath}`,
|
|
185
|
+
`Format: ${nb.nbformat}.${nb.nbformat_minor}`,
|
|
186
|
+
`Cells: ${nb.cells.length}`,
|
|
187
|
+
`---`,
|
|
188
|
+
];
|
|
189
|
+
|
|
190
|
+
for (let i = 0; i < nb.cells.length; i++) {
|
|
191
|
+
parts.push(this.formatCell(nb.cells[i], i));
|
|
192
|
+
if (i < nb.cells.length - 1) parts.push("");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return { success: true, output: parts.join("\n") };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private editCell(filePath: string, input: Record<string, unknown>): ToolResult {
|
|
199
|
+
const nb = this.loadNotebook(filePath);
|
|
200
|
+
const idx = input.cell_index as number;
|
|
201
|
+
const content = input.content as string;
|
|
202
|
+
|
|
203
|
+
if (idx === undefined || idx === null) {
|
|
204
|
+
return { success: false, output: "", error: "cell_index is required for edit_cell" };
|
|
205
|
+
}
|
|
206
|
+
if (content === undefined) {
|
|
207
|
+
return { success: false, output: "", error: "content is required for edit_cell" };
|
|
208
|
+
}
|
|
209
|
+
if (idx < 0 || idx >= nb.cells.length) {
|
|
210
|
+
return { success: false, output: "", error: `cell_index ${idx} out of range (0-${nb.cells.length - 1})` };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const oldSource = Array.isArray(nb.cells[idx].source) ? nb.cells[idx].source.join("") : "";
|
|
214
|
+
nb.cells[idx].source = content.split("\n").map((line, i, arr) =>
|
|
215
|
+
i < arr.length - 1 ? line + "\n" : line
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
// Clear outputs for code cells when content changes
|
|
219
|
+
if (nb.cells[idx].cell_type === "code") {
|
|
220
|
+
nb.cells[idx].outputs = [];
|
|
221
|
+
nb.cells[idx].execution_count = null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
this.saveNotebook(filePath, nb);
|
|
225
|
+
|
|
226
|
+
const oldLines = oldSource.split("\n").length;
|
|
227
|
+
const newLines = content.split("\n").length;
|
|
228
|
+
return {
|
|
229
|
+
success: true,
|
|
230
|
+
output: `Edited cell [${idx}] (${nb.cells[idx].cell_type}): ${oldLines} → ${newLines} lines`,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private insertCell(filePath: string, input: Record<string, unknown>): ToolResult {
|
|
235
|
+
let nb: Notebook;
|
|
236
|
+
if (fs.existsSync(filePath)) {
|
|
237
|
+
nb = this.loadNotebook(filePath);
|
|
238
|
+
} else {
|
|
239
|
+
nb = this.createEmptyNotebook();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const content = (input.content as string) || "";
|
|
243
|
+
const cellType = (input.cell_type as "code" | "markdown" | "raw") || "code";
|
|
244
|
+
const pos = input.insert_position !== undefined ? (input.insert_position as number) : nb.cells.length;
|
|
245
|
+
|
|
246
|
+
if (pos < 0 || pos > nb.cells.length) {
|
|
247
|
+
return { success: false, output: "", error: `insert_position ${pos} out of range (0-${nb.cells.length})` };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const newCell: NotebookCell = {
|
|
251
|
+
cell_type: cellType,
|
|
252
|
+
source: content.split("\n").map((line, i, arr) =>
|
|
253
|
+
i < arr.length - 1 ? line + "\n" : line
|
|
254
|
+
),
|
|
255
|
+
metadata: {},
|
|
256
|
+
...(cellType === "code" ? { outputs: [], execution_count: null } : {}),
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
nb.cells.splice(pos, 0, newCell);
|
|
260
|
+
this.saveNotebook(filePath, nb);
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
success: true,
|
|
264
|
+
output: `Inserted ${cellType} cell at position [${pos}] (${nb.cells.length} cells total)`,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private deleteCell(filePath: string, input: Record<string, unknown>): ToolResult {
|
|
269
|
+
const nb = this.loadNotebook(filePath);
|
|
270
|
+
const idx = input.cell_index as number;
|
|
271
|
+
|
|
272
|
+
if (idx === undefined || idx === null) {
|
|
273
|
+
return { success: false, output: "", error: "cell_index is required for delete_cell" };
|
|
274
|
+
}
|
|
275
|
+
if (idx < 0 || idx >= nb.cells.length) {
|
|
276
|
+
return { success: false, output: "", error: `cell_index ${idx} out of range (0-${nb.cells.length - 1})` };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const removed = nb.cells.splice(idx, 1)[0];
|
|
280
|
+
this.saveNotebook(filePath, nb);
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
success: true,
|
|
284
|
+
output: `Deleted cell [${idx}] (${removed.cell_type}), ${nb.cells.length} cells remaining`,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private changeType(filePath: string, input: Record<string, unknown>): ToolResult {
|
|
289
|
+
const nb = this.loadNotebook(filePath);
|
|
290
|
+
const idx = input.cell_index as number;
|
|
291
|
+
const newType = input.cell_type as "code" | "markdown" | "raw";
|
|
292
|
+
|
|
293
|
+
if (idx === undefined || idx === null) {
|
|
294
|
+
return { success: false, output: "", error: "cell_index is required for change_type" };
|
|
295
|
+
}
|
|
296
|
+
if (!newType) {
|
|
297
|
+
return { success: false, output: "", error: "cell_type is required for change_type" };
|
|
298
|
+
}
|
|
299
|
+
if (idx < 0 || idx >= nb.cells.length) {
|
|
300
|
+
return { success: false, output: "", error: `cell_index ${idx} out of range (0-${nb.cells.length - 1})` };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const oldType = nb.cells[idx].cell_type;
|
|
304
|
+
nb.cells[idx].cell_type = newType;
|
|
305
|
+
|
|
306
|
+
// Add/remove outputs field based on type
|
|
307
|
+
if (newType === "code") {
|
|
308
|
+
if (!nb.cells[idx].outputs) nb.cells[idx].outputs = [];
|
|
309
|
+
if (nb.cells[idx].execution_count === undefined) nb.cells[idx].execution_count = null;
|
|
310
|
+
} else {
|
|
311
|
+
delete nb.cells[idx].outputs;
|
|
312
|
+
delete nb.cells[idx].execution_count;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
this.saveNotebook(filePath, nb);
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
success: true,
|
|
319
|
+
output: `Changed cell [${idx}] type: ${oldType} → ${newType}`,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private clearOutputs(filePath: string): ToolResult {
|
|
324
|
+
const nb = this.loadNotebook(filePath);
|
|
325
|
+
let cleared = 0;
|
|
326
|
+
|
|
327
|
+
for (const cell of nb.cells) {
|
|
328
|
+
if (cell.cell_type === "code" && cell.outputs && cell.outputs.length > 0) {
|
|
329
|
+
cell.outputs = [];
|
|
330
|
+
cell.execution_count = null;
|
|
331
|
+
cleared++;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
this.saveNotebook(filePath, nb);
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
success: true,
|
|
339
|
+
output: `Cleared outputs from ${cleared} code cell(s) in ${filePath}`,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { BaseTool, ToolResult } from "./types";
|
|
2
|
+
|
|
3
|
+
/** Central registry of all available tools */
|
|
4
|
+
export class ToolRegistry {
|
|
5
|
+
private tools: Map<string, BaseTool> = new Map();
|
|
6
|
+
|
|
7
|
+
register(tool: BaseTool): void {
|
|
8
|
+
this.tools.set(tool.definition.name, tool);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
get(name: string): BaseTool | undefined {
|
|
12
|
+
return this.tools.get(name);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
getAll(): BaseTool[] {
|
|
16
|
+
return Array.from(this.tools.values());
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getDefinitions() {
|
|
20
|
+
return this.getAll().map((t) => t.definition);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async execute(name: string, input: Record<string, unknown>): Promise<ToolResult> {
|
|
24
|
+
const tool = this.tools.get(name);
|
|
25
|
+
if (!tool) {
|
|
26
|
+
return { success: false, output: "", error: `Unknown tool: ${name}` };
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
return await tool.execute(input);
|
|
30
|
+
} catch (err) {
|
|
31
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
32
|
+
return { success: false, output: "", error: message };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
getToolSchemas() {
|
|
37
|
+
return this.getAll().map((t) => ({
|
|
38
|
+
name: t.definition.name,
|
|
39
|
+
description: t.definition.description,
|
|
40
|
+
inputSchema: t.definition.inputSchema,
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
}
|