DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

Boenxia /

Boenxia/dsh-plugin-notebook-tools

Verified

Cell-aware Jupyter notebook tools for DSH agents

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@18974ad6

dsh-plugin-notebook-tools

Cell-aware Jupyter notebook tools for DSH agents. A zero-dependency Node ESM plugin that ports the OpenClaw notebook-tools plugin (9 tools) onto the DSH host.

The core read / write / edit tools treat .ipynb files as opaque JSON, which is error-prone, token-hungry, and easy to corrupt. This plugin provides a structured, cell-aware API instead: stable cell ids, output-aware truncation, stale-edit guards, and format-preserving atomic writes.

Install

dsh plugin --profile desktop add --config.minimumReleaseAge=0 F:\path\to\dsh-plugin-notebook-tools

dsh plugin forwards to the pnpm bundled with DSH, and --profile is required. The --config.minimumReleaseAge=0 override is needed while the target profile's existing lockfile entries are newer than pnpm's default minimumReleaseAge cutoff (pnpm 11.7.0 enforces that default even when the profile's pnpm-workspace.yaml lists the same offenders under minimumReleaseAgeExclude). Without it you get ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION naming packages you did not touch.

On success the package is appended to both dependencies and dsh.profile.bundles in <DSH_HOME>\profiles\<profile>\package.json — no manual edit needed. A failed run can still leave a link in node_modules, so treat the package.json entry, not node_modules, as proof.

The plugin ships a cordis.patch.yml that inserts it under the id notebook-tools. DSH composes that row at boot, so restart the DSH app for the tools to appear in an already-running session.

No dependencies are installed: pnpm install is unnecessary, and the package imports only node: builtins.

Companion skill

skill/notebook-tools/SKILL.md is the matching DSH skill: it tells the agent when to reach for these tools, which of them can mutate a notebook, and how stable a cell id really is. Copy it into the skills directory:

Copy-Item -Recurse skill\notebook-tools "$env:USERPROFILE\.dsh\skills\notebook-tools" -Force

DSH hot-reloads the skills directory, so the skill shows up without a restart — unlike the plugin itself, which is composed at boot.

Tools

Tool Writes Purpose
notebook_create yes Create a notebook from structured cells
notebook_read no Structured read, optionally with outputs
notebook_list_cells no Cheap index: id, type, preview, counts
notebook_search no Find cells by text or regex
notebook_validate no Validate structure and report facts
notebook_edit_cell yes Replace a cell's source / type
notebook_insert_cell yes Insert a new cell at a position
notebook_delete_cell yes Delete a cell by id or index
notebook_clear_outputs yes Clear outputs (all or one cell)

Guarantees

  • Writes preserve the file's own formatting. The plugin probes the layout a notebook already uses — JSON indent (0/1/2/4), CRLF vs LF, trailing newline — and writes it back unchanged, so a notebook produced by JupyterLab, nbformat.writes, or a text editor is not reformatted. A cell whose source is a plain string stays a string, and keys that were absent (outputs, execution_count, metadata) are not invented. Read-then-write with no edit is byte-identical. Not preserved: tab indentation, \uXXXX-escaped non-ASCII (Python's ensure_ascii=True), and more than one trailing newline.
  • Atomic saves. Mutations serialize to a sibling temp file (.‹basename›.tmp-‹pid›-‹counter›-‹random›) in the same directory, fsync it, then rename over the target. A killed process can never leave a half-written notebook, and no temp file ever lands in the system temp directory.
  • Validation before every write. A notebook that fails the deep audit — cell ids outside ^[A-Za-z0-9_-]{1,64}$ or duplicated, non-object metadata, non-array outputs, non-string source elements — is refused by the writers and reported by notebook_validate, instead of being silently rewritten.
  • Cell ids over indexes. notebook_read, notebook_list_cells, and notebook_search always return an id. A notebook too old to store ids (nbformat < 4.5) gets ids derived from sha256("v1|<ordinal>|<source>"), so the same cell keeps the same id across loads and processes; the derived id is persisted only once the file's format supports ids. nbformat_minor is never bumped. Indexes shift after every insert/delete — prefer ids.
  • Editing a code cell clears outputs and execution_count, and the response text says so.
  • Three stale-edit guards on every mutating tool. expected_file_sha256 catches any change to the bytes on disk — including a rewrite inside the same millisecond, which expected_file_mtime_ms cannot see; expected_source_sha256 catches a change to the one cell an operation depends on (the anchor cell for an insert, the target cell for an edit, delete, or clear); the mtime check is the cheap one and tolerates sub-millisecond jitter. All of them fail before any write. notebook_read and notebook_list_cells return both mtime_ms and file_sha256; only notebook_read returns per-cell source_sha256.
  • nbformat 4 only. v3 and earlier are rejected.
  • No kernel execution, ever.
  • Failures throw a NotebookError subclass carrying a stable code (NOTEBOOK_NOT_FOUND, STALE_NOTEBOOK, CELL_NOT_FOUND, …). There is no isError field on the DSH tool result contract.

