caudate-cli 0.1.0__py3-none-any.whl
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.
- api/__init__.py +5 -0
- api/anthropic_compat.py +1518 -0
- api/artifact_viewer.py +366 -0
- api/caudate_middleware.py +618 -0
- api/forge_bootstrapper_routes.py +377 -0
- api/forge_routes.py +630 -0
- api/forge_system_routes.py +294 -0
- api/openai_compat.py +1993 -0
- api/server.py +667 -0
- api/storyboard_page.py +677 -0
- caudate_cli-0.1.0.dist-info/METADATA +354 -0
- caudate_cli-0.1.0.dist-info/RECORD +153 -0
- caudate_cli-0.1.0.dist-info/WHEEL +5 -0
- caudate_cli-0.1.0.dist-info/entry_points.txt +2 -0
- caudate_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- caudate_cli-0.1.0.dist-info/top_level.txt +14 -0
- cognos_mcp/__init__.py +4 -0
- cognos_mcp/bridge.py +41 -0
- cognos_mcp/client.py +70 -0
- cognos_mcp/config.py +49 -0
- cognos_mcp/server.py +66 -0
- config.py +82 -0
- core/__init__.py +0 -0
- core/agent.py +468 -0
- core/agentic_loop.py +731 -0
- core/anthropic_auth.py +91 -0
- core/background.py +113 -0
- core/banner.py +134 -0
- core/bootstrap.py +292 -0
- core/citations.py +131 -0
- core/compaction.py +109 -0
- core/constitution.py +198 -0
- core/diff_viewer.py +87 -0
- core/export.py +85 -0
- core/file_refs.py +119 -0
- core/files.py +199 -0
- core/hooks.py +209 -0
- core/image.py +599 -0
- core/input.py +91 -0
- core/loop.py +238 -0
- core/memory_md.py +147 -0
- core/notifications.py +99 -0
- core/ownership.py +181 -0
- core/paste.py +81 -0
- core/permissions.py +210 -0
- core/plan_mode.py +215 -0
- core/sandbox_prompt.py +185 -0
- core/scheduler.py +195 -0
- core/schemas.py +202 -0
- core/session.py +90 -0
- core/settings.py +132 -0
- core/skills.py +398 -0
- core/slash_commands.py +977 -0
- core/statusline.py +61 -0
- core/subagent.py +300 -0
- core/thinking.py +50 -0
- core/updater.py +122 -0
- core/usage.py +109 -0
- core/worktree.py +93 -0
- execution/__init__.py +0 -0
- execution/executor.py +329 -0
- execution/plugins.py +108 -0
- execution/tools/__init__.py +0 -0
- execution/tools/agent_tool.py +107 -0
- execution/tools/agentic_tool.py +297 -0
- execution/tools/artifact_tool.py +191 -0
- execution/tools/ask_user_question_tool.py +137 -0
- execution/tools/base.py +81 -0
- execution/tools/calculator_tool.py +137 -0
- execution/tools/cognos_card_tool.py +124 -0
- execution/tools/cron_tool.py +215 -0
- execution/tools/datetime_tool.py +215 -0
- execution/tools/describe_image_tool.py +161 -0
- execution/tools/draw_tool.py +164 -0
- execution/tools/edit_image_tool.py +262 -0
- execution/tools/edit_tool.py +245 -0
- execution/tools/file_tool.py +90 -0
- execution/tools/find_anywhere_tool.py +255 -0
- execution/tools/forge_feature_tools.py +377 -0
- execution/tools/glob_tool.py +59 -0
- execution/tools/grep_tool.py +89 -0
- execution/tools/http_request_tool.py +224 -0
- execution/tools/load_skill_tool.py +104 -0
- execution/tools/longcat_avatar_tool.py +384 -0
- execution/tools/mcp_tool.py +100 -0
- execution/tools/notebook_tool.py +279 -0
- execution/tools/openapi_tool.py +440 -0
- execution/tools/plan_mode_tool.py +95 -0
- execution/tools/push_notification_tool.py +157 -0
- execution/tools/python_tool.py +61 -0
- execution/tools/respond_tool.py +40 -0
- execution/tools/sandbox_tool.py +378 -0
- execution/tools/search_tool.py +153 -0
- execution/tools/semantic_search_tool.py +106 -0
- execution/tools/shell_tool.py +283 -0
- execution/tools/speak_tool.py +134 -0
- execution/tools/storyboard_tool.py +727 -0
- execution/tools/system_info_tool.py +212 -0
- execution/tools/task_tool.py +323 -0
- execution/tools/think_tool.py +49 -0
- execution/tools/transcribe_audio_tool.py +86 -0
- execution/tools/update_memory_tool.py +92 -0
- execution/tools/web_fetch_tool.py +82 -0
- execution/tools/worktree_tool.py +174 -0
- llm/__init__.py +0 -0
- llm/fallback.py +116 -0
- llm/models.py +320 -0
- llm/provider.py +1356 -0
- llm/router.py +373 -0
- main.py +1889 -0
- memory/__init__.py +0 -0
- memory/episodic.py +99 -0
- memory/procedural.py +145 -0
- memory/semantic.py +71 -0
- memory/working.py +64 -0
- nn/__init__.py +43 -0
- nn/auto_evolve.py +245 -0
- nn/caudate.py +136 -0
- nn/config.py +141 -0
- nn/consolidator.py +81 -0
- nn/data.py +1635 -0
- nn/encoder.py +258 -0
- nn/forge_advisor.py +303 -0
- nn/format.py +235 -0
- nn/heads.py +432 -0
- nn/observer.py +994 -0
- nn/policy.py +214 -0
- nn/runtime.py +343 -0
- nn/scorer.py +175 -0
- nn/trainer.py +515 -0
- nn/vision.py +352 -0
- personality/__init__.py +23 -0
- personality/engine.py +129 -0
- personality/identity.py +144 -0
- personality/inner_voice.py +100 -0
- personality/mood.py +205 -0
- planning/__init__.py +0 -0
- planning/dev_server.py +221 -0
- planning/forge_models.py +718 -0
- planning/orchestrator.py +1363 -0
- planning/planner.py +451 -0
- planning/task_graph.py +61 -0
- reflection/__init__.py +0 -0
- reflection/meta_learner.py +156 -0
- reflection/reflector.py +127 -0
- ui/__init__.py +5 -0
- ui/display.py +88 -0
- voice/__init__.py +0 -0
- voice/conversation.py +125 -0
- voice/listener.py +111 -0
- voice/speaker.py +59 -0
- voice/stt.py +126 -0
- voice/tts.py +214 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"""NotebookEdit — surgical edits to Jupyter `.ipynb` notebook cells.
|
|
2
|
+
|
|
3
|
+
`.ipynb` files are JSON; you *can* edit them with `Edit` if you carefully
|
|
4
|
+
match the surrounding JSON, but cell `source` is stored as a list of
|
|
5
|
+
strings with embedded escapes — it's brittle and easy to corrupt. This
|
|
6
|
+
tool parses the notebook properly, performs the requested cell-level
|
|
7
|
+
operation, and writes valid notebook JSON back out.
|
|
8
|
+
|
|
9
|
+
Modes (`mode` argument):
|
|
10
|
+
- `replace` (default) — replace the entire source of one cell.
|
|
11
|
+
- `insert` — insert a new cell at `cell_index` (existing
|
|
12
|
+
cells shift down). Existing default if no
|
|
13
|
+
cell_index → append at the end.
|
|
14
|
+
- `delete` — delete the cell at `cell_index`.
|
|
15
|
+
|
|
16
|
+
Cell types: `code` (default), `markdown`, `raw`. New cells default to
|
|
17
|
+
`code` unless `cell_type` is set.
|
|
18
|
+
|
|
19
|
+
Identifies the cell by either:
|
|
20
|
+
- `cell_index` (0-based integer), or
|
|
21
|
+
- `cell_id` (notebook v4.5+ stable id like `abc123`).
|
|
22
|
+
|
|
23
|
+
Outputs (cell `outputs` and `execution_count`) are reset on edits to
|
|
24
|
+
`code` cells so the file looks "fresh" after editing — same convention
|
|
25
|
+
Claude Code's NotebookEdit uses.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import json
|
|
31
|
+
import logging
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
from typing import Any
|
|
34
|
+
|
|
35
|
+
from core.schemas import ToolResult
|
|
36
|
+
from execution.tools.base import BaseTool
|
|
37
|
+
|
|
38
|
+
logger = logging.getLogger(__name__)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
_VALID_MODES = {"replace", "insert", "delete"}
|
|
42
|
+
_VALID_CELL_TYPES = {"code", "markdown", "raw"}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _normalize_source(text: str) -> list[str]:
|
|
46
|
+
"""Notebook spec wants `source` as a list of lines (each line ends
|
|
47
|
+
in `\\n` except possibly the last). Most notebooks store it that
|
|
48
|
+
way; some store a single string. We always emit the list form."""
|
|
49
|
+
if not text:
|
|
50
|
+
return []
|
|
51
|
+
# Preserve final newline behavior of the original text.
|
|
52
|
+
lines = text.splitlines(keepends=True)
|
|
53
|
+
return lines
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class NotebookEditTool(BaseTool):
|
|
57
|
+
mutates = True
|
|
58
|
+
name = "NotebookEdit"
|
|
59
|
+
description = (
|
|
60
|
+
"Edit Jupyter `.ipynb` notebook cells in place. Modes: "
|
|
61
|
+
"`replace` an existing cell's source, `insert` a new cell, or "
|
|
62
|
+
"`delete` a cell. Identify the target by `cell_index` (0-based) "
|
|
63
|
+
"or `cell_id` (v4.5+). New cells default to `code`; pass "
|
|
64
|
+
"`cell_type` for `markdown` / `raw`. Code-cell outputs are "
|
|
65
|
+
"reset on edit. Use this instead of the generic `Edit` tool — "
|
|
66
|
+
"the JSON-with-escaped-strings shape is too brittle for "
|
|
67
|
+
"string replacement."
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def input_schema(self) -> dict[str, Any]:
|
|
72
|
+
return {
|
|
73
|
+
"type": "object",
|
|
74
|
+
"properties": {
|
|
75
|
+
"path": {
|
|
76
|
+
"type": "string",
|
|
77
|
+
"description": "Absolute or relative path to the .ipynb file.",
|
|
78
|
+
},
|
|
79
|
+
"mode": {
|
|
80
|
+
"type": "string",
|
|
81
|
+
"enum": list(_VALID_MODES),
|
|
82
|
+
"description": "replace (default) | insert | delete",
|
|
83
|
+
"default": "replace",
|
|
84
|
+
},
|
|
85
|
+
"cell_index": {
|
|
86
|
+
"type": "integer",
|
|
87
|
+
"description": (
|
|
88
|
+
"0-based cell index. For `insert`, this is "
|
|
89
|
+
"where the new cell goes; omit to append at the "
|
|
90
|
+
"end. For `replace`/`delete`, this identifies "
|
|
91
|
+
"the target cell (or use `cell_id`)."
|
|
92
|
+
),
|
|
93
|
+
},
|
|
94
|
+
"cell_id": {
|
|
95
|
+
"type": "string",
|
|
96
|
+
"description": (
|
|
97
|
+
"Stable cell ID (notebook v4.5+). Alternative "
|
|
98
|
+
"to `cell_index` for `replace`/`delete`."
|
|
99
|
+
),
|
|
100
|
+
},
|
|
101
|
+
"source": {
|
|
102
|
+
"type": "string",
|
|
103
|
+
"description": (
|
|
104
|
+
"New cell content (required for `replace` and "
|
|
105
|
+
"`insert`). Pass the raw text — newlines are "
|
|
106
|
+
"handled automatically."
|
|
107
|
+
),
|
|
108
|
+
},
|
|
109
|
+
"cell_type": {
|
|
110
|
+
"type": "string",
|
|
111
|
+
"enum": list(_VALID_CELL_TYPES),
|
|
112
|
+
"description": "code (default) | markdown | raw. Used for `insert`.",
|
|
113
|
+
"default": "code",
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
"required": ["path"],
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async def execute(self, **kwargs: Any) -> ToolResult:
|
|
120
|
+
path_str = (kwargs.get("path") or "").strip()
|
|
121
|
+
mode = (kwargs.get("mode") or "replace").lower()
|
|
122
|
+
cell_index = kwargs.get("cell_index")
|
|
123
|
+
cell_id = kwargs.get("cell_id")
|
|
124
|
+
source = kwargs.get("source")
|
|
125
|
+
cell_type = (kwargs.get("cell_type") or "code").lower()
|
|
126
|
+
|
|
127
|
+
# ---- validation ----
|
|
128
|
+
if not path_str:
|
|
129
|
+
return ToolResult(
|
|
130
|
+
tool_name=self.name, status="error",
|
|
131
|
+
error="`path` is required",
|
|
132
|
+
)
|
|
133
|
+
if mode not in _VALID_MODES:
|
|
134
|
+
return ToolResult(
|
|
135
|
+
tool_name=self.name, status="error",
|
|
136
|
+
error=f"unknown mode {mode!r}; use one of {sorted(_VALID_MODES)}",
|
|
137
|
+
)
|
|
138
|
+
if cell_type not in _VALID_CELL_TYPES:
|
|
139
|
+
return ToolResult(
|
|
140
|
+
tool_name=self.name, status="error",
|
|
141
|
+
error=f"unknown cell_type {cell_type!r}; use one of {sorted(_VALID_CELL_TYPES)}",
|
|
142
|
+
)
|
|
143
|
+
if mode in ("replace", "insert") and source is None:
|
|
144
|
+
return ToolResult(
|
|
145
|
+
tool_name=self.name, status="error",
|
|
146
|
+
error=f"`source` is required for mode={mode!r}",
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
path = Path(path_str).expanduser().resolve()
|
|
150
|
+
if not path.exists() or not path.is_file():
|
|
151
|
+
return ToolResult(
|
|
152
|
+
tool_name=self.name, status="error",
|
|
153
|
+
error=f"notebook not found: {path}",
|
|
154
|
+
)
|
|
155
|
+
if path.suffix != ".ipynb":
|
|
156
|
+
return ToolResult(
|
|
157
|
+
tool_name=self.name, status="error",
|
|
158
|
+
error=f"not a .ipynb file: {path.suffix}",
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
# ---- load ----
|
|
162
|
+
try:
|
|
163
|
+
nb = json.loads(path.read_text(encoding="utf-8"))
|
|
164
|
+
except (OSError, json.JSONDecodeError) as e:
|
|
165
|
+
return ToolResult(
|
|
166
|
+
tool_name=self.name, status="error",
|
|
167
|
+
error=f"failed to parse notebook: {e}",
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
cells = nb.get("cells")
|
|
171
|
+
if not isinstance(cells, list):
|
|
172
|
+
return ToolResult(
|
|
173
|
+
tool_name=self.name, status="error",
|
|
174
|
+
error="notebook has no `cells` array — corrupted file?",
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# ---- resolve target index ----
|
|
178
|
+
target_idx: int | None = None
|
|
179
|
+
if cell_id is not None and cell_id != "":
|
|
180
|
+
for i, c in enumerate(cells):
|
|
181
|
+
if isinstance(c, dict) and c.get("id") == cell_id:
|
|
182
|
+
target_idx = i
|
|
183
|
+
break
|
|
184
|
+
if target_idx is None and mode != "insert":
|
|
185
|
+
return ToolResult(
|
|
186
|
+
tool_name=self.name, status="error",
|
|
187
|
+
error=f"no cell with id {cell_id!r} (notebook has {len(cells)} cells)",
|
|
188
|
+
)
|
|
189
|
+
elif cell_index is not None:
|
|
190
|
+
try:
|
|
191
|
+
target_idx = int(cell_index)
|
|
192
|
+
except (TypeError, ValueError):
|
|
193
|
+
return ToolResult(
|
|
194
|
+
tool_name=self.name, status="error",
|
|
195
|
+
error=f"cell_index must be an integer, got {cell_index!r}",
|
|
196
|
+
)
|
|
197
|
+
if mode != "insert":
|
|
198
|
+
if not (0 <= target_idx < len(cells)):
|
|
199
|
+
return ToolResult(
|
|
200
|
+
tool_name=self.name, status="error",
|
|
201
|
+
error=(f"cell_index {target_idx} out of range "
|
|
202
|
+
f"(notebook has {len(cells)} cells)"),
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
# ---- apply ----
|
|
206
|
+
action_summary = ""
|
|
207
|
+
if mode == "replace":
|
|
208
|
+
if target_idx is None:
|
|
209
|
+
return ToolResult(
|
|
210
|
+
tool_name=self.name, status="error",
|
|
211
|
+
error="replace mode needs `cell_index` or `cell_id`",
|
|
212
|
+
)
|
|
213
|
+
cell = cells[target_idx]
|
|
214
|
+
if not isinstance(cell, dict):
|
|
215
|
+
return ToolResult(
|
|
216
|
+
tool_name=self.name, status="error",
|
|
217
|
+
error=f"cell at index {target_idx} is not an object",
|
|
218
|
+
)
|
|
219
|
+
cell["source"] = _normalize_source(source)
|
|
220
|
+
# Reset transient state on code cells so the file looks
|
|
221
|
+
# fresh after edit. Mirrors Claude Code's NotebookEdit.
|
|
222
|
+
if cell.get("cell_type") == "code":
|
|
223
|
+
cell["outputs"] = []
|
|
224
|
+
cell["execution_count"] = None
|
|
225
|
+
action_summary = (
|
|
226
|
+
f"replaced cell {target_idx} "
|
|
227
|
+
f"({cell.get('cell_type', '?')}, {len(source)} chars)"
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
elif mode == "delete":
|
|
231
|
+
if target_idx is None:
|
|
232
|
+
return ToolResult(
|
|
233
|
+
tool_name=self.name, status="error",
|
|
234
|
+
error="delete mode needs `cell_index` or `cell_id`",
|
|
235
|
+
)
|
|
236
|
+
removed = cells.pop(target_idx)
|
|
237
|
+
action_summary = (
|
|
238
|
+
f"deleted cell {target_idx} "
|
|
239
|
+
f"({removed.get('cell_type', '?')})"
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
elif mode == "insert":
|
|
243
|
+
new_cell: dict[str, Any] = {
|
|
244
|
+
"cell_type": cell_type,
|
|
245
|
+
"metadata": {},
|
|
246
|
+
"source": _normalize_source(source),
|
|
247
|
+
}
|
|
248
|
+
if cell_type == "code":
|
|
249
|
+
new_cell["outputs"] = []
|
|
250
|
+
new_cell["execution_count"] = None
|
|
251
|
+
insert_at = target_idx if target_idx is not None else len(cells)
|
|
252
|
+
insert_at = max(0, min(insert_at, len(cells)))
|
|
253
|
+
cells.insert(insert_at, new_cell)
|
|
254
|
+
action_summary = (
|
|
255
|
+
f"inserted {cell_type} cell at index {insert_at} "
|
|
256
|
+
f"({len(source)} chars)"
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
# ---- write back ----
|
|
260
|
+
try:
|
|
261
|
+
path.write_text(
|
|
262
|
+
json.dumps(nb, indent=1, ensure_ascii=False) + "\n",
|
|
263
|
+
encoding="utf-8",
|
|
264
|
+
)
|
|
265
|
+
except OSError as e:
|
|
266
|
+
return ToolResult(
|
|
267
|
+
tool_name=self.name, status="error",
|
|
268
|
+
error=f"failed to write notebook: {e}",
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
return ToolResult(
|
|
272
|
+
tool_name=self.name, status="success",
|
|
273
|
+
output=f"{path}: {action_summary} (notebook now has {len(cells)} cells)",
|
|
274
|
+
metadata={
|
|
275
|
+
"path": str(path),
|
|
276
|
+
"mode": mode,
|
|
277
|
+
"cell_count": len(cells),
|
|
278
|
+
},
|
|
279
|
+
)
|