jupyter-link 0.2.0 → 0.2.1

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/SKILL.md ADDED
@@ -0,0 +1,180 @@
1
+ ---
2
+ name: jupyter-link
3
+ description: Execute code in running Jupyter kernels and persist outputs to the target notebook via Jupyter Server REST API and kernel WebSocket channels. Implements discovery of sessions, cell insert/update, execution, and output mapping (nbformat v4).
4
+ compatibility: Requires Node.js 20+ and npm
5
+ allowed-tools: Bash(npx jupyter-link:*)
6
+ metadata:
7
+ version: "0.2.1"
8
+ author: Roberto Arce
9
+ ---
10
+
11
+ ## IMPORTANT: Always use the `jupyter-link` CLI via npx
12
+
13
+ **NEVER use Python, curl, or raw HTTP requests to interact with Jupyter Server.**
14
+ All operations MUST go through `npx jupyter-link@0.2.1`. Every command reads JSON from stdin and writes JSON to stdout.
15
+
16
+ ## Commands Reference
17
+
18
+ ### Configure connection (persistent — run once)
19
+ ```bash
20
+ # Save URL and token to ~/.config/jupyter-link/config.json
21
+ echo '{"url":"http://localhost:8888","token":"your-token-here"}' | npx jupyter-link@0.2.1 config:set
22
+
23
+ # Show effective config and where each value comes from
24
+ echo '{}' | npx jupyter-link@0.2.1 config:get
25
+ ```
26
+ After `config:set`, all subsequent commands use the saved config. No need to pass env vars.
27
+ Environment variables (`JUPYTER_URL`, `JUPYTER_TOKEN`) still override the config file if set.
28
+
29
+ ### Check connectivity
30
+ ```bash
31
+ echo '{}' | npx jupyter-link@0.2.1 check:env
32
+ ```
33
+ Returns `{"ok":true|false, "sessions_ok":..., "contents_ok":...}`
34
+
35
+ ### Create a notebook
36
+ ```bash
37
+ # Create an empty Python3 notebook (generates nbformat v4 boilerplate)
38
+ echo '{"path":"notebooks/new.ipynb"}' | npx jupyter-link@0.2.1 contents:create
39
+
40
+ # Create with a specific kernel
41
+ echo '{"path":"notebooks/new.ipynb","kernel_name":"julia-1.9"}' | npx jupyter-link@0.2.1 contents:create
42
+ ```
43
+ Returns `{"ok":true,"created":true|false,"path":"..."}`. If notebook already exists, returns `created:false` without overwriting.
44
+
45
+ ### Create a session (start a kernel)
46
+ ```bash
47
+ echo '{"path":"notebooks/my.ipynb"}' | npx jupyter-link@0.2.1 sessions:create
48
+ echo '{"path":"notebooks/my.ipynb","kernel_name":"python3"}' | npx jupyter-link@0.2.1 sessions:create
49
+ ```
50
+ Returns the full Jupyter session object with `id`, `kernel.id`, `kernel.name`, etc. If a session already exists for the notebook, returns it without creating a duplicate.
51
+
52
+ ### List sessions
53
+ ```bash
54
+ echo '{}' | npx jupyter-link@0.2.1 list:sessions
55
+ echo '{"path":"notebooks/my.ipynb"}' | npx jupyter-link@0.2.1 list:sessions
56
+ ```
57
+
58
+ ### Read cells (preferred for inspection)
59
+ ```bash
60
+ # Summary of all cells (index, type, source preview, has_outputs)
61
+ echo '{"path":"notebooks/my.ipynb"}' | npx jupyter-link@0.2.1 cell:read
62
+
63
+ # Read specific cells by index
64
+ echo '{"path":"notebooks/my.ipynb","cells":[4,8,10,12]}' | npx jupyter-link@0.2.1 cell:read
65
+
66
+ # Read a single cell
67
+ echo '{"path":"notebooks/my.ipynb","cell_id":4}' | npx jupyter-link@0.2.1 cell:read
68
+
69
+ # Read a range of cells (start inclusive, end exclusive)
70
+ echo '{"path":"notebooks/my.ipynb","range":[4,10]}' | npx jupyter-link@0.2.1 cell:read
71
+
72
+ # Control output truncation (default: 3000 chars per field)
73
+ echo '{"path":"notebooks/my.ipynb","cells":[4],"max_chars":5000}' | npx jupyter-link@0.2.1 cell:read
74
+ ```
75
+ Returns `{"total_cells":N,"cells":[...]}` with source, outputs, execution_count, and agent metadata.
76
+ Binary outputs (images, PDFs) are replaced with size placeholders. Error tracebacks keep last 5 frames.
77
+
78
+ ### Read notebook (full JSON)
79
+ ```bash
80
+ echo '{"path":"notebooks/my.ipynb"}' | npx jupyter-link@0.2.1 contents:read
81
+ ```
82
+
83
+ ### Write notebook
84
+ ```bash
85
+ echo '{"path":"notebooks/my.ipynb","nb_json":{...}}' | npx jupyter-link@0.2.1 contents:write
86
+ ```
87
+
88
+ ### Insert a code cell
89
+ ```bash
90
+ echo '{"path":"notebooks/my.ipynb","code":"print(42)"}' | npx jupyter-link@0.2.1 cell:insert
91
+ echo '{"path":"notebooks/my.ipynb","code":"print(42)","index":0}' | npx jupyter-link@0.2.1 cell:insert
92
+ ```
93
+ Returns `{"cell_id":N,"index":N}`. Defaults to appending at end.
94
+
95
+ ### Update a cell
96
+ ```bash
97
+ echo '{"path":"notebooks/my.ipynb","cell_id":3,"code":"x=1"}' | npx jupyter-link@0.2.1 cell:update
98
+ echo '{"path":"notebooks/my.ipynb","cell_id":3,"outputs":[...],"execution_count":5}' | npx jupyter-link@0.2.1 cell:update
99
+ ```
100
+ If `cell_id` is omitted, updates the latest agent-managed cell.
101
+
102
+ ### Open kernel channels (persistent WebSocket)
103
+ ```bash
104
+ echo '{"path":"notebooks/my.ipynb"}' | npx jupyter-link@0.2.1 open:kernel-channels
105
+ echo '{"kernel_id":"..."}' | npx jupyter-link@0.2.1 open:kernel-channels
106
+ ```
107
+ Returns `{"channel_ref":"ch-...","session_id":"..."}`. Auto-starts daemon if needed.
108
+ **Auto-creates session**: If no running session exists for the notebook, automatically creates one (defaults to `python3` kernel, override with `kernel_name`).
109
+
110
+ ### Run cell (insert + execute + collect + update in one step)
111
+ ```bash
112
+ echo '{"path":"notebooks/my.ipynb","channel_ref":"ch-...","code":"print(42)"}' | npx jupyter-link@0.2.1 run:cell
113
+ echo '{"path":"notebooks/my.ipynb","channel_ref":"ch-...","code":"import time; time.sleep(5)","timeout_s":30}' | npx jupyter-link@0.2.1 run:cell
114
+ ```
115
+ Returns `{"cell_id":N,"status":"ok"|"error","execution_count":N,"outputs":[...]}`.
116
+ This is the **recommended** way to execute code — it handles the full pipeline: insert cell, execute on kernel, collect outputs, and update the cell with results.
117
+
118
+ ### Execute code on a channel
119
+ ```bash
120
+ echo '{"channel_ref":"ch-...","code":"print(123)"}' | npx jupyter-link@0.2.1 execute:code
121
+ ```
122
+ Returns `{"parent_msg_id":"msg-..."}`.
123
+
124
+ ### Collect execution outputs
125
+ ```bash
126
+ echo '{"channel_ref":"ch-...","parent_msg_id":"msg-..."}' | npx jupyter-link@0.2.1 collect:outputs
127
+ echo '{"channel_ref":"ch-...","parent_msg_id":"msg-...","timeout_s":120}' | npx jupyter-link@0.2.1 collect:outputs
128
+ ```
129
+ Returns `{"outputs":[...],"execution_count":N,"status":"ok"|"error"|"timeout"}`.
130
+
131
+ ### Close channels
132
+ ```bash
133
+ echo '{"channel_ref":"ch-..."}' | npx jupyter-link@0.2.1 close:channels
134
+ ```
135
+
136
+ ### Save notebook
137
+ ```bash
138
+ echo '{"path":"notebooks/my.ipynb"}' | npx jupyter-link@0.2.1 save:notebook
139
+ ```
140
+
141
+ ## Typical workflow
142
+
143
+ ### Recommended (using `run:cell`)
144
+ 1. **Configure**: `echo '{"url":"...","token":"..."}' | npx jupyter-link@0.2.1 config:set`
145
+ 2. **Check env**: `echo '{}' | npx jupyter-link@0.2.1 check:env`
146
+ 3. **Create notebook** (if needed): `echo '{"path":"..."}' | npx jupyter-link@0.2.1 contents:create`
147
+ 4. **Open channel**: `echo '{"path":"..."}' | npx jupyter-link@0.2.1 open:kernel-channels` → get `channel_ref` (auto-creates session if needed)
148
+ 5. **Run cell** (repeat): `echo '{"path":"...","channel_ref":"...","code":"..."}' | npx jupyter-link@0.2.1 run:cell` → get `cell_id`, `status`, `outputs`
149
+ 6. **Save**: `echo '{"path":"..."}' | npx jupyter-link@0.2.1 save:notebook`
150
+ 7. **Close**: `echo '{"channel_ref":"..."}' | npx jupyter-link@0.2.1 close:channels`
151
+
152
+ ### Granular (step-by-step control)
153
+ 1. **Configure**: `echo '{"url":"...","token":"..."}' | npx jupyter-link@0.2.1 config:set`
154
+ 2. **Check env**: `echo '{}' | npx jupyter-link@0.2.1 check:env`
155
+ 3. **Create notebook** (if needed): `echo '{"path":"..."}' | npx jupyter-link@0.2.1 contents:create`
156
+ 4. **Create session** (if needed): `echo '{"path":"..."}' | npx jupyter-link@0.2.1 sessions:create`
157
+ 5. **Open channel**: `echo '{"path":"..."}' | npx jupyter-link@0.2.1 open:kernel-channels` → get `channel_ref`
158
+ 6. **Insert cell**: `echo '{"path":"...","code":"..."}' | npx jupyter-link@0.2.1 cell:insert` → get `index`
159
+ 7. **Execute**: `echo '{"channel_ref":"...","code":"..."}' | npx jupyter-link@0.2.1 execute:code` → get `parent_msg_id`
160
+ 8. **Collect**: `echo '{"channel_ref":"...","parent_msg_id":"..."}' | npx jupyter-link@0.2.1 collect:outputs` → get outputs
161
+ 9. **Update cell**: `echo '{"path":"...","cell_id":N,"outputs":[...],"execution_count":N}' | npx jupyter-link@0.2.1 cell:update`
162
+ 10. **Save**: `echo '{"path":"..."}' | npx jupyter-link@0.2.1 save:notebook`
163
+ 11. **Close**: `echo '{"channel_ref":"..."}' | npx jupyter-link@0.2.1 close:channels`
164
+
165
+ ## Notes
166
+
167
+ - Inserts cells at end by default. Reuses latest agent cell (`metadata.agent.role="jupyter-driver"`) when `cell_id` is omitted in update.
168
+ - Kernel errors are surfaced as `error` outputs with traceback.
169
+ - Persistent channels are managed by a daemon on `127.0.0.1:${JUPYTER_LINK_PORT:-32123}`. Auto-starts on first use.
170
+
171
+ ## Canonical parameter names
172
+
173
+ | Primary | Fallback | Used in |
174
+ |----------------|--------------|-------------------------------------|
175
+ | `path` | `notebook` | All notebook commands |
176
+ | `code` | `source` | insert, update, execute, run:cell |
177
+ | `channel_ref` | `ref` | execute, collect, close, run:cell |
178
+ | `parent_msg_id`| `parent_id` | collect |
179
+ | `nb_json` | `content` | write |
180
+ | `kernel_name` | `kernel` | sessions:create, contents:create, open:kernel-channels |
@@ -84,14 +84,14 @@
84
84
  "env.mjs"
