@tlockney/cdp-kernel 0.4.0 → 0.5.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/README.md CHANGED
@@ -1,507 +1,86 @@
1
1
  # cdp-kernel
2
2
 
3
- A Jupyter kernel extracted from Deno's (`deno jupyter`, v2.9.6) that drives
4
- any V8 runtime over the Chrome DevTools Protocol. The kernel host runs on
5
- Node or Deno; the code you type runs in a separate **target** runtime:
6
- Node or Deno over CDP. Bun, workerd and celld evaluate cells in-process
7
- instead (see [Bun](#bun), [workerd](#workerd) and [celld](#celld)). In
8
- principle, anything else whose inspector speaks CDP works too.
3
+ A Jupyter kernel for TypeScript and JavaScript, extracted from the one built
4
+ into Deno 2.9.6 (`deno jupyter`), that runs notebooks on several JavaScript runtimes. The kernel
5
+ host runs on Node or Deno; the code you type runs in a separate **target**
6
+ runtime: Node or Deno over the Chrome DevTools Protocol, or Bun, workerd and
7
+ celld, which evaluate cells in-process. In principle, anything else whose
8
+ inspector speaks CDP works too.
9
+
10
+ **Website:** <https://cdp-kernel.org/> ·
11
+ **Docs:** [cdp-kernel.org/docs](https://cdp-kernel.org/docs/)
9
12
 
10
13
  This is early software. It passes a conformance suite derived from Deno's
11
14
  own Jupyter integration tests on every target, and it's packaged for npm and
12
- JSR as `@tlockney/cdp-kernel`, but it hasn't seen much real use yet. The
13
- feasibility study behind it covers the design,
14
- the other options considered (workerd, Bun, rusty_v8, FFI), and the
15
- evidence: [read it online](https://claude.ai/artifact/S66TnTmhCzPy2EW7jbeeue)
16
- (also in the repo as
17
- [`docs/life-after-deno-jupyter.html`](docs/life-after-deno-jupyter.html)). It
18
- was written before packaging, so it calls the project a spike.
19
- The scripts that produced its runtime findings are in [`probes/`](probes/).
20
-
21
- ## Why this shape
22
-
23
- Deno 2.9's kernel is already two halves joined by thirteen ops
24
- (denoland/deno#34083 rewrote it in JS and dropped libzmq):
15
+ JSR as `@tlockney/cdp-kernel`, but it hasn't seen much real use yet.
25
16
 
26
- - `cli/js/jupyter_kernel.js`: ZMTP and the Jupyter wire protocol in plain
27
- JS over TCP.
28
- - A REPL isolate driven by CDP calls: `Runtime.evaluate` with
29
- `replMode: true`, `callFunctionOn`, `getProperties`,
30
- `globalLexicalScopeNames`.
17
+ ## Features
31
18
 
32
- So the extraction is:
33
-
34
- - Port the protocol half to `node:net` and `node:crypto`: `src/zmtp.ts`,
35
- `src/wire.ts`, `src/kernel.ts`.
36
- - Point the CDP half at another runtime's `--inspect` WebSocket:
37
- `src/target.ts`, `src/cdp.ts`.
38
- - Replace what Deno's module loader did with esbuild + acorn
39
- (`src/transform.ts`) and Node resolve hooks (`runtime/node-hooks.mjs`).
40
- - Port `Deno.jupyter` (`40_jupyter.js`) to `runtime/display.mjs`.
19
+ - The `Deno.jupyter` display API (`jupyter.display`, `md`, `html`, `svg`,
20
+ `image`, …) on every target.
21
+ - TypeScript, TSX, top-level `await`, and `npm:`/`jsr:` imports, with an
22
+ optional lockfile.
23
+ - Deno projects (`deno.json` import maps, JSX settings) and Workers
24
+ projects (`wrangler` bindings in `env`).
25
+ - JupyterLab's visual debugger on Node and Deno targets.
26
+ - anywidget-style widgets and Jupyter comms.
27
+ - `prompt()` and `confirm()`, interrupt that keeps state, Shift-Tab
28
+ inspection, completion and history.
41
29
 
42
30
  ## Install
43
31
 
44
- Each install writes a Jupyter kernelspec: `cdp-node`, `cdp-deno`,
45
- `cdp-node-deno` (Node with the Deno compatibility namespace), `cdp-bun`,
46
- `cdp-workerd`, or `cdp-celld`. Then pick the kernel in JupyterLab or VS Code.
47
-
48
- **From npm** (host runs on Node 24+). CI tests Node 24 and 26 with Deno 2.9
49
- and the latest 2.x, on Linux and macOS. Windows isn't supported yet.
32
+ Node 24+ host, from npm:
50
33
 
51
34
  ```sh
52
35
  npm install -g @tlockney/cdp-kernel
53
- cdp-kernel install --runtime node
54
- cdp-kernel install --runtime deno
55
- cdp-kernel install --runtime node --deno-compat
56
- cdp-kernel install --runtime bun
57
- cdp-kernel install --runtime workerd
58
- cdp-kernel install --runtime celld
36
+ cdp-kernel install --runtime node # or deno, bun, workerd, celld
59
37
  ```
60
38
 
61
- The kernelspec records the absolute paths of `node` and the installed
62
- package. Re-run `install` after upgrading either.
63
-
64
- **From JSR** (host runs on Deno):
39
+ Deno host, from JSR:
65
40
 
66
41
  ```sh
67
42
  deno run -A jsr:@tlockney/cdp-kernel/cli install --runtime deno
68
43
  ```
69
44
 
70
- The kernelspec pins the release it was installed from
71
- (`jsr:@tlockney/cdp-kernel@<version>/cli`). On first launch the target's
72
- runtime modules are copied into `~/.cache/cdp-kernel/runtime/`. A Node
73
- target needs `node` on `PATH`, and a Bun target needs `bun` (1.4 is what's
74
- been tested). A workerd target uses the `workerd` in the notebook
75
- directory's `node_modules` (a Workers project has one through wrangler),
76
- or the one on `PATH` (`npm install -g workerd`; 2026-10-15 is what's been
77
- tested). A celld target uses `~/.local/bin/celld`, where celld's installer
78
- puts it, or the one on `PATH` (0.6.2 is what's been tested; celld has no
79
- Windows build).
80
-
81
- Deno 2.9+ refuses package versions less than 24 hours old by default
82
- ([minimum dependency age](https://docs.deno.com/go/minimum-dependency-age)).
83
- In a release's first day, the install command fails with "blocked by the
84
- minimum dependency age policy". Pass `--min-dep-age=0` to the install
85
- command to get it through:
86
-
87
- ```sh
88
- deno run -A --min-dep-age=0 jsr:@tlockney/cdp-kernel/cli install --runtime deno
89
- ```
90
-
91
- The kernelspec it writes works on release day too. It relaunches the kernel
92
- with a dependency cutoff of two days after the release
93
- (`--min-dep-age=<date>`), so it never resolves anything published after
94
- that.
95
-
96
- **From a checkout**, for working on the kernel itself:
97
-
98
- ```sh
99
- npm ci
100
- npm run install:node # kernelspec cdp-node
101
- npm run install:deno # kernelspec cdp-deno
102
- npm run install:node-deno # kernelspec cdp-node-deno
103
- npm run install:bun # kernelspec cdp-bun
104
- npm run install:workerd # kernelspec cdp-workerd
105
- npm run install:celld # kernelspec cdp-celld
106
- ```
107
-
108
- These kernelspecs point at the checkout, so don't move it after installing.
109
-
110
- In a cell:
111
-
112
- - `jupyter.display`, `jupyter.md`, `jupyter.html`, `jupyter.svg`,
113
- `jupyter.image`, `jupyter.format`, `jupyter.broadcast`, and
114
- `Symbol.for("Jupyter.display")`: the same API as `Deno.jupyter`.
115
- - `Deno.jupyter` also works:
116
- - On a Deno target it is a proxy over the real namespace. Deno's own
117
- getter throws outside `deno jupyter`.
118
- - On Node it needs `--deno-compat`. That installs a `Deno` namespace for
119
- file, env and process APIs (`runtime/deno-compat.mjs`).
120
- - The shim is opt-in because libraries detect Deno with `typeof Deno`, and
121
- would take Deno code paths that call APIs it doesn't cover.
122
- - Imports work: TypeScript, TSX, enums, top-level `await`, and `const`
123
- redeclaration across cells. Specifiers can be `./local.ts`, bare packages
124
- from the notebook's `node_modules`, `node:` builtins, and `npm:pkg@range`
125
- or `jsr:@scope/pkg@range`.
126
- - On Node, `npm:` and `jsr:` install on first use into
127
- `~/.cache/cdp-kernel/npm`. An exact version (`npm:react@19.0.0`) installs
128
- exactly that version. A range (`npm:react@19`) is resolved the first
129
- time it's imported and stays at that version, even as other packages
130
- are installed later. Caches written by cdp-kernel 0.3.0 or earlier
131
- recorded ranges, which later installs could re-resolve. Delete
132
- `~/.cache/cdp-kernel/npm` to start fresh. `jsr:` works with npm 12,
133
- which otherwise refuses JSR's registry tarballs.
134
- - **Lockfile** (cdp-kernel 0.4.0 or later). To pin versions, create a `cdp-kernel.lock.json` in the
135
- notebook's directory (or above it) containing `{}`. From then on, each
136
- `npm:`/`jsr:` range is recorded with the exact version it resolved to,
137
- and installed at that version, so the notebook gets the same packages
138
- on another machine. Commit it next to the notebook. Deno targets use
139
- Deno's own resolution and lock instead. Installs into the shared cache
140
- are serialized, so two kernels installing at once can't corrupt it.
141
- - On Deno they use Deno's global cache, unless the notebook is in a Deno
142
- project (below).
143
- - On Bun they install with `bun add` into `~/.cache/cdp-kernel/bun`. Other
144
- bare imports resolve from the notebook's directory, so without a
145
- `node_modules` there, Bun auto-installs them.
146
- - On workerd, the host bundles every import other than a builtin
147
- (`node:`, `cloudflare:`) with esbuild, using Workers' resolution
148
- conditions, and sends it with the cell. `npm:` and `jsr:` install into
149
- the npm cache first, as on Node.
150
- - **Deno projects.** If a `deno.json` or `deno.jsonc` sits in the notebook's
151
- directory or above it, a Deno target uses it, as `deno jupyter` does:
152
- - its import map (`"@/": "./lib/"` aliases, mapped packages);
153
- - its `nodeModulesDir` setting, with `package.json` dependencies from the
154
- project's `node_modules`;
155
- - its JSX and decorator `compilerOptions`: `jsx` (`react-jsx`,
156
- `react-jsxdev`, `precompile`, `react`), `jsxImportSource`,
157
- `jsxFactory`, `jsxFragmentFactory`, `experimentalDecorators`.
158
-
159
- The import map wins over `node_modules`, as in Deno. The kernel never
160
- writes to the project's lockfile. A `deno.json` that doesn't parse is
161
- reported in the first cell's output, and the kernel runs as in a plain
162
- directory. Outside a project, the target runs with
163
- `--node-modules-dir=none --no-lock`.
164
- - `prompt()` and `confirm()` work over the stdin channel, except on
165
- workerd. Interrupt keeps state, except for a busy cell on Bun or workerd
166
- (below).
167
- - **Shift-Tab** shows a tooltip for the name at the cursor: its type, a
168
- function's signature (and its source at the second press), a preview, and
169
- its properties. Nothing is run to get it. On Node and Deno the value is
170
- evaluated with V8's side-effect check (`throwOnSideEffect`), which also
171
- guards completion. Bun, workerd and celld look the name up by property,
172
- without calling getters. Executed cells are kept for `history_request`
173
- (tail, range and search).
174
-
175
- ## Bun
176
-
177
- Bun's `--inspect` speaks the WebKit Inspector Protocol, which lacks what the
178
- CDP backend relies on: `replMode`, `Runtime.addBinding`,
179
- `Runtime.terminateExecution`, and top-level `await` in `Runtime.evaluate`.
180
- So the Bun target has its own backend (`src/bun-target.ts`,
181
- `runtime/bun-channel.mjs`):
182
-
183
- - **No inspector.** The host and Bun exchange JSON lines over a pipe on
184
- fd 3, which only the host holds, so there is no port to discover.
185
- - **Cells run in-process.** Each cell becomes global code wrapped in an
186
- async function (`transformCellForEval` in `src/transform.ts`). Its
187
- top-level declarations become properties of `globalThis`, so later
188
- cells, and closures from this one, see them. This is the
189
- tslab/IJavascript technique.
190
- - **Same output ordering.** stdout and stderr stay pipes, ordered against
191
- display messages by the same barrier markers as the CDP targets.
192
- - **Interrupt.** A cell that's waiting on a promise is interrupted in place,
193
- keeping state. A busy one (`while (true) {}`) can't be reached, so Bun is
194
- stopped and restarts on the next cell without the earlier state. The cell
195
- output says so.
196
-
197
- What differs from the CDP targets is listed under [Known gaps](#known-gaps).
198
-
199
- ## workerd
200
-
201
- workerd's inspector can evaluate code, but outside a request there is no
202
- I/O context: timers, `fetch` and `crypto.randomUUID()` throw "Disallowed
203
- operation called within global scope". So the workerd target runs its
204
- cells inside a driver Worker (`src/workerd-target.ts`,
205
- `runtime/workerd-worker.mjs`):
206
-
207
- - **One session.** The host opens a single WebSocket to the driver, and
208
- every cell runs inside that session's request. Cells get Workers'
209
- `fetch`, `Request`, `caches`, timers and randomness, and anything one
210
- cell makes carries over to the next: globals, a Durable Object stub, a
211
- stream, a timer still pending.
212
- - **A private socket.** Outside a Workers project, the host writes a
213
- workerd config into a private temporary directory and serves the driver
214
- on a Unix socket there, opened with a per-launch token. There is no TCP
215
- port and no inspector.
216
- - **Cells.** Cells are transformed as for Bun and run by workerd's
217
- `UnsafeEval` binding (hence `--experimental`).
218
- - **Output.** `console` output and display messages come back over the
219
- session as they happen, in order.
220
- - **Interrupt.** A cell that's waiting on something is abandoned in place,
221
- keeping state, as on Bun. A busy one gets Bun's treatment too: workerd is
222
- stopped and restarts on the next cell.
223
-
224
- What workerd's own process prints, such as its warnings, goes to the Jupyter
225
- server log, not the notebook.
226
-
227
- ### Workers projects
228
-
229
- If a `wrangler.jsonc`, `wrangler.json` or `wrangler.toml` sits in the
230
- notebook's directory or above it, the driver runs on the project's own
231
- Miniflare, the simulator `wrangler dev` uses, with the project's bindings
232
- (`src/workers-project.ts`):
233
-
234
- - `env` holds the bindings: whatever wrangler translates for Miniflare.
235
- Vars, KV, D1 and Durable Objects are tested; R2, Queues and the others
236
- go through the same path but aren't.
237
- - The project's `main` is bundled into the driver, so its Durable Object
238
- classes run, and `worker` is its default export:
239
- `await worker.fetch(new Request("https://x/"), env)`.
240
- - State lives in the project's `.wrangler/state/cdp-kernel`, beside
241
- `wrangler dev`'s own, so the two never open the same Durable Object.
242
- - The first cell's output names the bindings. If the project can't load,
243
- for example because wrangler isn't installed in it, the first cell says
244
- why and the kernel runs on plain workerd.
245
-
246
- wrangler and Miniflare come from the project's `node_modules`, so they match
247
- its versions. Miniflare 4 and 5 both work. Miniflare listens on a loopback
248
- TCP port, so there the per-launch token is what keeps other local processes
249
- out.
250
-
251
- ## celld
252
-
253
- [celld](https://github.com/denoland/celld) runs Workers and Durable Objects
254
- on your own storage. Like workerd it has no I/O outside a request, so the
255
- celld target runs the same driver Worker over the same WebSocket session
256
- (`src/celld-target.ts`). Cells, output, interrupts and host-bundled imports
257
- all work as on workerd. The differences:
258
-
259
- - **No `UnsafeEval` needed.** celld allows a plain global `eval`, so it runs
260
- without workerd's `--experimental`.
261
- - **One module.** The host bundles the driver, and a project's `main`, into
262
- a single module, and gives celld the result with `no_bundle`. celld
263
- doesn't need its own esbuild.
264
- - **Loopback TCP.** celld listens only on TCP, so the session is a loopback
265
- WebSocket opened with a per-launch token. The kernel picks a free port
266
- and runs `celld dev --no-watch` on a generated config.
267
- - **Projects.** In a celld project (a `wrangler.jsonc` or `wrangler.json`
268
- at or above the notebook), the generated config copies the project's
269
- compatibility settings and bindings: vars, KV, D1, R2, Durable Objects,
270
- Queues, Workflows, services, Worker Loaders. `env` and `worker` then work
271
- as on workerd. The generated config is written to the project's
272
- `.celld/cdp-kernel` and removed when the kernel stops. The state stays
273
- there across kernels, beside `celld dev`'s own `.celld/dev`. celld rejects `wrangler.toml`; a project
274
- that can't load is reported in the first cell, and the driver runs alone.
275
- - **Stopping.** `celld dev` runs its node in a separate process group and
276
- waits for it to stop gracefully, which a busy cell never allows. So an
277
- interrupt of a busy cell kills the node, then `celld dev`.
278
-
279
- ## Debugger
280
-
281
- On Node and Deno targets, JupyterLab's visual debugger works: the bug
282
- button in the notebook toolbar, breakpoints in the cell gutter, the call
283
- stack, variables and scopes, stepping, pause on exceptions, the debug
284
- console, and the rich variable viewer. The kernel translates the Jupyter
285
- debug protocol (DAP over `debug_request`) to V8's `Debugger` domain on the
286
- session it already holds (`src/debugger.ts`):
287
-
288
- - **Cell paths.** Each cell is named as JupyterLab names it: a temp-dir
289
- prefix plus the Murmur2 hash of its code (`src/murmur2.ts`, matched
290
- against ipykernel's). It's evaluated with that name as its
291
- `//# sourceURL`, so breakpoints bind to the cell and frames point back at
292
- it.
293
- - **Lines.** Breakpoint and frame lines go through the cell's source map
294
- (`src/sourcemap.ts`), since esbuild rewrites the TypeScript. A breakpoint
295
- on a blank or type-only line moves to the next statement.
296
- - **Nothing runs on its own.** The variable explorer between cells reads
297
- the cells' declarations with V8's side-effect check, so a getter never
298
- runs. Frames, scopes and the rich viewer evaluate on the paused frame.
299
- - **Kernelspec.** `metadata.debugger` is set for Node and Deno kernelspecs,
300
- which is what JupyterLab checks. Reinstall older kernelspecs to get it.
45
+ Each install writes a Jupyter kernelspec (`cdp-node`, `cdp-deno`, …). Pick
46
+ it in JupyterLab or VS Code. Linux and macOS are supported; Windows isn't
47
+ yet. See [Install](https://cdp-kernel.org/docs/install)
48
+ for `--deno-compat`, release-day installs on Deno, target runtime
49
+ requirements, and installing from a checkout.
301
50
 
302
- `CDPK_DEBUG_TRACE=1` logs every debug request, reply and event to the
303
- Jupyter server's log.
304
-
305
- Bun, workerd and celld don't run over an inspector, so they have no
306
- debugger.
307
-
308
- ## Editor types
309
-
310
- Cells run untyped, as in `deno jupyter`. Notebook code your editor
311
- type-checks can declare the globals.
312
-
313
- **Node notebooks.** The npm package ships the declarations. Add it as a dev
314
- dependency, then reference it once from a cell or a `.d.ts` in the project:
315
-
316
- ```ts
317
- /// <reference types="@tlockney/cdp-kernel/globals" />
318
- await jupyter.display(jupyter.md`typed`);
319
- const name: string | null = prompt("Name?", "", { password: false });
320
- ```
321
-
322
- That declares `jupyter`, `prompt` and `confirm`. With the `cdp-node-deno`
323
- kernel, use `@tlockney/cdp-kernel/globals/deno-compat` instead: it adds the
324
- `Deno` namespace the compatibility shim implements. A custom display is a
325
- class with a `[jupyter.$display]()` method returning a MIME bundle.
326
- `jupyter.$display` is `Symbol.for("Jupyter.display")` at runtime, but only
327
- `jupyter.$display` carries the precise type.
328
-
329
- **Deno notebooks.** Use `Deno.jupyter`, which Deno already types and which a
330
- Deno target provides. The JSR package doesn't ship these globals, because
331
- JSR doesn't allow packages to augment global types.
332
-
333
- ## Widgets and comms
334
-
335
- `deno jupyter` has no comms. Here they're supported in both directions, so
336
- anywidget-style widgets work in frontends with the `anywidget` extension:
337
- JupyterLab, Notebook 7 and VS Code. Install it with
338
- `pip install anywidget`; that's the frontend half, no Python kernel needed.
339
-
340
- ```ts
341
- const counter = jupyter.widget({
342
- esm: `export default {
343
- render({ model, el }) {
344
- const btn = document.createElement("button");
345
- const update = () => { btn.textContent = \`count is \${model.get("count")}\`; };
346
- btn.onclick = () => { model.set("count", model.get("count") + 1); model.save_changes(); };
347
- model.on("change:count", update);
348
- update();
349
- el.append(btn);
350
- },
351
- };`,
352
- state: { count: 0 },
353
- });
354
- counter.on("change:count", (value) => console.log("clicked to", value));
355
- counter; // display it
356
- ```
357
-
358
- The model API:
359
-
360
- - `get(key)`, and `set(key, value)` or `set({ ... })`, which syncs to every
361
- view.
362
- - `on("change:key" | "change" | "msg:custom", listener)` and `off(...)`.
363
- - `send(content, buffers)` for custom messages.
364
- - `close()`.
365
-
366
- `Uint8Array`, `ArrayBuffer` and `DataView` values in the state travel as
367
- binary buffers. JupyterLab restores widget state after a page reload over
368
- the `jupyter.widget.control` comm.
369
-
370
- The comm layer underneath is available directly, for other comm-based
371
- protocols:
51
+ ## Quick start
372
52
 
373
53
  ```ts
374
- const comm = jupyter.comm.open("my.target", { hello: 1 });
375
- comm.onMsg(({ data, buffers }) => console.log(data, buffers.length));
376
- comm.send({ ping: true });
377
- jupyter.comm.registerTarget("echo", (comm) => comm.onMsg((m) => comm.send(m.data)));
378
- ```
379
-
380
- Comm handlers run like cells. Their output goes to the notebook as replies
381
- to the message that triggered them, an exception is shown on stderr, and
382
- interrupt stops a stuck handler. Messages are processed one at a time with
383
- cell executions, as in ipykernel, so a widget event that arrives during a
384
- long cell waits for the cell to finish.
385
-
386
- ## How the tricky parts work
387
-
388
- | Problem | Approach |
389
- |---|---|
390
- | Inspector-evaluated code has no module referrer, so Node rejects `import()` | Static imports are rewritten to `var x = (await __cdpk.import("m")).x` (Deno's `var_decl_imports`), and `import()` and `import.meta` go through the bootstrap module. A resolve hook re-parents those imports to the notebook directory. |
391
- | A busy loop inside `Runtime.evaluate` blocks every inspector message, so interrupt is never seen | Each cell starts with `await __cdpk.tick()` (one macrotask), so user code runs as an ordinary task. `Runtime.terminateExecution` then arrives on a second CDP session. The host abandons the evaluate, whose promise never settles. |
392
- | stdout/stderr arrive over pipes and display messages over the WebSocket, so they race | Every display message is preceded by a numbered marker on both pipes. The host releases the message only after both pipes reach the marker, and holds later pipe output until the message is out. |
393
- | `prompt()` must be synchronous | The target sends an `input_request` through a CDP binding, then blocks on a `readSync` of stdin. The host writes the reply as one JSON line. |
394
- | Uncaught errors from timers would kill the target | `uncaughtException` and `unhandledrejection` handlers report them to stderr. |
395
-
396
- ## Tests
54
+ import { format } from "npm:date-fns@4";
397
55
 
398
- ```sh
399
- npm run check # tsc --strict over src/ and test/
400
- npm test # unit tests (node:test)
401
- npm run e2e # conformance suite via jupyter_client/libzmq, on every target variant
402
- npm run test:lab # an anywidget counter in real JupyterLab, headless Chromium (node, deno, bun, workerd, celld)
403
- uv run --with nbclient --with nbformat python examples/run_notebook.py # Deno-style notebook on deno + node-deno
56
+ const now: Date = new Date();
57
+ await jupyter.display(jupyter.md`**Today** is ${format(now, "PPPP")}`);
404
58
  ```
405
59
 
406
- The e2e suite follows Deno's `tests/integration/jupyter_tests.rs` and adds
407
- the gaps those tests leave: display ordering, TS, imports, `npm:`/`jsr:`,
408
- completion, and `update_display_data`. Set `CDPK_E2E_OFFLINE=1` to skip the
409
- registry-dependent checks. Set `CDPK_E2E_CLI` to run it through a different
410
- CLI: an installed npm bin, or `deno run -A src/main.ts` to test a Deno host.
411
-
412
- ## Releasing
413
-
414
- One version number lives in three places: `package.json`, `deno.jsonc` and
415
- `src/package.ts`. `test/package.test.ts` fails if they disagree. To release:
416
-
417
- 1. Bump all three, and set `RELEASE_DATE` in `src/package.ts` to today
418
- (UTC). Commit, and merge to `main`.
419
- 2. Tag and push: `git tag v0.1.1 && git push origin v0.1.1`.
420
-
421
- The `publish` workflow then:
422
-
423
- 1. Checks that the tag matches the version, and that `RELEASE_DATE` is
424
- today or yesterday. A JSR kernelspec's dependency cutoff comes from that
425
- date, so a stale one would block the release it ships in.
426
- 2. Runs the type check, the unit tests, and the e2e suite against the packed
427
- npm tarball and against a Deno host.
428
- 3. Publishes to JSR (`deno publish`).
429
- 4. Publishes to npm (`npm publish`).
430
-
431
- Both registries authenticate the workflow through GitHub OIDC, so no tokens
432
- are stored:
433
- - **npm:** a trusted publisher for `tlockney/cdp-kernel` + `publish.yml`.
434
- - **JSR:** the package is linked to this repo in its JSR settings.
435
-
436
- Neither registry lets a version be republished. Each job skips a version
437
- its registry already has, so re-running a release only fills in the half
438
- that failed.
439
-
440
- ## Known gaps
441
-
442
- - Widgets are anywidget-style (`jupyter.widget`). The classic ipywidgets
443
- controls (sliders, dropdowns) are Python classes, so they're not available.
444
- - Inspection tooltips show what's found at runtime (type, signature,
445
- preview, properties), not TypeScript types or JSDoc. On Bun, workerd and
446
- celld, a getter on the path is reported but not called. History is kept
447
- for the current kernel session only.
448
- - The debugger is for Node and Deno targets only. Breakpoints are per line,
449
- conditional breakpoints and logpoints aren't supported, variables can't be
450
- set from the debugger, and "copy to globals" isn't offered.
451
- - `npm:` on Node, Bun, workerd and celld:
452
- - one npm alias per `name@range` (or exact version, when locked);
453
- - the cache is shared by every notebook, so peer dependencies resolve by
454
- real name from it;
455
- - a project's own `package.json` doesn't decide an `npm:` import's version
456
- (bare imports do use its `node_modules`).
457
- - No type checking. Deno doesn't type-check REPL cells either.
458
- - Results are formatted by each runtime's own inspector: `util.inspect` on
459
- Node, Bun, workerd and celld, and `Deno.inspect` on Deno. On a fixture of
460
- 30 typical values, 23 format identically on Node and Deno. The rest
461
- differ in two ways:
462
- - strings are quoted `'…'` on Node and `"…"` on Deno, wherever they
463
- appear;
464
- - a displayed `Error`'s stack also lists Node's own module-loader frames.
465
-
466
- That's deliberate: each runtime's users see the format they expect, and
467
- Node's inspector has no quote-style option, short of porting Deno's
468
- formatter. `test/formatting.test.ts` runs both runtimes on the fixture
469
- and fails if a new kind of difference appears (#17).
470
- - WebGPU texture and buffer display is dropped.
471
- - On Bun:
472
- - Interrupting a busy cell restarts Bun and loses state. An awaiting cell
473
- keeps it.
474
- - Top-level declarations are properties of `globalThis`. A `const` can be
475
- reassigned, and declaring a global's name (`const fetch = ...`) replaces
476
- that global rather than shadowing it.
477
- - Only a trailing expression statement shows a result: `x` does, but
478
- `if (c) { x }` doesn't.
479
- - Bun 1.4 can't import a module whose path contains `?`.
480
- - No `--deno-compat` namespace.
481
- - On workerd:
482
- - All cells share one request, so it never ends. Unlike production
483
- Workers, there is no per-request CPU or duration limit, and
484
- `ctx.waitUntil` isn't available in a cell.
485
- - No `prompt()` or `confirm()` answers: they return `null`, as without a
486
- stdin channel. `jupyter.image(path)` can't read files; pass the bytes.
487
- - Each imported module is bundled with its dependencies. Two imports that
488
- share a dependency each get their own copy.
489
- - Interrupting a busy cell restarts workerd and loses state.
490
- - Top-level declarations and results behave as on Bun: declarations are
491
- `globalThis` properties, and only a trailing expression shows a result.
492
- - In a Workers project, `main` is bundled with esbuild directly, not with
493
- wrangler's build. Custom `build` commands, `alias` and module `rules`
494
- aren't applied.
495
- - On celld, as on workerd, except:
496
- - Only the binding and compatibility keys of a project's config are
497
- copied. `assets`, `triggers` and `containers` aren't, and neither is
498
- `.dev.vars`. Relative paths inside the copied keys resolve from
499
- `.celld/cdp-kernel`, not from the project.
500
- - Interrupting a busy cell restarts celld and loses state.
501
-
502
- ## Provenance
503
-
504
- `src/zmtp.ts`, `src/wire.ts`, `src/kernel.ts`, `src/text.ts` and
505
- `runtime/display.mjs` are ports of Deno's `cli/js/jupyter_kernel.js` and
506
- `cli/js/40_jupyter.js` at v2.9.6 (commit `e518fbd`), Copyright 2018-2026 the
507
- Deno authors, MIT license. Each file header says which parts.
60
+ ## Documentation
61
+
62
+ New to Jupyter? Start with
63
+ [Getting started](https://cdp-kernel.org/docs/getting-started).
64
+
65
+ - Guides: [Writing notebooks](https://cdp-kernel.org/docs/notebooks),
66
+ [The `jupyter` namespace](https://cdp-kernel.org/docs/jupyter-api),
67
+ [Choosing a runtime](https://cdp-kernel.org/docs/runtimes),
68
+ [Widgets and comms](https://cdp-kernel.org/docs/widgets),
69
+ [Debugger](https://cdp-kernel.org/docs/debugger)
70
+ - Reference: [Editor types](https://cdp-kernel.org/docs/editor-types),
71
+ [Known gaps](https://cdp-kernel.org/docs/known-gaps)
72
+ - Internals: [Architecture](https://cdp-kernel.org/docs/architecture),
73
+ [Runtime integrations](https://cdp-kernel.org/docs/integrations)
74
+ - Contributing: [Development](https://cdp-kernel.org/docs/development)
75
+
76
+ The [feasibility study](https://cdp-kernel.org/docs/feasibility-study)
77
+ behind the project covers the design, the alternatives considered (workerd,
78
+ Bun, rusty_v8, FFI), and the evidence.
79
+
80
+ ## License
81
+
82
+ MIT. Parts are ports of Deno's Jupyter kernel, Copyright 2018-2026 the Deno
83
+ authors. The debugger's MurmurHash2 follows JupyterLab's (BSD-3-Clause) and
84
+ Gary Court's (MIT); their notices are in
85
+ [THIRD-PARTY-NOTICES](THIRD-PARTY-NOTICES). See
86
+ [Architecture › Provenance](https://cdp-kernel.org/docs/architecture#provenance).
@@ -0,0 +1,62 @@
1
+ Third-party notices for cdp-kernel
2
+
3
+ cdp-kernel itself is under the MIT license in LICENSE. The files below carry
4
+ code from other projects under their own licenses.
5
+
6
+ --------------------------------------------------------------------------------
7
+
8
+ src/murmur2.ts follows JupyterLab's packages/debugger/src/hash.ts
9
+ (Copyright (c) Jupyter Development Team), which is distributed under the
10
+ following license:
11
+
12
+ Copyright (c) 2015-2026 Project Jupyter Contributors
13
+ All rights reserved.
14
+
15
+ Redistribution and use in source and binary forms, with or without
16
+ modification, are permitted provided that the following conditions are met:
17
+
18
+ 1. Redistributions of source code must retain the above copyright notice, this
19
+ list of conditions and the following disclaimer.
20
+
21
+ 2. Redistributions in binary form must reproduce the above copyright notice,
22
+ this list of conditions and the following disclaimer in the documentation
23
+ and/or other materials provided with the distribution.
24
+
25
+ 3. Neither the name of the copyright holder nor the names of its
26
+ contributors may be used to endorse or promote products derived from
27
+ this software without specific prior written permission.
28
+
29
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
30
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
31
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
32
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
33
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
34
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
36
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
37
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
38
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39
+
40
+ JupyterLab's hash.ts is in turn adapted from Gary Court's murmurhash2_gc.js
41
+ (https://github.com/garycourt/murmurhash-js), distributed under the following
42
+ license:
43
+
44
+ Copyright (c) 2011 Gary Court
45
+
46
+ Permission is hereby granted, free of charge, to any person obtaining a copy
47
+ of this software and associated documentation files (the "Software"), to deal
48
+ in the Software without restriction, including without limitation the rights
49
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
50
+ copies of the Software, and to permit persons to whom the Software is
51
+ furnished to do so, subject to the following conditions:
52
+
53
+ The above copyright notice and this permission notice shall be included in all
54
+ copies or substantial portions of the Software.
55
+
56
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
57
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
58
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
59
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
60
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
61
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
62
+ SOFTWARE.
@@ -1,4 +1,4 @@
1
- import type { CdpParams, EvaluateResult, RemoteObject } from "./cdp.ts";
1
+ import { type CdpParams, type EvaluateResult, type RemoteObject } from "./cdp.ts";
2
2
  import { transformCellForEval } from "./transform.ts";
3
3
  import { type KernelTarget, type TargetEvents } from "./target.ts";
4
4
  /**
@@ -55,6 +55,6 @@ export declare class BunTarget implements KernelTarget {
55
55
  inspect(path: string, detail: number): Promise<string | null>;
56
56
  /** None: top-level declarations here are properties of globalThis. */
57
57
  lexicalNames(): Promise<string[]>;
58
- replyInput(value: string | null): void;
58
+ replyInput(value: string | null, interrupted?: boolean): void;
59
59
  kill(): void;
60
60
  }