Usage

notebook_list_cells  { path: "~/analysis.ipynb" }
notebook_read        { path: "~/analysis.ipynb", cell_ids: ["a1b2c3d4"] }
notebook_search      { path: "~/analysis.ipynb", query: "train_test_split" }
notebook_edit_cell   { path: "~/analysis.ipynb", cell_id: "a1b2c3d4",
                       new_source: "model.fit(X, y)\n",
                       expected_source_sha256: "…",
                       expected_file_sha256: "…" }
notebook_validate    { path: "~/analysis.ipynb" }

Pass the guards straight back from the read that produced them — file_sha256 comes from notebook_read or notebook_list_cells, source_sha256 only from notebook_read.

Development

node --test          # from this directory

Tests use node:test + node:assert/strict and require Node >= 22 (verified on Node 24).

Releasing

Releases are one command, run from a clean main that already contains the version bump:

npm run release -- --title "v0.2.1 - short one-line summary"

It refuses to run unless CHANGELOG.md carries a non-empty ## [<version>] section and a [<version>]: link reference for the version in package.json, the working tree is clean, and HEAD is the commit that carries the bump. Then it runs node --test, pushes the release branch, creates the annotated tag v<version>, pushes it, and creates the GitHub Release: the tag annotation is the release title and the CHANGELOG section is its body. Credentials come from GITHUB_TOKEN / GH_TOKEN, falling back to the stored git credential for github.com (nothing is ever printed); the GitHub CLI is not required.

Other flags: --dry-run (print the plan, change nothing), --skip-tests, --branch <name> (default main), --remote <name> (default origin).

Layout

lib/index.js              composition only — registers the 9 tools on ctx.tools
lib/nb/*.js               pure domain; node: builtins only, no host SDK imports
lib/tools/*.js            adapters: JSON Schema params, error mapping, response text
scripts/release.mjs       the release command behind "npm run release"
test/*.test.js            node:test suites
cordis.patch.yml          the composition row DSH merges at boot
skill/notebook-tools/     the companion skill (SKILL.md)

The domain layer (lib/nb/) knows nothing about DSH — it only knows about notebooks, so it stays unit-testable in isolation.

Credits and license

MIT. This is a port, not a fork: the domain layer and all nine tool adapters were re-implemented for the DSH plugin host from the original plugin's documented behavior.

  • Upstream: Lightbridge-KS/openclaw-notebook-tools by Kittipos Sirivongrungson (MIT). The tool surface, the behavioral spec in its docs/Notebook-Tools-Spec.md, and the companion skill all originate there.
  • This port: the DSH packaging (cordis.patch.yml, the dsh.bundle.patch hook), the zero-dependency plain-object tool descriptors, and the test suites.

LICENSE carries both copyright notices.

—/ 5

No ratings yet

Verified DSH bundle

Commit 18974ad667f4

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout