gencode-ai 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/.env.example +11 -0
- package/CLAUDE.md +70 -0
- package/LICENSE +21 -0
- package/README.md +117 -0
- package/dist/agent/agent.d.ts +84 -0
- package/dist/agent/agent.d.ts.map +1 -0
- package/dist/agent/agent.js +233 -0
- package/dist/agent/agent.js.map +1 -0
- package/dist/agent/index.d.ts +6 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +6 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/types.d.ts +47 -0
- package/dist/agent/types.d.ts.map +1 -0
- package/dist/agent/types.js +5 -0
- package/dist/agent/types.js.map +1 -0
- package/dist/cli/components/App.d.ts +14 -0
- package/dist/cli/components/App.d.ts.map +1 -0
- package/dist/cli/components/App.js +395 -0
- package/dist/cli/components/App.js.map +1 -0
- package/dist/cli/components/CommandSuggestions.d.ts +13 -0
- package/dist/cli/components/CommandSuggestions.d.ts.map +1 -0
- package/dist/cli/components/CommandSuggestions.js +32 -0
- package/dist/cli/components/CommandSuggestions.js.map +1 -0
- package/dist/cli/components/Header.d.ts +9 -0
- package/dist/cli/components/Header.d.ts.map +1 -0
- package/dist/cli/components/Header.js +13 -0
- package/dist/cli/components/Header.js.map +1 -0
- package/dist/cli/components/Input.d.ts +13 -0
- package/dist/cli/components/Input.d.ts.map +1 -0
- package/dist/cli/components/Input.js +27 -0
- package/dist/cli/components/Input.js.map +1 -0
- package/dist/cli/components/Logo.d.ts +2 -0
- package/dist/cli/components/Logo.d.ts.map +1 -0
- package/dist/cli/components/Logo.js +8 -0
- package/dist/cli/components/Logo.js.map +1 -0
- package/dist/cli/components/Messages.d.ts +37 -0
- package/dist/cli/components/Messages.d.ts.map +1 -0
- package/dist/cli/components/Messages.js +106 -0
- package/dist/cli/components/Messages.js.map +1 -0
- package/dist/cli/components/ModelSelector.d.ts +13 -0
- package/dist/cli/components/ModelSelector.d.ts.map +1 -0
- package/dist/cli/components/ModelSelector.js +72 -0
- package/dist/cli/components/ModelSelector.js.map +1 -0
- package/dist/cli/components/Spinner.d.ts +12 -0
- package/dist/cli/components/Spinner.d.ts.map +1 -0
- package/dist/cli/components/Spinner.js +45 -0
- package/dist/cli/components/Spinner.js.map +1 -0
- package/dist/cli/components/index.d.ts +12 -0
- package/dist/cli/components/index.d.ts.map +1 -0
- package/dist/cli/components/index.js +12 -0
- package/dist/cli/components/index.js.map +1 -0
- package/dist/cli/components/theme.d.ts +31 -0
- package/dist/cli/components/theme.d.ts.map +1 -0
- package/dist/cli/components/theme.js +36 -0
- package/dist/cli/components/theme.js.map +1 -0
- package/dist/cli/index-legacy.d.ts +7 -0
- package/dist/cli/index-legacy.d.ts.map +1 -0
- package/dist/cli/index-legacy.js +431 -0
- package/dist/cli/index-legacy.js.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +116 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/ink-cli.d.ts +7 -0
- package/dist/cli/ink-cli.d.ts.map +1 -0
- package/dist/cli/ink-cli.js +105 -0
- package/dist/cli/ink-cli.js.map +1 -0
- package/dist/cli/session-picker.d.ts +16 -0
- package/dist/cli/session-picker.d.ts.map +1 -0
- package/dist/cli/session-picker.js +280 -0
- package/dist/cli/session-picker.js.map +1 -0
- package/dist/cli/ui.d.ts +61 -0
- package/dist/cli/ui.d.ts.map +1 -0
- package/dist/cli/ui.js +364 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/config/index.d.ts +7 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +6 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/manager.d.ts +31 -0
- package/dist/config/manager.d.ts.map +1 -0
- package/dist/config/manager.js +65 -0
- package/dist/config/manager.js.map +1 -0
- package/dist/config/types.d.ts +22 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +6 -0
- package/dist/config/types.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/memory/index.d.ts +10 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +9 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/init.d.ts +20 -0
- package/dist/memory/init.d.ts.map +1 -0
- package/dist/memory/init.js +332 -0
- package/dist/memory/init.js.map +1 -0
- package/dist/memory/manager.d.ts +85 -0
- package/dist/memory/manager.d.ts.map +1 -0
- package/dist/memory/manager.js +234 -0
- package/dist/memory/manager.js.map +1 -0
- package/dist/memory/types.d.ts +74 -0
- package/dist/memory/types.d.ts.map +1 -0
- package/dist/memory/types.js +6 -0
- package/dist/memory/types.js.map +1 -0
- package/dist/permissions/index.d.ts +7 -0
- package/dist/permissions/index.d.ts.map +1 -0
- package/dist/permissions/index.js +6 -0
- package/dist/permissions/index.js.map +1 -0
- package/dist/permissions/manager.d.ts +32 -0
- package/dist/permissions/manager.d.ts.map +1 -0
- package/dist/permissions/manager.js +79 -0
- package/dist/permissions/manager.js.map +1 -0
- package/dist/permissions/types.d.ts +14 -0
- package/dist/permissions/types.d.ts.map +1 -0
- package/dist/permissions/types.js +17 -0
- package/dist/permissions/types.js.map +1 -0
- package/dist/providers/anthropic.d.ts +20 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +185 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/gemini.d.ts +21 -0
- package/dist/providers/gemini.d.ts.map +1 -0
- package/dist/providers/gemini.js +241 -0
- package/dist/providers/gemini.js.map +1 -0
- package/dist/providers/index.d.ts +34 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +72 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/openai.d.ts +19 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +221 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/types.d.ts +125 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +6 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/session/index.d.ts +6 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +6 -0
- package/dist/session/index.js.map +1 -0
- package/dist/session/manager.d.ts +101 -0
- package/dist/session/manager.d.ts.map +1 -0
- package/dist/session/manager.js +295 -0
- package/dist/session/manager.js.map +1 -0
- package/dist/session/types.d.ts +39 -0
- package/dist/session/types.d.ts.map +1 -0
- package/dist/session/types.js +10 -0
- package/dist/session/types.js.map +1 -0
- package/dist/tools/builtin/bash.d.ts +7 -0
- package/dist/tools/builtin/bash.d.ts.map +1 -0
- package/dist/tools/builtin/bash.js +80 -0
- package/dist/tools/builtin/bash.js.map +1 -0
- package/dist/tools/builtin/edit.d.ts +7 -0
- package/dist/tools/builtin/edit.d.ts.map +1 -0
- package/dist/tools/builtin/edit.js +32 -0
- package/dist/tools/builtin/edit.js.map +1 -0
- package/dist/tools/builtin/glob.d.ts +7 -0
- package/dist/tools/builtin/glob.d.ts.map +1 -0
- package/dist/tools/builtin/glob.js +36 -0
- package/dist/tools/builtin/glob.js.map +1 -0
- package/dist/tools/builtin/grep.d.ts +7 -0
- package/dist/tools/builtin/grep.d.ts.map +1 -0
- package/dist/tools/builtin/grep.js +59 -0
- package/dist/tools/builtin/grep.js.map +1 -0
- package/dist/tools/builtin/read.d.ts +7 -0
- package/dist/tools/builtin/read.d.ts.map +1 -0
- package/dist/tools/builtin/read.js +29 -0
- package/dist/tools/builtin/read.js.map +1 -0
- package/dist/tools/builtin/write.d.ts +7 -0
- package/dist/tools/builtin/write.d.ts.map +1 -0
- package/dist/tools/builtin/write.js +24 -0
- package/dist/tools/builtin/write.js.map +1 -0
- package/dist/tools/index.d.ts +38 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +32 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/registry.d.ts +22 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +71 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/types.d.ts +62 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +126 -0
- package/dist/tools/types.js.map +1 -0
- package/docs/README.md +16 -0
- package/docs/proposals/0001-web-fetch-tool.md +293 -0
- package/docs/proposals/0002-web-search-tool.md +306 -0
- package/docs/proposals/0003-task-subagents.md +333 -0
- package/docs/proposals/0004-plan-mode.md +338 -0
- package/docs/proposals/0005-todo-system.md +299 -0
- package/docs/proposals/0006-memory-system.md +539 -0
- package/docs/proposals/0007-context-management.md +429 -0
- package/docs/proposals/0008-checkpointing.md +327 -0
- package/docs/proposals/0009-hooks-system.md +343 -0
- package/docs/proposals/0010-mcp-integration.md +382 -0
- package/docs/proposals/0011-custom-commands.md +374 -0
- package/docs/proposals/0012-ask-user-question.md +317 -0
- package/docs/proposals/0013-multi-edit-tool.md +345 -0
- package/docs/proposals/0014-lsp-tool.md +478 -0
- package/docs/proposals/0015-ls-tool.md +407 -0
- package/docs/proposals/0016-kill-shell-tool.md +455 -0
- package/docs/proposals/0017-background-tasks.md +489 -0
- package/docs/proposals/0018-parallel-tool-execution.md +415 -0
- package/docs/proposals/0019-session-enhancements.md +462 -0
- package/docs/proposals/0020-session-summarization.md +447 -0
- package/docs/proposals/0021-skills-system.md +409 -0
- package/docs/proposals/0022-plugin-system.md +467 -0
- package/docs/proposals/0023-permission-enhancements.md +470 -0
- package/docs/proposals/0024-keyboard-shortcuts.md +443 -0
- package/docs/proposals/0025-cost-tracking.md +447 -0
- package/docs/proposals/0026-git-integration.md +475 -0
- package/docs/proposals/0027-enhanced-read-tool.md +514 -0
- package/docs/proposals/0028-enhanced-bash-tool.md +511 -0
- package/docs/proposals/0029-notebook-edit-tool.md +413 -0
- package/docs/proposals/0030-plugin-marketplace.md +360 -0
- package/docs/proposals/0031-command-suggestions.md +295 -0
- package/docs/proposals/0032-ide-integrations.md +328 -0
- package/docs/proposals/0033-enterprise-deployment.md +221 -0
- package/docs/proposals/0034-sandboxing.md +273 -0
- package/docs/proposals/0035-auto-updater.md +311 -0
- package/docs/proposals/0036-enhanced-glob-tool.md +267 -0
- package/docs/proposals/0037-enhanced-grep-tool.md +360 -0
- package/docs/proposals/0038-interactive-cli-ui.md +373 -0
- package/docs/proposals/0039-streaming-enhancements.md +359 -0
- package/docs/proposals/0040-multi-provider-enhancements.md +369 -0
- package/docs/proposals/README.md +84 -0
- package/docs/proposals/TEMPLATE.md +57 -0
- package/docs/proposals/research/claude-code-research.md +307 -0
- package/examples/agent-demo.ts +115 -0
- package/examples/basic.ts +166 -0
- package/package.json +50 -0
- package/src/agent/agent.ts +276 -0
- package/src/agent/index.ts +6 -0
- package/src/agent/types.ts +62 -0
- package/src/cli/components/App.tsx +565 -0
- package/src/cli/components/CommandSuggestions.tsx +58 -0
- package/src/cli/components/Header.tsx +36 -0
- package/src/cli/components/Input.tsx +60 -0
- package/src/cli/components/Logo.tsx +16 -0
- package/src/cli/components/Messages.tsx +210 -0
- package/src/cli/components/ModelSelector.tsx +135 -0
- package/src/cli/components/Spinner.tsx +72 -0
- package/src/cli/components/index.ts +21 -0
- package/src/cli/components/theme.ts +36 -0
- package/src/cli/index.tsx +136 -0
- package/src/config/index.ts +7 -0
- package/src/config/manager.ts +77 -0
- package/src/config/types.ts +25 -0
- package/src/index.ts +86 -0
- package/src/permissions/index.ts +7 -0
- package/src/permissions/manager.ts +97 -0
- package/src/permissions/types.ts +29 -0
- package/src/providers/anthropic.ts +224 -0
- package/src/providers/gemini.ts +295 -0
- package/src/providers/index.ts +97 -0
- package/src/providers/openai.ts +261 -0
- package/src/providers/types.ts +181 -0
- package/src/session/index.ts +6 -0
- package/src/session/manager.ts +354 -0
- package/src/session/types.ts +49 -0
- package/src/tools/builtin/bash.ts +92 -0
- package/src/tools/builtin/edit.ts +37 -0
- package/src/tools/builtin/glob.ts +42 -0
- package/src/tools/builtin/grep.ts +67 -0
- package/src/tools/builtin/read.ts +34 -0
- package/src/tools/builtin/write.ts +27 -0
- package/src/tools/index.ts +36 -0
- package/src/tools/registry.ts +83 -0
- package/src/tools/types.ts +172 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
# Proposal: NotebookEdit Tool
|
|
2
|
+
|
|
3
|
+
- **Proposal ID**: 0029
|
|
4
|
+
- **Author**: mycode team
|
|
5
|
+
- **Status**: Draft
|
|
6
|
+
- **Created**: 2025-01-15
|
|
7
|
+
- **Updated**: 2025-01-15
|
|
8
|
+
|
|
9
|
+
## Summary
|
|
10
|
+
|
|
11
|
+
Implement a NotebookEdit tool for modifying Jupyter notebook cells, enabling the agent to create, edit, and manage interactive notebooks for data science and documentation workflows.
|
|
12
|
+
|
|
13
|
+
## Motivation
|
|
14
|
+
|
|
15
|
+
Jupyter notebooks require special handling:
|
|
16
|
+
|
|
17
|
+
1. **JSON structure**: Can't use regular Edit tool
|
|
18
|
+
2. **Cell-based editing**: Need cell-level operations
|
|
19
|
+
3. **Output preservation**: Must maintain cell outputs
|
|
20
|
+
4. **Metadata handling**: Cell metadata needs preservation
|
|
21
|
+
5. **Data science workflows**: Notebooks are essential for ML/data work
|
|
22
|
+
|
|
23
|
+
NotebookEdit enables proper notebook manipulation.
|
|
24
|
+
|
|
25
|
+
## Claude Code Reference
|
|
26
|
+
|
|
27
|
+
Claude Code's NotebookEdit tool provides cell-level operations:
|
|
28
|
+
|
|
29
|
+
### From Tool Description
|
|
30
|
+
```
|
|
31
|
+
Completely replaces the contents of a specific cell in a Jupyter notebook.
|
|
32
|
+
- notebook_path: absolute path to the .ipynb file
|
|
33
|
+
- cell_id: ID of the cell to edit (or position for insert)
|
|
34
|
+
- cell_type: 'code' or 'markdown'
|
|
35
|
+
- new_source: The new content for the cell
|
|
36
|
+
- edit_mode: 'replace', 'insert', or 'delete'
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Operations
|
|
40
|
+
- **replace**: Replace cell content by ID
|
|
41
|
+
- **insert**: Add new cell at position
|
|
42
|
+
- **delete**: Remove cell by ID
|
|
43
|
+
|
|
44
|
+
## Detailed Design
|
|
45
|
+
|
|
46
|
+
### API Design
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
// src/tools/notebook-edit/types.ts
|
|
50
|
+
type CellType = 'code' | 'markdown' | 'raw';
|
|
51
|
+
type EditMode = 'replace' | 'insert' | 'delete';
|
|
52
|
+
|
|
53
|
+
interface NotebookEditInput {
|
|
54
|
+
notebook_path: string;
|
|
55
|
+
cell_id?: string; // Cell ID for replace/delete
|
|
56
|
+
cell_type?: CellType; // Required for insert
|
|
57
|
+
new_source: string; // New cell content
|
|
58
|
+
edit_mode?: EditMode; // Default: 'replace'
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface NotebookEditOutput {
|
|
62
|
+
success: boolean;
|
|
63
|
+
cell_id?: string; // ID of affected cell
|
|
64
|
+
cell_index?: number; // Position of affected cell
|
|
65
|
+
error?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Jupyter notebook structure
|
|
69
|
+
interface JupyterNotebook {
|
|
70
|
+
nbformat: number;
|
|
71
|
+
nbformat_minor: number;
|
|
72
|
+
metadata: NotebookMetadata;
|
|
73
|
+
cells: JupyterCell[];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface JupyterCell {
|
|
77
|
+
id?: string; // Cell ID (nbformat 4.5+)
|
|
78
|
+
cell_type: CellType;
|
|
79
|
+
source: string | string[];
|
|
80
|
+
metadata: CellMetadata;
|
|
81
|
+
outputs?: CellOutput[]; // For code cells
|
|
82
|
+
execution_count?: number | null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
interface CellMetadata {
|
|
86
|
+
id?: string;
|
|
87
|
+
tags?: string[];
|
|
88
|
+
[key: string]: unknown;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
interface CellOutput {
|
|
92
|
+
output_type: 'stream' | 'execute_result' | 'display_data' | 'error';
|
|
93
|
+
[key: string]: unknown;
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### NotebookEdit Tool
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
// src/tools/notebook-edit/notebook-edit-tool.ts
|
|
101
|
+
const notebookEditTool: Tool<NotebookEditInput> = {
|
|
102
|
+
name: 'NotebookEdit',
|
|
103
|
+
description: `Edit Jupyter notebook cells.
|
|
104
|
+
|
|
105
|
+
Parameters:
|
|
106
|
+
- notebook_path: Absolute path to the .ipynb file
|
|
107
|
+
- cell_id: ID of the cell to edit (for replace/delete)
|
|
108
|
+
- cell_type: 'code' or 'markdown' (required for insert)
|
|
109
|
+
- new_source: New content for the cell
|
|
110
|
+
- edit_mode: 'replace' (default), 'insert', or 'delete'
|
|
111
|
+
|
|
112
|
+
Operations:
|
|
113
|
+
- replace: Replace content of existing cell by ID
|
|
114
|
+
- insert: Add new cell after specified cell_id (or at start if not specified)
|
|
115
|
+
- delete: Remove cell by ID
|
|
116
|
+
|
|
117
|
+
Notes:
|
|
118
|
+
- Cell IDs are preserved when editing
|
|
119
|
+
- Cell outputs are cleared when replacing code cells
|
|
120
|
+
- Use 0-indexed position or cell_id for targeting
|
|
121
|
+
`,
|
|
122
|
+
parameters: z.object({
|
|
123
|
+
notebook_path: z.string(),
|
|
124
|
+
cell_id: z.string().optional(),
|
|
125
|
+
cell_type: z.enum(['code', 'markdown', 'raw']).optional(),
|
|
126
|
+
new_source: z.string(),
|
|
127
|
+
edit_mode: z.enum(['replace', 'insert', 'delete']).optional().default('replace')
|
|
128
|
+
}),
|
|
129
|
+
execute: async (input, context) => {
|
|
130
|
+
return notebookEditor.edit(input, context);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Notebook Editor
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
// src/tools/notebook-edit/editor.ts
|
|
139
|
+
class NotebookEditor {
|
|
140
|
+
async edit(input: NotebookEditInput, context: ToolContext): Promise<NotebookEditOutput> {
|
|
141
|
+
const fullPath = path.isAbsolute(input.notebook_path)
|
|
142
|
+
? input.notebook_path
|
|
143
|
+
: path.resolve(context.cwd, input.notebook_path);
|
|
144
|
+
|
|
145
|
+
// Read notebook
|
|
146
|
+
if (!fs.existsSync(fullPath)) {
|
|
147
|
+
return { success: false, error: 'Notebook not found' };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
151
|
+
let notebook: JupyterNotebook;
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
notebook = JSON.parse(content);
|
|
155
|
+
} catch {
|
|
156
|
+
return { success: false, error: 'Invalid notebook format' };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Perform operation
|
|
160
|
+
let result: NotebookEditOutput;
|
|
161
|
+
|
|
162
|
+
switch (input.edit_mode) {
|
|
163
|
+
case 'replace':
|
|
164
|
+
result = this.replaceCell(notebook, input);
|
|
165
|
+
break;
|
|
166
|
+
case 'insert':
|
|
167
|
+
result = this.insertCell(notebook, input);
|
|
168
|
+
break;
|
|
169
|
+
case 'delete':
|
|
170
|
+
result = this.deleteCell(notebook, input);
|
|
171
|
+
break;
|
|
172
|
+
default:
|
|
173
|
+
return { success: false, error: 'Invalid edit mode' };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (!result.success) return result;
|
|
177
|
+
|
|
178
|
+
// Write notebook back
|
|
179
|
+
await fs.writeFile(fullPath, JSON.stringify(notebook, null, 1), 'utf-8');
|
|
180
|
+
|
|
181
|
+
return result;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private replaceCell(
|
|
185
|
+
notebook: JupyterNotebook,
|
|
186
|
+
input: NotebookEditInput
|
|
187
|
+
): NotebookEditOutput {
|
|
188
|
+
const cellIndex = this.findCellIndex(notebook, input.cell_id);
|
|
189
|
+
|
|
190
|
+
if (cellIndex === -1) {
|
|
191
|
+
return { success: false, error: `Cell not found: ${input.cell_id}` };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const cell = notebook.cells[cellIndex];
|
|
195
|
+
|
|
196
|
+
// Update source
|
|
197
|
+
cell.source = this.normalizeSource(input.new_source);
|
|
198
|
+
|
|
199
|
+
// Update cell type if specified
|
|
200
|
+
if (input.cell_type) {
|
|
201
|
+
cell.cell_type = input.cell_type;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Clear outputs for code cells
|
|
205
|
+
if (cell.cell_type === 'code') {
|
|
206
|
+
cell.outputs = [];
|
|
207
|
+
cell.execution_count = null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
success: true,
|
|
212
|
+
cell_id: cell.id || input.cell_id,
|
|
213
|
+
cell_index: cellIndex
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private insertCell(
|
|
218
|
+
notebook: JupyterNotebook,
|
|
219
|
+
input: NotebookEditInput
|
|
220
|
+
): NotebookEditOutput {
|
|
221
|
+
if (!input.cell_type) {
|
|
222
|
+
return { success: false, error: 'cell_type required for insert' };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const newCell: JupyterCell = {
|
|
226
|
+
id: this.generateCellId(),
|
|
227
|
+
cell_type: input.cell_type,
|
|
228
|
+
source: this.normalizeSource(input.new_source),
|
|
229
|
+
metadata: {}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
if (input.cell_type === 'code') {
|
|
233
|
+
newCell.outputs = [];
|
|
234
|
+
newCell.execution_count = null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Find insertion position
|
|
238
|
+
let insertIndex: number;
|
|
239
|
+
if (input.cell_id) {
|
|
240
|
+
const refIndex = this.findCellIndex(notebook, input.cell_id);
|
|
241
|
+
if (refIndex === -1) {
|
|
242
|
+
return { success: false, error: `Reference cell not found: ${input.cell_id}` };
|
|
243
|
+
}
|
|
244
|
+
insertIndex = refIndex + 1;
|
|
245
|
+
} else {
|
|
246
|
+
insertIndex = 0; // Insert at beginning
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
notebook.cells.splice(insertIndex, 0, newCell);
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
success: true,
|
|
253
|
+
cell_id: newCell.id,
|
|
254
|
+
cell_index: insertIndex
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private deleteCell(
|
|
259
|
+
notebook: JupyterNotebook,
|
|
260
|
+
input: NotebookEditInput
|
|
261
|
+
): NotebookEditOutput {
|
|
262
|
+
if (!input.cell_id) {
|
|
263
|
+
return { success: false, error: 'cell_id required for delete' };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const cellIndex = this.findCellIndex(notebook, input.cell_id);
|
|
267
|
+
|
|
268
|
+
if (cellIndex === -1) {
|
|
269
|
+
return { success: false, error: `Cell not found: ${input.cell_id}` };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
notebook.cells.splice(cellIndex, 1);
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
success: true,
|
|
276
|
+
cell_id: input.cell_id,
|
|
277
|
+
cell_index: cellIndex
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
private findCellIndex(notebook: JupyterNotebook, cellId?: string): number {
|
|
282
|
+
if (!cellId) return -1;
|
|
283
|
+
|
|
284
|
+
// Try to find by ID
|
|
285
|
+
const byId = notebook.cells.findIndex(c => c.id === cellId);
|
|
286
|
+
if (byId !== -1) return byId;
|
|
287
|
+
|
|
288
|
+
// Try to parse as index
|
|
289
|
+
const index = parseInt(cellId, 10);
|
|
290
|
+
if (!isNaN(index) && index >= 0 && index < notebook.cells.length) {
|
|
291
|
+
return index;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return -1;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
private normalizeSource(source: string): string[] {
|
|
298
|
+
// Jupyter stores source as array of lines
|
|
299
|
+
return source.split('\n').map((line, i, arr) =>
|
|
300
|
+
i < arr.length - 1 ? line + '\n' : line
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
private generateCellId(): string {
|
|
305
|
+
return crypto.randomUUID().replace(/-/g, '').slice(0, 8);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export const notebookEditor = new NotebookEditor();
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### File Changes
|
|
313
|
+
|
|
314
|
+
| File | Action | Description |
|
|
315
|
+
|------|--------|-------------|
|
|
316
|
+
| `src/tools/notebook-edit/types.ts` | Create | Type definitions |
|
|
317
|
+
| `src/tools/notebook-edit/notebook-edit-tool.ts` | Create | Tool implementation |
|
|
318
|
+
| `src/tools/notebook-edit/editor.ts` | Create | Notebook editing logic |
|
|
319
|
+
| `src/tools/notebook-edit/index.ts` | Create | Module exports |
|
|
320
|
+
| `src/tools/index.ts` | Modify | Register tool |
|
|
321
|
+
|
|
322
|
+
## User Experience
|
|
323
|
+
|
|
324
|
+
### Replace Cell
|
|
325
|
+
```
|
|
326
|
+
Agent: [NotebookEdit:
|
|
327
|
+
notebook_path="analysis.ipynb"
|
|
328
|
+
cell_id="abc123"
|
|
329
|
+
new_source="import pandas as pd\nimport numpy as np"
|
|
330
|
+
]
|
|
331
|
+
|
|
332
|
+
✓ Cell updated
|
|
333
|
+
Notebook: analysis.ipynb
|
|
334
|
+
Cell: abc123 (index 0)
|
|
335
|
+
Type: code
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Insert Cell
|
|
339
|
+
```
|
|
340
|
+
Agent: [NotebookEdit:
|
|
341
|
+
notebook_path="analysis.ipynb"
|
|
342
|
+
cell_id="abc123"
|
|
343
|
+
cell_type="markdown"
|
|
344
|
+
new_source="# Data Analysis\n\nThis section analyzes..."
|
|
345
|
+
edit_mode="insert"
|
|
346
|
+
]
|
|
347
|
+
|
|
348
|
+
✓ Cell inserted
|
|
349
|
+
Notebook: analysis.ipynb
|
|
350
|
+
New cell: def456 (index 1)
|
|
351
|
+
Type: markdown
|
|
352
|
+
Inserted after: abc123
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### Delete Cell
|
|
356
|
+
```
|
|
357
|
+
Agent: [NotebookEdit:
|
|
358
|
+
notebook_path="analysis.ipynb"
|
|
359
|
+
cell_id="xyz789"
|
|
360
|
+
edit_mode="delete"
|
|
361
|
+
]
|
|
362
|
+
|
|
363
|
+
✓ Cell deleted
|
|
364
|
+
Notebook: analysis.ipynb
|
|
365
|
+
Removed: xyz789 (was at index 5)
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
## Alternatives Considered
|
|
369
|
+
|
|
370
|
+
### Alternative 1: Full Notebook Rewrite
|
|
371
|
+
Use Write tool to replace entire notebook.
|
|
372
|
+
|
|
373
|
+
**Pros**: Simpler
|
|
374
|
+
**Cons**: Loses outputs, risky
|
|
375
|
+
**Decision**: Rejected - Need cell-level precision
|
|
376
|
+
|
|
377
|
+
### Alternative 2: JupyterLab Extension
|
|
378
|
+
Integrate with running JupyterLab.
|
|
379
|
+
|
|
380
|
+
**Pros**: Full notebook capabilities
|
|
381
|
+
**Cons**: Requires running server
|
|
382
|
+
**Decision**: Deferred - Start with file-based
|
|
383
|
+
|
|
384
|
+
## Security Considerations
|
|
385
|
+
|
|
386
|
+
1. **Path Validation**: Validate notebook paths
|
|
387
|
+
2. **JSON Validation**: Verify notebook structure
|
|
388
|
+
3. **Output Preservation**: Don't execute arbitrary code
|
|
389
|
+
4. **Backup Creation**: Optionally backup before edit
|
|
390
|
+
|
|
391
|
+
## Testing Strategy
|
|
392
|
+
|
|
393
|
+
1. **Unit Tests**:
|
|
394
|
+
- Cell operations
|
|
395
|
+
- ID generation
|
|
396
|
+
- Source normalization
|
|
397
|
+
|
|
398
|
+
2. **Integration Tests**:
|
|
399
|
+
- Full edit workflows
|
|
400
|
+
- Multiple operations
|
|
401
|
+
- Edge cases
|
|
402
|
+
|
|
403
|
+
## Migration Path
|
|
404
|
+
|
|
405
|
+
1. **Phase 1**: Basic replace/insert/delete
|
|
406
|
+
2. **Phase 2**: Batch operations
|
|
407
|
+
3. **Phase 3**: Output preservation options
|
|
408
|
+
4. **Phase 4**: Notebook creation
|
|
409
|
+
|
|
410
|
+
## References
|
|
411
|
+
|
|
412
|
+
- [Jupyter Notebook Format](https://nbformat.readthedocs.io/)
|
|
413
|
+
- [nbformat Python Package](https://github.com/jupyter/nbformat)
|