one-code-extension 0.1.3 → 0.3.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/LICENSE +20 -0
- package/README.md +200 -172
- package/agents/explore.md +9 -6
- package/agents/general-purpose.md +1 -1
- package/agents/plan.md +6 -4
- package/docs/guide/README.md +52 -0
- package/docs/guide/bring-your-claude-code-setup.md +126 -0
- package/docs/guide/configuration.md +171 -0
- package/docs/guide/differences-from-claude-code.md +150 -0
- package/docs/guide/doctor.md +86 -0
- package/docs/guide/hooks.md +109 -0
- package/docs/guide/installation.md +124 -0
- package/docs/guide/permissions-modes-and-auto-mode.md +450 -0
- package/docs/guide/providers-and-models.md +194 -0
- package/docs/guide/reference.md +224 -0
- package/docs/guide/sessions-and-context.md +190 -0
- package/docs/guide/skills-plugins-and-mcp.md +176 -0
- package/docs/guide/subagents-and-workflows.md +193 -0
- package/docs/guide/tasks-and-background-work.md +111 -0
- package/docs/guide/terminal-interface.md +155 -0
- package/docs/guide/tools.md +175 -0
- package/docs/guide/troubleshooting.md +175 -0
- package/extensions/ask-user/index.ts +65 -71
- package/extensions/ask-user/questions.ts +15 -28
- package/extensions/ask-user/widget.ts +533 -0
- package/extensions/auto-mode/actions.ts +9 -26
- package/extensions/auto-mode/classifier-prompt.ts +127 -0
- package/extensions/auto-mode/classifier.ts +305 -227
- package/extensions/auto-mode/config.ts +279 -77
- package/extensions/auto-mode/defaults.ts +17 -60
- package/extensions/auto-mode/instructions.ts +2 -1
- package/extensions/auto-mode/model-picker.ts +53 -31
- package/extensions/auto-mode/model-select.ts +153 -82
- package/extensions/auto-mode/paths.ts +13 -1
- package/extensions/auto-mode/pause.ts +8 -0
- package/extensions/auto-mode/prompt.ts +205 -238
- package/extensions/auto-mode/recoverability.ts +229 -0
- package/extensions/auto-mode/rules.ts +92 -75
- package/extensions/auto-mode/safety-floor.ts +43 -12
- package/extensions/auto-mode/sensitive.ts +11 -0
- package/extensions/auto-mode/setup-run.ts +245 -0
- package/extensions/auto-mode/setup.ts +235 -0
- package/extensions/auto-mode/shell-analysis.ts +332 -26
- package/extensions/auto-mode/transcript.ts +102 -0
- package/extensions/background/index.ts +256 -43
- package/extensions/background/monitor-batch.ts +58 -0
- package/extensions/background/registry.ts +9 -0
- package/extensions/background/wakeup.ts +45 -0
- package/extensions/bash/background.ts +47 -22
- package/extensions/bash/guards.ts +229 -0
- package/extensions/bash/index.ts +81 -19
- package/extensions/branding/index.ts +44 -18
- package/extensions/branding/startup.ts +23 -8
- package/extensions/claude-compat/index.ts +17 -2
- package/extensions/claude-context/index.ts +41 -12
- package/extensions/clear/index.ts +27 -0
- package/extensions/compaction/fit.ts +121 -0
- package/extensions/compaction/index.ts +176 -71
- package/extensions/compaction/prompt.ts +21 -1
- package/extensions/context-budget/budget.ts +61 -0
- package/extensions/context-budget/index.ts +66 -0
- package/extensions/context-management/index.ts +40 -16
- package/extensions/doctor/build.ts +226 -0
- package/extensions/doctor/cli.ts +99 -0
- package/extensions/doctor/compat.ts +532 -0
- package/extensions/doctor/dependencies.ts +186 -0
- package/extensions/doctor/fix-prompt.ts +125 -0
- package/extensions/doctor/index.ts +308 -0
- package/extensions/doctor/models.ts +231 -0
- package/extensions/doctor/presets.ts +208 -0
- package/extensions/doctor/report.ts +189 -0
- package/extensions/doctor/update-lookup.ts +46 -0
- package/extensions/doctor/viewer.ts +124 -0
- package/extensions/effort/index.ts +41 -4
- package/extensions/effort/slider.ts +33 -2
- package/extensions/file-tracker/index.ts +143 -20
- package/extensions/file-tracker/replay.ts +46 -0
- package/extensions/file-tracker/tracker.ts +51 -4
- package/extensions/footer/footer-line.ts +207 -0
- package/extensions/footer/index.ts +152 -0
- package/extensions/footer/pr.ts +53 -0
- package/extensions/hooks/executor.ts +57 -20
- package/extensions/hooks/formatter-notice.ts +76 -0
- package/extensions/hooks/index.ts +314 -73
- package/extensions/hooks/matcher.ts +35 -1
- package/extensions/hooks/plugin-hooks.ts +3 -3
- package/extensions/hooks/protocol.ts +58 -0
- package/extensions/hooks/subagent-bridge.ts +77 -0
- package/extensions/hooks/trust.ts +15 -4
- package/extensions/init/index.ts +26 -0
- package/extensions/init/prompt.ts +40 -0
- package/extensions/interrupted/index.ts +35 -0
- package/extensions/interrupted/line.ts +9 -0
- package/extensions/lib/abort.ts +15 -0
- package/extensions/lib/agent-loader.ts +183 -0
- package/extensions/lib/anthropic-payload.ts +15 -0
- package/extensions/lib/atomic-write.ts +56 -0
- package/extensions/lib/bridge-watch.ts +20 -0
- package/extensions/lib/cache-retention.ts +60 -0
- package/extensions/lib/capability-index.ts +466 -0
- package/extensions/lib/claude-context.ts +318 -27
- package/extensions/lib/claude-settings.ts +64 -0
- package/extensions/lib/consent-preview.ts +30 -0
- package/extensions/lib/deferred.ts +144 -13
- package/extensions/lib/favorites.ts +43 -0
- package/extensions/lib/fetch-timeout.ts +15 -0
- package/extensions/lib/frontmatter.ts +31 -0
- package/extensions/lib/git.ts +44 -2
- package/extensions/lib/hook-gate.ts +90 -0
- package/extensions/lib/interrupt.ts +94 -0
- package/extensions/lib/mcp-overrides.ts +61 -0
- package/extensions/lib/mcp-share.ts +25 -0
- package/extensions/lib/mcp-status.ts +34 -0
- package/extensions/lib/memory.ts +41 -6
- package/extensions/lib/model-facts.generated.json +2480 -0
- package/extensions/lib/model-facts.ts +162 -0
- package/extensions/lib/model-policy.ts +145 -189
- package/extensions/lib/model-tier.ts +480 -64
- package/extensions/lib/model-unusable.ts +37 -0
- package/extensions/lib/notifications.ts +320 -1
- package/extensions/lib/one-code-settings.ts +63 -0
- package/extensions/lib/original-command.ts +71 -0
- package/extensions/lib/package-version.ts +31 -0
- package/extensions/lib/paths.ts +39 -4
- package/extensions/lib/permission-gate.ts +192 -0
- package/extensions/lib/persisted-output.ts +18 -0
- package/extensions/lib/pi-ai-estimate.ts +160 -0
- package/extensions/lib/pi-version.ts +1 -1
- package/extensions/lib/plan-mode-channels.ts +11 -0
- package/extensions/lib/plugin-overrides.ts +24 -0
- package/extensions/lib/plugin-root.ts +44 -0
- package/extensions/lib/plugins.ts +233 -54
- package/extensions/lib/prefix-warm-gate.ts +157 -0
- package/extensions/lib/process-tree.ts +56 -0
- package/extensions/lib/reminders.ts +321 -68
- package/extensions/lib/scratchpad.ts +11 -8
- package/extensions/lib/session-lifecycle.ts +28 -0
- package/extensions/lib/settings-channels.ts +14 -0
- package/extensions/lib/shell-tasks.ts +54 -0
- package/extensions/lib/skill-overrides.ts +86 -0
- package/extensions/lib/skill-scan.ts +110 -0
- package/extensions/lib/text-width.ts +267 -0
- package/extensions/lib/tui-render.ts +281 -54
- package/extensions/lib/usage-bus.ts +75 -0
- package/extensions/lib/usage-tracker.ts +67 -0
- package/extensions/lib/worktree-isolation.ts +170 -0
- package/extensions/lsp/client.ts +69 -12
- package/extensions/lsp/format.ts +12 -2
- package/extensions/lsp/index.ts +273 -84
- package/extensions/lsp/install-hints.ts +43 -0
- package/extensions/lsp/keep-alive.ts +19 -0
- package/extensions/lsp/plugin-servers.ts +331 -0
- package/extensions/lsp/watcher.ts +157 -0
- package/extensions/mcp/announce.ts +99 -0
- package/extensions/mcp/client.ts +181 -22
- package/extensions/mcp/config.ts +55 -24
- package/extensions/mcp/index.ts +586 -86
- package/extensions/mcp/oauth/browser.ts +26 -0
- package/extensions/mcp/oauth/callback.ts +104 -0
- package/extensions/mcp/oauth/flow.ts +68 -0
- package/extensions/mcp/oauth/provider.ts +147 -0
- package/extensions/mcp/oauth/store.ts +86 -0
- package/extensions/mcp/panel/keys.ts +34 -0
- package/extensions/mcp/panel/model.ts +62 -0
- package/extensions/mcp/panel/render.ts +141 -0
- package/extensions/mcp/panel/state.ts +108 -0
- package/extensions/mcp/schema.ts +83 -6
- package/extensions/mcp/trust.ts +340 -0
- package/extensions/memory/entries.ts +163 -0
- package/extensions/memory/index.ts +119 -2
- package/extensions/memory/open-external.ts +97 -0
- package/extensions/memory/panel.ts +124 -0
- package/extensions/model-default/index.ts +74 -0
- package/extensions/notebook/index.ts +36 -18
- package/extensions/notebook/notebook.ts +110 -17
- package/extensions/permissions/index.ts +1012 -247
- package/extensions/permissions/matcher.ts +488 -57
- package/extensions/permissions/modes.ts +55 -7
- package/extensions/permissions/project-trust.ts +81 -0
- package/extensions/permissions/protected-paths.ts +16 -3
- package/extensions/permissions/rule-prose.ts +79 -0
- package/extensions/permissions/session-grant.ts +91 -0
- package/extensions/permissions/settings.ts +112 -33
- package/extensions/permissions/subagent-gate.ts +57 -0
- package/extensions/plan-mode/index.ts +143 -33
- package/extensions/plan-mode/reminder.ts +16 -19
- package/extensions/plan-mode/viewer.ts +40 -31
- package/extensions/plugins/counts.ts +68 -0
- package/extensions/plugins/index.ts +496 -32
- package/extensions/plugins/install/install.ts +167 -0
- package/extensions/plugins/install/paths.ts +23 -0
- package/extensions/plugins/install/registry.ts +72 -0
- package/extensions/plugins/marketplace/git.ts +103 -0
- package/extensions/plugins/marketplace/official.ts +36 -0
- package/extensions/plugins/marketplace/parse.ts +94 -0
- package/extensions/plugins/marketplace/registry.ts +60 -0
- package/extensions/plugins/marketplace/sync.ts +146 -0
- package/extensions/plugins/marketplace/types.ts +128 -0
- package/extensions/plugins/panel/keys.ts +52 -0
- package/extensions/plugins/panel/render.ts +315 -0
- package/extensions/plugins/panel/rows.ts +230 -0
- package/extensions/plugins/panel/state.ts +333 -0
- package/extensions/recap/index.ts +215 -0
- package/extensions/recap/prompt.ts +44 -0
- package/extensions/recap/scheduler.ts +87 -0
- package/extensions/search-tools/index.ts +0 -0
- package/extensions/search-tools/tools.ts +7 -5
- package/extensions/skill/index.ts +360 -34
- package/extensions/skill/invoke.ts +185 -0
- package/extensions/skill/listing.ts +21 -0
- package/extensions/skill/panel/keys.ts +41 -0
- package/extensions/skill/panel/render.ts +91 -0
- package/extensions/skill/panel/state.ts +115 -0
- package/extensions/spinner/index.ts +110 -0
- package/extensions/spinner/line.ts +51 -0
- package/extensions/spinner/verbs.ts +201 -0
- package/extensions/subagents/agents.ts +88 -44
- package/extensions/subagents/default-model.ts +62 -32
- package/extensions/subagents/delegation-steer.ts +23 -0
- package/extensions/subagents/hand-back-review.ts +67 -0
- package/extensions/subagents/index.ts +1545 -346
- package/extensions/subagents/live-runs.ts +293 -0
- package/extensions/subagents/model-select.ts +252 -82
- package/extensions/subagents/outcome.ts +90 -0
- package/extensions/subagents/panel-keys.ts +41 -0
- package/extensions/subagents/panel-render.ts +357 -0
- package/extensions/subagents/panel-widget.ts +330 -0
- package/extensions/subagents/pending-claim.ts +45 -0
- package/extensions/subagents/prose.ts +105 -0
- package/extensions/subagents/runner.ts +674 -0
- package/extensions/subagents/runs.ts +68 -1
- package/extensions/subagents/send-to-main-tool.ts +55 -0
- package/extensions/subagents/session-turns.ts +138 -0
- package/extensions/subagents/shell-panel.ts +198 -0
- package/extensions/subagents/usage.ts +16 -4
- package/extensions/subagents/worktree.ts +13 -0
- package/extensions/system-prompt/environment.ts +5 -3
- package/extensions/system-prompt/git-status.ts +93 -0
- package/extensions/system-prompt/index.ts +23 -1
- package/extensions/system-prompt/template.ts +34 -4
- package/extensions/system-prompt/tiers/common.ts +31 -6
- package/extensions/system-prompt/tiers/low.ts +13 -3
- package/extensions/system-prompt/tiers/mid.ts +21 -8
- package/extensions/system-reminder/index.ts +53 -9
- package/extensions/tasks/index.ts +65 -16
- package/extensions/tasks/store.ts +76 -3
- package/extensions/tool-search/announce.ts +42 -0
- package/extensions/tool-search/index.ts +136 -13
- package/extensions/turn-duration/index.ts +60 -0
- package/extensions/turn-duration/line.ts +22 -0
- package/extensions/turn-duration/span.ts +41 -0
- package/extensions/turn-duration/verbs.ts +24 -0
- package/extensions/web/backends.ts +421 -0
- package/extensions/web/index.ts +178 -21
- package/extensions/web-fetch/index.ts +74 -18
- package/extensions/web-fetch/summarize.ts +1 -1
- package/extensions/workflow/agent-session.ts +182 -112
- package/extensions/workflow/globals.ts +129 -106
- package/extensions/workflow/index.ts +343 -28
- package/extensions/workflow/records.ts +118 -0
- package/extensions/workflow/run-manager.ts +80 -31
- package/extensions/workflow/saved-workflows.ts +2 -1
- package/extensions/workflow/script-worker.mjs +110 -0
- package/extensions/workflow/types.ts +74 -8
- package/extensions/workflow/viewer.ts +562 -0
- package/extensions/workflow/vm-runtime.ts +118 -22
- package/extensions/workflow/widget.ts +126 -18
- package/extensions/worktree/guards.ts +240 -0
- package/extensions/worktree/index.ts +30 -5
- package/extensions/worktree/rewrite.ts +32 -16
- package/package.json +20 -10
- package/skills/code-review/SKILL.md +165 -0
- package/skills/fewer-permission-prompts/SKILL.md +61 -0
- package/skills/security-review/SKILL.md +178 -0
- package/skills/simplify/SKILL.md +70 -0
- package/themes/{one-code-light.json → onecode-light.json} +6 -1
- package/themes/{one-code.json → onecode.json} +6 -1
- package/types/app.d.ts +8 -1
- package/types/community-packages.d.ts +45 -2
- package/docs/decisions/auto-mode.md +0 -526
- package/docs/decisions/branding.md +0 -127
- package/docs/decisions/code-review-remediation.md +0 -106
- package/docs/decisions/compaction.md +0 -140
- package/docs/decisions/distribution.md +0 -168
- package/docs/decisions/hooks.md +0 -64
- package/docs/decisions/lsp.md +0 -58
- package/docs/decisions/mcp.md +0 -104
- package/docs/decisions/memory-state.md +0 -170
- package/docs/decisions/model-policy.md +0 -126
- package/docs/decisions/modes.md +0 -178
- package/docs/decisions/skills-plugins.md +0 -56
- package/docs/decisions/subagents-workflows.md +0 -225
- package/docs/decisions/system-prompt.md +0 -98
- package/docs/decisions/tools.md +0 -407
- package/docs/decisions/tui.md +0 -202
- package/docs/decisions.md +0 -148
- package/extensions/subagents/child.ts +0 -414
- package/extensions/subagents/rpc-turns.ts +0 -123
- package/extensions/todo/index.ts +0 -133
- package/extensions/workflow/permission-gate.ts +0 -71
|
@@ -12,7 +12,7 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
12
12
|
import { Type } from "typebox";
|
|
13
13
|
import { DEFER_CHANNEL } from "../lib/deferred.ts";
|
|
14
14
|
import { ccToolRenderers } from "../lib/tui-render.ts";
|
|
15
|
-
import { applyEdit, type EditMode, parseNotebook } from "./notebook.ts";
|
|
15
|
+
import { applyEdit, type EditMode, type Notebook, notebookErrorHint, parseNotebook } from "./notebook.ts";
|
|
16
16
|
|
|
17
17
|
export default function notebookExtension(pi: ExtensionAPI) {
|
|
18
18
|
pi.registerTool({
|
|
@@ -20,52 +20,70 @@ export default function notebookExtension(pi: ExtensionAPI) {
|
|
|
20
20
|
label: "Notebook Edit",
|
|
21
21
|
...ccToolRenderers("Notebook Edit"),
|
|
22
22
|
description:
|
|
23
|
-
"Edit a Jupyter notebook (.ipynb): replace a cell's source, insert a new cell, or delete a cell. Cells are addressed by their `id
|
|
23
|
+
"Edit a Jupyter notebook (.ipynb): replace a cell's source, insert or append a new cell, or delete a cell. Read the notebook first — like the other file tools, this one refuses to edit a file you have not read. Cells are addressed by their `id` or, for a notebook whose cells have none, by position — `cell-0` is the first cell. Use edit_mode `append` to add a cell at the end without naming one. Editing a code cell clears its outputs.",
|
|
24
24
|
parameters: Type.Object({
|
|
25
|
-
path: Type.String({ description: "Absolute or workspace-relative path to the .ipynb file" }),
|
|
26
|
-
|
|
25
|
+
path: Type.Optional(Type.String({ description: "Absolute or workspace-relative path to the .ipynb file" })),
|
|
26
|
+
// Claude Code's NotebookEdit spells the path `notebook_path` and defaults
|
|
27
|
+
// edit_mode to `replace`; a CC-trained model's `{notebook_path, cell_id,
|
|
28
|
+
// new_source}` used to fail schema validation twice over
|
|
29
|
+
// (TOOL-FIDELITY-REVIEW-2026-09-07 M2). Accept both.
|
|
30
|
+
notebook_path: Type.Optional(Type.String({ description: "Alias of `path` (Claude Code's name for it)." })),
|
|
31
|
+
edit_mode: Type.Optional(StringEnum(["replace", "insert", "append", "delete"] as const, { description: "The type of edit to make. Defaults to replace." })),
|
|
27
32
|
cell_id: Type.Optional(
|
|
28
33
|
Type.String({
|
|
29
34
|
description:
|
|
30
|
-
"Target cell id
|
|
35
|
+
"Target cell id, either the cell's own id or its position as `cell-0`, `cell-1`, … Required for replace and delete; for insert the new cell goes after it (omit to insert at the top); ignored for append.",
|
|
31
36
|
}),
|
|
32
37
|
),
|
|
33
38
|
new_source: Type.Optional(Type.String({ description: "New cell source. Required unless deleting." })),
|
|
34
39
|
cell_type: Type.Optional(
|
|
35
40
|
StringEnum(["code", "markdown"] as const, {
|
|
36
|
-
description: "Cell type. Required when inserting; when replacing, changes the cell's type.",
|
|
41
|
+
description: "Cell type. Required when inserting or appending; when replacing, changes the cell's type.",
|
|
37
42
|
}),
|
|
38
43
|
),
|
|
39
44
|
}),
|
|
40
45
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
41
|
-
const
|
|
46
|
+
const rawPath = params.path ?? params.notebook_path;
|
|
47
|
+
if (!rawPath) {
|
|
48
|
+
return {
|
|
49
|
+
content: [{ type: "text", text: "`path` (or Claude Code's `notebook_path`) is required: the .ipynb file to edit." }],
|
|
50
|
+
details: {},
|
|
51
|
+
isError: true,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const editMode = (params.edit_mode ?? "replace") as EditMode;
|
|
55
|
+
const path = isAbsolute(rawPath) ? rawPath : resolve(ctx.cwd, rawPath);
|
|
56
|
+
// Held outside the try so a failure raised AFTER the parse can name the
|
|
57
|
+
// ids the notebook actually has — a bare "cell_id is required" left both
|
|
58
|
+
// weak tiers patching ids in by hand with bash (review M4).
|
|
59
|
+
let parsed: Notebook | undefined;
|
|
42
60
|
try {
|
|
43
|
-
const notebook = parseNotebook(readFileSync(path, "utf-8"));
|
|
61
|
+
const notebook = (parsed = parseNotebook(readFileSync(path, "utf-8")));
|
|
44
62
|
const { notebook: updated, summary } = applyEdit(
|
|
45
63
|
notebook,
|
|
46
64
|
{
|
|
47
65
|
cellId: params.cell_id,
|
|
48
66
|
newSource: params.new_source,
|
|
49
67
|
cellType: params.cell_type as "code" | "markdown" | undefined,
|
|
50
|
-
editMode
|
|
68
|
+
editMode,
|
|
51
69
|
},
|
|
52
70
|
() => randomUUID().slice(0, 8),
|
|
53
71
|
);
|
|
54
72
|
writeFileSync(path, `${JSON.stringify(updated, null, 1)}\n`);
|
|
55
73
|
return {
|
|
56
|
-
content: [{ type: "text", text: `${summary} in ${
|
|
74
|
+
content: [{ type: "text", text: `${summary} in ${rawPath} (${updated.cells.length} cells).` }],
|
|
57
75
|
details: { path, cellCount: updated.cells.length },
|
|
58
76
|
};
|
|
59
77
|
} catch (error) {
|
|
60
|
-
const
|
|
61
|
-
const hint =
|
|
62
|
-
code
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
78
|
+
const message = (error as Error).message;
|
|
79
|
+
const hint = notebookErrorHint({
|
|
80
|
+
code: (error as NodeJS.ErrnoException).code,
|
|
81
|
+
syntax: error instanceof SyntaxError,
|
|
82
|
+
message,
|
|
83
|
+
parsed,
|
|
84
|
+
});
|
|
67
85
|
return {
|
|
68
|
-
content: [{ type: "text", text: `Notebook edit failed: ${
|
|
86
|
+
content: [{ type: "text", text: `Notebook edit failed: ${message}${hint}` }],
|
|
69
87
|
details: {},
|
|
70
88
|
isError: true,
|
|
71
89
|
};
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Jupyter notebook editing (pure) — Claude Code's NotebookEdit semantics.
|
|
3
|
+
*
|
|
4
|
+
* Cells are addressed by `id`, but nbformat only made `id` mandatory in 4.5:
|
|
5
|
+
* a 4.4-era notebook has none, and a model handed one had no id to pass and no
|
|
6
|
+
* way to append. Both a cheap-tier and a tiny-tier model fell back to bash plus
|
|
7
|
+
* `nbformat`/`json` to patch ids in — the exact route the prompt had told them
|
|
8
|
+
* not to take — 6/6 runs (WEAK-MODEL-REVIEW-2026-09-06 M4). So cells are also
|
|
9
|
+
* addressable by POSITION, as Claude Code's Read renders them: `cell-0`,
|
|
10
|
+
* `cell-1`, … Positional ids are resolved against the notebook as it is when
|
|
11
|
+
* the call arrives, so they shift after an insert or a delete — which is why
|
|
12
|
+
* every failure enumerates the notebook's current cells.
|
|
3
13
|
*/
|
|
4
14
|
|
|
5
15
|
export interface NotebookCell {
|
|
@@ -16,7 +26,7 @@ export interface Notebook {
|
|
|
16
26
|
[key: string]: unknown;
|
|
17
27
|
}
|
|
18
28
|
|
|
19
|
-
export type EditMode = "replace" | "insert" | "delete";
|
|
29
|
+
export type EditMode = "replace" | "insert" | "append" | "delete";
|
|
20
30
|
|
|
21
31
|
export interface EditRequest {
|
|
22
32
|
cellId?: string;
|
|
@@ -37,12 +47,69 @@ export function toSourceLines(source: string): string[] {
|
|
|
37
47
|
return lines.map((line, i) => (i === lines.length - 1 ? line : `${line}\n`)).filter((l, i) => l !== "" || i === 0);
|
|
38
48
|
}
|
|
39
49
|
|
|
50
|
+
/** The positional id of a cell, Claude Code's spelling. */
|
|
51
|
+
export function positionalId(index: number): string {
|
|
52
|
+
return `cell-${index}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** How a cell should be named back to the model: its own id, else its position. */
|
|
56
|
+
export function cellLabel(cell: NotebookCell, index: number): string {
|
|
57
|
+
return cell.id ?? positionalId(index);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** `aaa (markdown), cell-1 (code)` — every id the notebook currently answers to. */
|
|
61
|
+
export function describeCells(notebook: Notebook): string {
|
|
62
|
+
if (notebook.cells.length === 0) return "(none — the notebook has no cells)";
|
|
63
|
+
return notebook.cells.map((cell, index) => `${cellLabel(cell, index)} (${cell.cell_type})`).join(", ");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Trailing hint appended to a notebook_edit failure message. A missing file or
|
|
68
|
+
* malformed JSON gets a targeted pointer; a cell-addressing failure (a missing or
|
|
69
|
+
* wrong `cell_id`) gets the list of ids the notebook actually has, so a weak tier
|
|
70
|
+
* picks a real one instead of inventing it by hand (review M4). A missing
|
|
71
|
+
* `new_source`/`cell_type` is not about a cell, so it gets no id listing — that
|
|
72
|
+
* would misdirect (code-review F3). A real "No cell with id" already lists them,
|
|
73
|
+
* so it is left untouched.
|
|
74
|
+
*/
|
|
75
|
+
export function notebookErrorHint(opts: { code?: string; syntax: boolean; message: string; parsed: Notebook | undefined }): string {
|
|
76
|
+
if (opts.code === "ENOENT") return " — no such file; check the path points to an existing .ipynb";
|
|
77
|
+
if (opts.syntax) return " — the file is not valid notebook JSON";
|
|
78
|
+
if (opts.parsed && opts.message.includes("cell_id") && !opts.message.includes("cells are:")) {
|
|
79
|
+
return ` — this notebook's cells are: ${describeCells(opts.parsed)}. Address a cell by its own id or by position (cell-0 is the first); edit_mode "append" adds one at the end.`;
|
|
80
|
+
}
|
|
81
|
+
return "";
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Index of a cell by its own id, or by its `cell-N` position. -1 when neither matches. */
|
|
40
85
|
export function findCellIndex(notebook: Notebook, cellId: string): number {
|
|
41
|
-
|
|
86
|
+
const byId = notebook.cells.findIndex((cell) => cell.id === cellId);
|
|
87
|
+
if (byId !== -1) return byId;
|
|
88
|
+
const positional = /^cell-(\d+)$/.exec(cellId);
|
|
89
|
+
if (!positional) return -1;
|
|
90
|
+
const index = Number.parseInt(positional[1], 10);
|
|
91
|
+
return index < notebook.cells.length ? index : -1;
|
|
42
92
|
}
|
|
43
93
|
|
|
44
|
-
|
|
45
|
-
|
|
94
|
+
/**
|
|
95
|
+
* The "fail loud, name the fix" error for an id that resolves to nothing
|
|
96
|
+
* (docs/decisions/tools.md): which ids exist, and the two ways to add a cell
|
|
97
|
+
* without naming one.
|
|
98
|
+
*/
|
|
99
|
+
export function noSuchCellError(notebook: Notebook, cellId: string): Error {
|
|
100
|
+
return new Error(
|
|
101
|
+
`No cell with id "${cellId}". This notebook's cells are: ${describeCells(notebook)}. ` +
|
|
102
|
+
"Cells can be addressed by their own id or by position (cell-0 is the first). " +
|
|
103
|
+
'To add a cell without naming one, use edit_mode "append" (at the end) or omit cell_id with edit_mode "insert" (at the top).',
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function newCell(cellType: "code" | "markdown", source: string, id: string | undefined): NotebookCell {
|
|
108
|
+
const cell: NotebookCell = { cell_type: cellType, metadata: {}, source: toSourceLines(source) };
|
|
109
|
+
// A 4.4 notebook has no legal `id` field, so a new cell must not carry one
|
|
110
|
+
// either — stamping one would leave a single id-bearing cell among id-less
|
|
111
|
+
// ones, which is exactly the shape withRepairedIds refuses to create.
|
|
112
|
+
if (id !== undefined) cell.id = id;
|
|
46
113
|
if (cellType === "code") {
|
|
47
114
|
cell.outputs = [];
|
|
48
115
|
cell.execution_count = null;
|
|
@@ -50,6 +117,23 @@ function newCell(cellType: "code" | "markdown", source: string, id: string): Not
|
|
|
50
117
|
return cell;
|
|
51
118
|
}
|
|
52
119
|
|
|
120
|
+
/**
|
|
121
|
+
* nbformat 4.5 REQUIRES every cell to carry an id, so a 4.5+ notebook missing
|
|
122
|
+
* them is malformed and filling them in on the way out is a repair, not a
|
|
123
|
+
* rewrite. A 4.4 notebook is left alone: `id` is not a legal cell field there,
|
|
124
|
+
* and its cells stay addressable by position.
|
|
125
|
+
*/
|
|
126
|
+
function supportsIds(notebook: Notebook): boolean {
|
|
127
|
+
const minor = typeof notebook.nbformat_minor === "number" ? notebook.nbformat_minor : 0;
|
|
128
|
+
const major = typeof notebook.nbformat === "number" ? notebook.nbformat : 4;
|
|
129
|
+
return major > 4 || (major === 4 && minor >= 5);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function withRepairedIds(notebook: Notebook, cells: NotebookCell[], makeId: () => string): NotebookCell[] {
|
|
133
|
+
if (!supportsIds(notebook)) return cells;
|
|
134
|
+
return cells.map((cell) => (cell.id ? cell : { ...cell, id: makeId() }));
|
|
135
|
+
}
|
|
136
|
+
|
|
53
137
|
export interface EditResult {
|
|
54
138
|
notebook: Notebook;
|
|
55
139
|
summary: string;
|
|
@@ -61,35 +145,44 @@ export interface EditResult {
|
|
|
61
145
|
*/
|
|
62
146
|
export function applyEdit(notebook: Notebook, request: EditRequest, makeId: () => string): EditResult {
|
|
63
147
|
const cells = [...notebook.cells];
|
|
148
|
+
const finish = (updated: NotebookCell[], summary: string): EditResult => ({
|
|
149
|
+
notebook: { ...notebook, cells: withRepairedIds(notebook, updated, makeId) },
|
|
150
|
+
summary,
|
|
151
|
+
});
|
|
64
152
|
|
|
65
153
|
if (request.editMode === "delete") {
|
|
66
154
|
if (!request.cellId) throw new Error("cell_id is required for edit_mode 'delete'");
|
|
67
155
|
const index = findCellIndex(notebook, request.cellId);
|
|
68
|
-
if (index === -1) throw
|
|
156
|
+
if (index === -1) throw noSuchCellError(notebook, request.cellId);
|
|
157
|
+
const label = cellLabel(cells[index], index);
|
|
69
158
|
cells.splice(index, 1);
|
|
70
|
-
return
|
|
159
|
+
return finish(cells, `Deleted cell ${label}`);
|
|
71
160
|
}
|
|
72
161
|
|
|
73
162
|
if (request.newSource === undefined) throw new Error("new_source is required unless edit_mode is 'delete'");
|
|
74
163
|
|
|
75
|
-
if (request.editMode === "insert") {
|
|
76
|
-
if (!request.cellType) throw new Error(
|
|
77
|
-
const
|
|
78
|
-
|
|
164
|
+
if (request.editMode === "insert" || request.editMode === "append") {
|
|
165
|
+
if (!request.cellType) throw new Error(`cell_type is required for edit_mode '${request.editMode}'`);
|
|
166
|
+
const cell = newCell(request.cellType, request.newSource, supportsIds(notebook) ? makeId() : undefined);
|
|
167
|
+
if (request.editMode === "append") {
|
|
168
|
+
cells.push(cell);
|
|
169
|
+
// Named the way the model must address it next: its id, or its position
|
|
170
|
+
// in a notebook that has none.
|
|
171
|
+
return finish(cells, `Appended ${request.cellType} cell ${cellLabel(cell, cells.length - 1)} at the end`);
|
|
172
|
+
}
|
|
79
173
|
// Claude Code inserts AFTER the given cell; no cell_id means insert first.
|
|
80
174
|
const index = request.cellId ? findCellIndex(notebook, request.cellId) : -1;
|
|
81
|
-
if (request.cellId && index === -1) throw
|
|
175
|
+
if (request.cellId && index === -1) throw noSuchCellError(notebook, request.cellId);
|
|
176
|
+
const afterLabel = request.cellId ? cellLabel(notebook.cells[index], index) : undefined;
|
|
82
177
|
cells.splice(index + 1, 0, cell);
|
|
83
|
-
return {
|
|
84
|
-
notebook: { ...notebook, cells },
|
|
85
|
-
summary: `Inserted ${request.cellType} cell ${id}${request.cellId ? ` after ${request.cellId}` : " at the top"}`,
|
|
86
|
-
};
|
|
178
|
+
return finish(cells, `Inserted ${request.cellType} cell ${cellLabel(cell, index + 1)}${afterLabel ? ` after ${afterLabel}` : " at the top"}`);
|
|
87
179
|
}
|
|
88
180
|
|
|
89
181
|
if (!request.cellId) throw new Error("cell_id is required for edit_mode 'replace'");
|
|
90
182
|
const index = findCellIndex(notebook, request.cellId);
|
|
91
|
-
if (index === -1) throw
|
|
183
|
+
if (index === -1) throw noSuchCellError(notebook, request.cellId);
|
|
92
184
|
const existing = cells[index];
|
|
185
|
+
const label = cellLabel(existing, index);
|
|
93
186
|
const cellType = (request.cellType ?? existing.cell_type) as "code" | "markdown";
|
|
94
187
|
const replacement: NotebookCell = {
|
|
95
188
|
...existing,
|
|
@@ -104,5 +197,5 @@ export function applyEdit(notebook: Notebook, request: EditRequest, makeId: () =
|
|
|
104
197
|
delete replacement.execution_count;
|
|
105
198
|
}
|
|
106
199
|
cells[index] = replacement;
|
|
107
|
-
return
|
|
200
|
+
return finish(cells, `Replaced cell ${label}`);
|
|
108
201
|
}
|