@lopecode/channel 0.1.3 → 0.1.5
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 +205 -0
- package/claude-code-pairing-module.js +292 -139
- package/lopecode-channel.ts +224 -11
- package/package.json +3 -2
- package/sync-module.ts +190 -0
- package/inject-module.js +0 -131
package/README.md
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# @lopecode/channel
|
|
2
|
+
|
|
3
|
+
Pair program with Claude Code inside [Lopecode](https://tomlarkworthy.github.io/lopecode/) notebooks. An MCP server that bridges browser-based Observable notebooks and Claude Code via WebSocket, enabling real-time collaboration: chat, define cells, watch reactive variables, run tests, and manipulate the DOM — all from inside the notebook.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Requires Bun (https://bun.sh)
|
|
9
|
+
bun install -g @lopecode/channel
|
|
10
|
+
claude mcp add lopecode bunx @lopecode/channel
|
|
11
|
+
|
|
12
|
+
# Start Claude Code with channels enabled
|
|
13
|
+
claude --dangerously-load-development-channels server:lopecode
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Then ask Claude: **"Open a lopecode notebook"**
|
|
17
|
+
|
|
18
|
+
Claude gets a pairing token, opens the notebook in your browser, and auto-connects. No manual setup needed.
|
|
19
|
+
|
|
20
|
+
## What is Lopecode?
|
|
21
|
+
|
|
22
|
+
Lopecode notebooks are self-contained HTML files built on the [Observable runtime](https://github.com/observablehq/runtime). Each notebook contains:
|
|
23
|
+
|
|
24
|
+
- **Modules** — collections of reactive cells (code units)
|
|
25
|
+
- **Embedded dependencies** — everything needed to run, in a single file
|
|
26
|
+
- **A multi-panel UI** (lopepage) — view and edit multiple modules side by side
|
|
27
|
+
|
|
28
|
+
The Observable runtime provides **reactive dataflow**: cells automatically recompute when their dependencies change, similar to a spreadsheet.
|
|
29
|
+
|
|
30
|
+
## How Pairing Works
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
Browser (Notebook) ←→ WebSocket ←→ Channel Server (Bun) ←→ MCP stdio ←→ Claude Code
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
1. The channel server starts a local WebSocket server and generates a pairing token (`LOPE-PORT-XXXX`)
|
|
37
|
+
2. Claude opens a notebook URL with `&cc=TOKEN` in the hash
|
|
38
|
+
3. The notebook auto-connects to the WebSocket server
|
|
39
|
+
4. Claude can now use MCP tools to interact with the live notebook
|
|
40
|
+
|
|
41
|
+
## Observable Cell Syntax
|
|
42
|
+
|
|
43
|
+
Lopecode cells use [Observable JavaScript](https://observablehq.com/@observablehq/observable-javascript) syntax. Here's what you need to know:
|
|
44
|
+
|
|
45
|
+
### Named Cells
|
|
46
|
+
|
|
47
|
+
```javascript
|
|
48
|
+
// A cell is a named expression. It re-runs when dependencies change.
|
|
49
|
+
x = 42
|
|
50
|
+
greeting = `Hello, ${name}!` // depends on the 'name' cell
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Markdown
|
|
54
|
+
|
|
55
|
+
```javascript
|
|
56
|
+
// Use the md tagged template literal for rich text
|
|
57
|
+
md`# My Title
|
|
58
|
+
|
|
59
|
+
Some **bold** text and a list:
|
|
60
|
+
- Item 1
|
|
61
|
+
- Item 2
|
|
62
|
+
`
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### HTML
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
68
|
+
// Use htl.html for DOM elements
|
|
69
|
+
htl.html`<div style="color: red">Hello</div>`
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Imports
|
|
73
|
+
|
|
74
|
+
```javascript
|
|
75
|
+
// Import from other modules in the notebook
|
|
76
|
+
import {md} from "@tomlarkworthy/editable-md"
|
|
77
|
+
import {chart} from "@tomlarkworthy/my-visualization"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### viewof — Interactive Inputs
|
|
81
|
+
|
|
82
|
+
```javascript
|
|
83
|
+
// viewof creates two cells:
|
|
84
|
+
// "viewof slider" — the DOM element (a range input)
|
|
85
|
+
// "slider" — the current value (a number)
|
|
86
|
+
viewof slider = Inputs.range([0, 100], {label: "Value", value: 50})
|
|
87
|
+
|
|
88
|
+
// Other cells can depend on the value
|
|
89
|
+
doubled = slider * 2
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Common inputs: `Inputs.range`, `Inputs.select`, `Inputs.text`, `Inputs.toggle`, `Inputs.button`, `Inputs.table`.
|
|
93
|
+
|
|
94
|
+
### mutable — Imperative State
|
|
95
|
+
|
|
96
|
+
```javascript
|
|
97
|
+
// mutable allows imperative updates from other cells
|
|
98
|
+
mutable counter = 0
|
|
99
|
+
|
|
100
|
+
increment = {
|
|
101
|
+
mutable counter++;
|
|
102
|
+
return counter;
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Generators — Streaming Values
|
|
107
|
+
|
|
108
|
+
```javascript
|
|
109
|
+
// Yield successive values over time
|
|
110
|
+
ticker = {
|
|
111
|
+
let i = 0;
|
|
112
|
+
while (true) {
|
|
113
|
+
yield i++;
|
|
114
|
+
await Promises.delay(1000);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Block Cells
|
|
120
|
+
|
|
121
|
+
```javascript
|
|
122
|
+
// Use braces for multi-statement cells
|
|
123
|
+
result = {
|
|
124
|
+
const data = await fetch("https://api.example.com/data").then(r => r.json());
|
|
125
|
+
const filtered = data.filter(d => d.value > 10);
|
|
126
|
+
return filtered;
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Testing
|
|
131
|
+
|
|
132
|
+
Lopecode uses a reactive testing pattern. Any cell named `test_*` is a test:
|
|
133
|
+
|
|
134
|
+
```javascript
|
|
135
|
+
test_addition = {
|
|
136
|
+
const result = add(2, 2);
|
|
137
|
+
if (result !== 4) throw new Error(`Expected 4, got ${result}`);
|
|
138
|
+
return "2 + 2 = 4"; // shown on success
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
test_greeting = {
|
|
142
|
+
if (typeof greeting !== "string") throw new Error("Expected string");
|
|
143
|
+
return `greeting is: ${greeting}`;
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Tests pass if they don't throw. Use `run_tests` to execute all `test_*` cells.
|
|
148
|
+
|
|
149
|
+
## MCP Tools Reference
|
|
150
|
+
|
|
151
|
+
| Tool | Description |
|
|
152
|
+
|------|-------------|
|
|
153
|
+
| `get_pairing_token` | Get the session pairing token |
|
|
154
|
+
| `reply` | Send markdown to the notebook chat |
|
|
155
|
+
| `define_cell` | **Primary tool.** Define a cell using Observable source code |
|
|
156
|
+
| `list_cells` | List cells with names, inputs, and source |
|
|
157
|
+
| `get_variable` | Read a runtime variable's current value |
|
|
158
|
+
| `define_variable` | Low-level: define a variable with a function string |
|
|
159
|
+
| `delete_variable` | Remove a variable |
|
|
160
|
+
| `list_variables` | List all named variables |
|
|
161
|
+
| `create_module` | Create a new empty module |
|
|
162
|
+
| `delete_module` | Remove a module and all its variables |
|
|
163
|
+
| `watch_variable` | Subscribe to reactive updates |
|
|
164
|
+
| `unwatch_variable` | Unsubscribe from updates |
|
|
165
|
+
| `run_tests` | Run all `test_*` cells |
|
|
166
|
+
| `eval_code` | Run ephemeral JS in the browser (not persisted) |
|
|
167
|
+
| `export_notebook` | Save the notebook to disk (persists cells) |
|
|
168
|
+
| `fork_notebook` | Create a copy as a sibling HTML file |
|
|
169
|
+
|
|
170
|
+
### Tool Usage Tips
|
|
171
|
+
|
|
172
|
+
- **`define_cell`** is the main tool for creating content. It accepts Observable source and compiles it via the toolchain.
|
|
173
|
+
- **`eval_code`** is for throwaway actions (DOM hacks, debugging). Effects are lost on reload.
|
|
174
|
+
- **`define_variable`** is a low-level escape hatch — prefer `define_cell`.
|
|
175
|
+
- Always specify `module` when targeting a specific module.
|
|
176
|
+
- Use `export_notebook` after defining cells to persist them across reloads.
|
|
177
|
+
|
|
178
|
+
## Typical Workflow
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
1. create_module("@tomlarkworthy/my-app")
|
|
182
|
+
2. define_cell('import {md} from "@tomlarkworthy/editable-md"', module: "...")
|
|
183
|
+
3. define_cell('title = md`# My App`', module: "...")
|
|
184
|
+
4. define_cell('viewof name = Inputs.text({label: "Name"})', module: "...")
|
|
185
|
+
5. define_cell('greeting = md`Hello, **${name}**!`', module: "...")
|
|
186
|
+
6. export_notebook() // persist to disk
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Starting from a Notebook
|
|
190
|
+
|
|
191
|
+
If you see the `@tomlarkworthy/claude-code-pairing` panel in a notebook but Claude isn't connected:
|
|
192
|
+
|
|
193
|
+
1. Install Bun: https://bun.sh
|
|
194
|
+
2. Install the plugin: `bun install -g @lopecode/channel`
|
|
195
|
+
3. Register with Claude: `claude mcp add lopecode bunx @lopecode/channel`
|
|
196
|
+
4. Start Claude: `claude --dangerously-load-development-channels server:lopecode`
|
|
197
|
+
5. Ask Claude to connect — it will provide a URL with an auto-connect token
|
|
198
|
+
|
|
199
|
+
## Environment Variables
|
|
200
|
+
|
|
201
|
+
- `LOPECODE_PORT` — WebSocket server port (default: random free port)
|
|
202
|
+
|
|
203
|
+
## License
|
|
204
|
+
|
|
205
|
+
MIT
|