85
85
  ]
86
86
  },
87
- "close:channels": {
87
+ "collect:outputs": {
88
88
  "aliases": [],
89
89
  "args": {},
90
- "description": "Close a previously opened channel_ref",
90
+ "description": "Wait for outputs/reply/idle for a parent_msg_id on a channel",
91
91
  "flags": {},
92
92
  "hasDynamicHelp": false,
93
93
  "hiddenAliases": [],
94
- "id": "close:channels",
94
+ "id": "collect:outputs",
95
95
  "pluginAlias": "jupyter-link",
96
96
  "pluginName": "jupyter-link",
97
97
  "pluginType": "core",
@@ -101,18 +101,18 @@
101
101
  "relativePath": [
102
102
  "src",
103
103
  "commands",
104
- "close",
105
- "channels.mjs"
104
+ "collect",
105
+ "outputs.mjs"
106
106
  ]
107
107
  },
108
- "collect:outputs": {
108
+ "close:channels": {
109
109
  "aliases": [],
110
110
  "args": {},
111
- "description": "Wait for outputs/reply/idle for a parent_msg_id on a channel",
111
+ "description": "Close a previously opened channel_ref",
112
112
  "flags": {},
113
113
  "hasDynamicHelp": false,
114
114
  "hiddenAliases": [],
115
- "id": "collect:outputs",
115
+ "id": "close:channels",
116
116
  "pluginAlias": "jupyter-link",
117
117
  "pluginName": "jupyter-link",
118
118
  "pluginType": "core",
@@ -122,8 +122,8 @@
122
122
  "relativePath": [
123
123
  "src",
124
124
  "commands",
125
- "collect",
126
- "outputs.mjs"
125
+ "close",
126
+ "channels.mjs"
127
127
  ]
128
128
  },
129
129
  "contents:create": {
@@ -231,14 +231,14 @@
231
231
  "code.mjs"
232
232
  ]
233
233
  },
234
- "open:kernel-channels": {
234
+ "run:cell": {
235
235
  "aliases": [],
236
236
  "args": {},
237
- "description": "Open persistent kernel WS channels and return a channel_ref",
237
+ "description": "Insert a cell, execute it, collect outputs, and update the cell in one step",
238
238
  "flags": {},
239
239
  "hasDynamicHelp": false,
240
240
  "hiddenAliases": [],
241
- "id": "open:kernel-channels",
241
+ "id": "run:cell",
242
242
  "pluginAlias": "jupyter-link",
243
243
  "pluginName": "jupyter-link",
244
244
  "pluginType": "core",
@@ -248,18 +248,18 @@
248
248
  "relativePath": [
249
249
  "src",
250
250
  "commands",
251
- "open",
252
- "kernel-channels.mjs"
251
+ "run",
252
+ "cell.mjs"
253
253
  ]
254
254
  },
255
- "config:get": {
255
+ "open:kernel-channels": {
256
256
  "aliases": [],
257
257
  "args": {},
258
- "description": "Show effective configuration (env vars > config file > defaults)",
258
+ "description": "Open persistent kernel WS channels and return a channel_ref",
259
259
  "flags": {},
260
260
  "hasDynamicHelp": false,
261
261
  "hiddenAliases": [],
262
- "id": "config:get",
262
+ "id": "open:kernel-channels",
263
263
  "pluginAlias": "jupyter-link",
264
264
  "pluginName": "jupyter-link",
265
265
  "pluginType": "core",
@@ -269,18 +269,18 @@
269
269
  "relativePath": [
270
270
  "src",
271
271
  "commands",
272
- "config",
273
- "get.mjs"
272
+ "open",
273
+ "kernel-channels.mjs"
274
274
  ]
275
275
  },
276
- "config:set": {
276
+ "config:get": {
277
277
  "aliases": [],
278
278
  "args": {},
279
- "description": "Save Jupyter connection settings to ~/.config/jupyter-link/config.json",
279
+ "description": "Show effective configuration (env vars > config file > defaults)",
280
280
  "flags": {},
281
281
  "hasDynamicHelp": false,
282
282
  "hiddenAliases": [],
283
- "id": "config:set",
283
+ "id": "config:get",
284
284
  "pluginAlias": "jupyter-link",
285
285
  "pluginName": "jupyter-link",
286
286
  "pluginType": "core",
@@ -291,17 +291,17 @@
291
291
  "src",
292
292
  "commands",
293
293
  "config",
294
- "set.mjs"
294
+ "get.mjs"
295
295
  ]
296
296
  },
297
- "run:cell": {
297
+ "config:set": {
298
298
  "aliases": [],
299
299
  "args": {},
300
- "description": "Insert a cell, execute it, collect outputs, and update the cell in one step",
300
+ "description": "Save Jupyter connection settings to ~/.config/jupyter-link/config.json",
301
301
  "flags": {},
302
302
  "hasDynamicHelp": false,
303
303
  "hiddenAliases": [],
304
- "id": "run:cell",
304
+ "id": "config:set",
305
305
  "pluginAlias": "jupyter-link",
306
306
  "pluginName": "jupyter-link",
307
307
  "pluginType": "core",
@@ -311,8 +311,8 @@
311
311
  "relativePath": [
312
312
  "src",
313
313
  "commands",
314
- "run",
315
- "cell.mjs"
314
+ "config",
315
+ "set.mjs"
316
316
  ]
317
317
  },
318
318
  "save:notebook": {
@@ -358,5 +358,5 @@
358
358
  ]
359
359
  }
360
360
  },
361
- "version": "0.2.0"
361
+ "version": "0.2.1"
362
362
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jupyter-link",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "CLI and AgentSkill to execute code in Jupyter kernels and persist outputs to notebooks",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -27,7 +27,9 @@
27
27
  "bin",
28
28
  "src",
29
29
  "scripts",
30
- "oclif.manifest.json"
30
+ "oclif.manifest.json",
31
+ "skills.json",
32
+ "SKILL.md"
31
33
  ],
32
34
  "dependencies": {
33
35
  "@oclif/core": "^3.26.3"
package/skills.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "jupyter-link",
3
+ "version": "0.2.1",
4
+ "description": "Execute code in Jupyter kernels and persist outputs to notebooks",
5
+ "using": "scripts",
6
+ "environment": {
7
+ "JUPYTER_URL": {"description": "Base URL, e.g. http://127.0.0.1:8888", "required": false},
8
+ "JUPYTER_TOKEN": {"description": "Server token if required", "required": false}
9
+ },
10
+ "tools": {
11
+ "config_set": { "description": "Save Jupyter connection settings (url, token, port) to persistent config file", "command": "npx jupyter-link@0.2.1 config:set", "stdin": "json", "stdout": "json" },
12
+ "config_get": { "description": "Show effective configuration with source (env/file/default) for each field", "command": "npx jupyter-link@0.2.1 config:get", "stdin": "json", "stdout": "json" },
13
+ "check_env": { "description": "Verify connectivity and basic Jupyter Server compatibility", "command": "npx jupyter-link@0.2.1 check:env", "stdin": "json", "stdout": "json" },
14
+ "list_sessions": { "description": "List sessions and optionally filter by path or name", "command": "npx jupyter-link@0.2.1 list:sessions", "stdin": "json", "stdout": "json" },
15
+ "read_notebook": { "description": "Read a notebook JSON via Contents API", "command": "npx jupyter-link@0.2.1 contents:read", "stdin": "json", "stdout": "json" },
16
+ "write_notebook": { "description": "Write notebook JSON via Contents API", "command": "npx jupyter-link@0.2.1 contents:write", "stdin": "json", "stdout": "json" },
17
+ "read_cell": { "description": "Read specific cells with source, outputs, and metadata. Supports single cell, list, range, or full summary", "command": "npx jupyter-link@0.2.1 cell:read", "stdin": "json", "stdout": "json" },
18
+ "insert_cell": { "description": "Insert a code cell with agent metadata", "command": "npx jupyter-link@0.2.1 cell:insert", "stdin": "json", "stdout": "json" },
19
+ "update_cell": { "description": "Update a code cell (source/outputs/execution_count/metadata)", "command": "npx jupyter-link@0.2.1 cell:update", "stdin": "json", "stdout": "json" },
20
+ "open_kernel_channels": { "description": "Open persistent kernel WS channels and return a channel_ref. Auto-creates session if none exists for the notebook", "command": "npx jupyter-link@0.2.1 open:kernel-channels", "stdin": "json", "stdout": "json" },
21
+ "execute_code": { "description": "Send execute_request on an open channel and return parent_msg_id", "command": "npx jupyter-link@0.2.1 execute:code", "stdin": "json", "stdout": "json" },
22
+ "collect_outputs": { "description": "Wait for outputs/reply/idle for a parent_msg_id on a channel", "command": "npx jupyter-link@0.2.1 collect:outputs", "stdin": "json", "stdout": "json" },
23
+ "close_channels": { "description": "Close a previously opened channel_ref", "command": "npx jupyter-link@0.2.1 close:channels", "stdin": "json", "stdout": "json" },
24
+ "save_notebook": { "description": "Save notebook (round-trip PUT)", "command": "npx jupyter-link@0.2.1 save:notebook", "stdin": "json", "stdout": "json" },
25
+ "create_notebook": { "description": "Create a new empty notebook via Contents API. Idempotent (returns existing if found). Accepts kernel_name, language, display_name", "command": "npx jupyter-link@0.2.1 contents:create", "stdin": "json", "stdout": "json" },
26
+ "create_session": { "description": "Create a Jupyter session (start kernel) for a notebook. Idempotent (returns existing session if found)", "command": "npx jupyter-link@0.2.1 sessions:create", "stdin": "json", "stdout": "json" },
27
+ "run_cell": { "description": "Insert, execute, and collect outputs for a cell in one compound command. Returns cell index, outputs, and execution status", "command": "npx jupyter-link@0.2.1 run:cell", "stdin": "json", "stdout": "json" },
28
+ "test_api": { "description": "Validate Jupyter Server REST API compatibility using the official OpenAPI schema", "command": "node scripts/test_api.mjs", "stdin": "json", "stdout": "json" }
29
+ }
30
+ }