@lopecode/channel 0.1.1 → 0.1.3
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/claude-code-pairing-module.js +50 -29
- package/lopecode-channel.ts +45 -51
- package/package.json +1 -1
|
@@ -26,24 +26,38 @@ const _cc_watches = function _cc_watches(Inputs){return(
|
|
|
26
26
|
Inputs.input([])
|
|
27
27
|
)};
|
|
28
28
|
|
|
29
|
-
const _cc_ws = function _cc_ws(cc_config, cc_notebook_id, cc_status, cc_messages, viewof_cc_watches, summarizeJS, observe, invalidation){return(
|
|
29
|
+
const _cc_ws = function _cc_ws(cc_config, cc_notebook_id, cc_status, cc_messages, viewof_cc_watches, summarizeJS, observe, realize, runtime, invalidation){return(
|
|
30
30
|
(function() {
|
|
31
31
|
var port = cc_config.port, host = cc_config.host;
|
|
32
32
|
var ws = null;
|
|
33
33
|
var paired = false;
|
|
34
34
|
|
|
35
|
+
|
|
35
36
|
function serializeValue(value, maxLen) {
|
|
36
37
|
maxLen = maxLen || 500;
|
|
37
38
|
try { return String(summarizeJS(value)).slice(0, maxLen); }
|
|
38
39
|
catch(e) { return String(value).slice(0, maxLen); }
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
// Framework modules that should not be the default target for define_variable
|
|
43
|
+
var FRAMEWORK_MODULES = new Set([
|
|
44
|
+
"bootloader", "builtin",
|
|
45
|
+
"@tomlarkworthy/lopepage",
|
|
46
|
+
"@tomlarkworthy/claude-code-pairing",
|
|
47
|
+
"@tomlarkworthy/module-selection"
|
|
48
|
+
]);
|
|
49
|
+
|
|
41
50
|
function findModule(runtime, moduleName) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (
|
|
46
|
-
return
|
|
51
|
+
// Use runtime.mains (Map<name, Module>) — the actual module refs used by the runtime
|
|
52
|
+
var mains = runtime.mains;
|
|
53
|
+
if (mains && mains instanceof Map) {
|
|
54
|
+
if (moduleName) {
|
|
55
|
+
return mains.get(moduleName) || null;
|
|
56
|
+
}
|
|
57
|
+
// No name specified — return the first non-framework main
|
|
58
|
+
for (var entry of mains) {
|
|
59
|
+
if (!FRAMEWORK_MODULES.has(entry[0])) return entry[1];
|
|
60
|
+
}
|
|
47
61
|
}
|
|
48
62
|
return null;
|
|
49
63
|
}
|
|
@@ -88,31 +102,36 @@ const _cc_ws = function _cc_ws(cc_config, cc_notebook_id, cc_status, cc_messages
|
|
|
88
102
|
var definition = cmd.params.definition;
|
|
89
103
|
var inputs = cmd.params.inputs || [];
|
|
90
104
|
var moduleName = cmd.params.module;
|
|
91
|
-
var
|
|
92
|
-
if (!
|
|
105
|
+
var mod = runtime.mains && runtime.mains.get(moduleName);
|
|
106
|
+
if (!mod && runtime.mains) {
|
|
107
|
+
for (var entry of runtime.mains) {
|
|
108
|
+
if (!FRAMEWORK_MODULES.has(entry[0])) { mod = entry[1]; break; }
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (!mod) return { ok: false, error: "Module not found: " + (moduleName || "default") };
|
|
93
112
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
eval("fn = " + definition);
|
|
113
|
+
return realize([definition], runtime).then(function(results) {
|
|
114
|
+
var fn = results[0];
|
|
97
115
|
if (typeof fn !== "function") return { ok: false, error: "Definition must evaluate to a function" };
|
|
98
|
-
} catch (e) {
|
|
99
|
-
return { ok: false, error: "Failed to parse definition: " + e.message };
|
|
100
|
-
}
|
|
101
116
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
117
|
+
var existingVar = null;
|
|
118
|
+
for (var v of runtime._variables) {
|
|
119
|
+
if (v._name === name && v._module === mod) { existingVar = v; break; }
|
|
120
|
+
}
|
|
106
121
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
else {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
122
|
+
if (existingVar) {
|
|
123
|
+
existingVar.define(name, inputs, fn);
|
|
124
|
+
} else {
|
|
125
|
+
var obsFactory = null;
|
|
126
|
+
for (var v of runtime._variables) {
|
|
127
|
+
if (v._name === "__ojs_observer" && typeof v._value === "function") { obsFactory = v._value; break; }
|
|
128
|
+
}
|
|
129
|
+
mod.variable(obsFactory ? obsFactory(name) : {}).define(name, inputs, fn);
|
|
130
|
+
}
|
|
131
|
+
return { ok: true, result: { success: true, name: name, module: moduleName || "default" } };
|
|
132
|
+
}).catch(function(e) {
|
|
133
|
+
return { ok: false, error: "define failed: " + e.message };
|
|
134
|
+
});
|
|
116
135
|
}
|
|
117
136
|
|
|
118
137
|
case "delete-variable": {
|
|
@@ -381,7 +400,7 @@ const _cc_ws = function _cc_ws(cc_config, cc_notebook_id, cc_status, cc_messages
|
|
|
381
400
|
function handleFork(runtime) {
|
|
382
401
|
return new Promise(function(resolve) {
|
|
383
402
|
for (var v of runtime._variables) {
|
|
384
|
-
if (v._name === "_exportToHTML" && typeof v._value === "function") {
|
|
403
|
+
if ((v._name === "_exportToHTML" || v._name === "exportToHTML") && typeof v._value === "function") {
|
|
385
404
|
try {
|
|
386
405
|
Promise.resolve(v._value()).then(function(html) {
|
|
387
406
|
resolve({ ok: true, result: { html: html } });
|
|
@@ -769,7 +788,7 @@ export default function define(runtime, observer) {
|
|
|
769
788
|
$def("_cc_messages", "cc_messages", ["Inputs"], _cc_messages);
|
|
770
789
|
$def("_cc_watches", "viewof cc_watches", ["Inputs"], _cc_watches);
|
|
771
790
|
main.variable().define("cc_watches", ["Generators", "viewof cc_watches"], (G, v) => G.input(v));
|
|
772
|
-
$def("_cc_ws", "cc_ws", ["cc_config","cc_notebook_id","cc_status","cc_messages","viewof cc_watches","summarizeJS","observe","invalidation"], _cc_ws);
|
|
791
|
+
$def("_cc_ws", "cc_ws", ["cc_config","cc_notebook_id","cc_status","cc_messages","viewof cc_watches","summarizeJS","observe","realize","runtime","invalidation"], _cc_ws);
|
|
773
792
|
$def("_cc_change_forwarder", "cc_change_forwarder", ["cc_ws","invalidation"], _cc_change_forwarder);
|
|
774
793
|
|
|
775
794
|
// Imports
|
|
@@ -780,6 +799,8 @@ export default function define(runtime, observer) {
|
|
|
780
799
|
main.define("viewof runtime_variables", ["module @tomlarkworthy/runtime-sdk", "@variable"], (_, v) => v.import("viewof runtime_variables", _));
|
|
781
800
|
main.define("runtime_variables", ["module @tomlarkworthy/runtime-sdk", "@variable"], (_, v) => v.import("runtime_variables", _));
|
|
782
801
|
main.define("observe", ["module @tomlarkworthy/runtime-sdk", "@variable"], (_, v) => v.import("observe", _));
|
|
802
|
+
main.define("realize", ["module @tomlarkworthy/runtime-sdk", "@variable"], (_, v) => v.import("realize", _));
|
|
803
|
+
main.define("runtime", ["module @tomlarkworthy/runtime-sdk", "@variable"], (_, v) => v.import("runtime", _));
|
|
783
804
|
main.define("module d/57d79353bac56631@44", async () => runtime.module((await import("/d/57d79353bac56631@44.js?v=4")).default));
|
|
784
805
|
main.define("hash", ["module d/57d79353bac56631@44", "@variable"], (_, v) => v.import("hash", _));
|
|
785
806
|
main.define("module @tomlarkworthy/summarizejs", async () => runtime.module((await import("/@tomlarkworthy/summarizejs.js?v=4")).default));
|
package/lopecode-channel.ts
CHANGED
|
@@ -16,9 +16,10 @@ import type { ServerWebSocket } from "bun";
|
|
|
16
16
|
import { join, dirname, basename } from "path";
|
|
17
17
|
|
|
18
18
|
// --- Configuration ---
|
|
19
|
-
const
|
|
19
|
+
const REQUESTED_PORT = Number(process.env.LOPECODE_PORT ?? 0); // 0 = OS picks a free port
|
|
20
|
+
let PORT = REQUESTED_PORT;
|
|
20
21
|
|
|
21
|
-
// --- Pairing token ---
|
|
22
|
+
// --- Pairing token (generated after port binding) ---
|
|
22
23
|
function generateToken(): string {
|
|
23
24
|
const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; // no O/0/I/1
|
|
24
25
|
let code = "";
|
|
@@ -26,7 +27,7 @@ function generateToken(): string {
|
|
|
26
27
|
return `LOPE-${PORT}-${code}`;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
let PAIRING_TOKEN = "";
|
|
30
31
|
|
|
31
32
|
// --- State ---
|
|
32
33
|
type ConnectionMeta = { url: string; title: string; modules?: string[] };
|
|
@@ -94,6 +95,8 @@ Variable updates (when watching):
|
|
|
94
95
|
- eval_code: Evaluate JS in browser context
|
|
95
96
|
- fork_notebook: Create a copy as sibling HTML file
|
|
96
97
|
|
|
98
|
+
IMPORTANT: Always specify the module parameter when calling define_variable, get_variable, etc.
|
|
99
|
+
Use the currentModules watch to identify the user's content module (not lopepage, module-selection, or claude-code-pairing).
|
|
97
100
|
When multiple notebooks are connected, specify notebook_id (the URL). When only one is connected, it's used automatically.`,
|
|
98
101
|
}
|
|
99
102
|
);
|
|
@@ -307,15 +310,19 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
307
310
|
action = "get-variable";
|
|
308
311
|
params = { name: args.name, module: args.module || null };
|
|
309
312
|
break;
|
|
310
|
-
case "define_variable":
|
|
313
|
+
case "define_variable": {
|
|
311
314
|
action = "define-variable";
|
|
315
|
+
let inputs = args.inputs;
|
|
316
|
+
if (typeof inputs === "string") inputs = JSON.parse(inputs);
|
|
317
|
+
if (!Array.isArray(inputs)) inputs = [];
|
|
312
318
|
params = {
|
|
313
319
|
name: args.name,
|
|
314
320
|
definition: args.definition,
|
|
315
|
-
inputs
|
|
321
|
+
inputs,
|
|
316
322
|
module: args.module || null,
|
|
317
323
|
};
|
|
318
324
|
break;
|
|
325
|
+
}
|
|
319
326
|
case "delete_variable":
|
|
320
327
|
action = "delete-variable";
|
|
321
328
|
params = { name: args.name, module: args.module || null };
|
|
@@ -534,53 +541,40 @@ function handleWsClose(ws: ServerWebSocket<unknown>) {
|
|
|
534
541
|
await mcp.connect(new StdioServerTransport());
|
|
535
542
|
|
|
536
543
|
// Start WebSocket + HTTP server
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
if (
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
websocket: {
|
|
565
|
-
open(ws) {
|
|
566
|
-
pendingConnections.add(ws);
|
|
567
|
-
},
|
|
568
|
-
message: handleWsMessage,
|
|
569
|
-
close: handleWsClose,
|
|
544
|
+
const server = Bun.serve({
|
|
545
|
+
port: REQUESTED_PORT,
|
|
546
|
+
hostname: "127.0.0.1",
|
|
547
|
+
fetch(req, server) {
|
|
548
|
+
const url = new URL(req.url);
|
|
549
|
+
if (url.pathname === "/ws") {
|
|
550
|
+
if (server.upgrade(req)) return;
|
|
551
|
+
return new Response("WebSocket upgrade failed", { status: 400 });
|
|
552
|
+
}
|
|
553
|
+
if (url.pathname === "/health") {
|
|
554
|
+
return new Response(JSON.stringify({
|
|
555
|
+
paired: pairedConnections.size,
|
|
556
|
+
pending: pendingConnections.size,
|
|
557
|
+
}), { headers: { "content-type": "application/json" } });
|
|
558
|
+
}
|
|
559
|
+
if (url.pathname === "/") {
|
|
560
|
+
const notebookUrl = `https://tomlarkworthy.github.io/lopecode/notebooks/@tomlarkworthy_blank-notebook.html#view=R100(S50(@tomlarkworthy/blank-notebook),S25(@tomlarkworthy/module-selection),S25(@tomlarkworthy/claude-code-pairing))&cc=${PAIRING_TOKEN}`;
|
|
561
|
+
return new Response(null, {
|
|
562
|
+
status: 302,
|
|
563
|
+
headers: { Location: notebookUrl },
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
return new Response("lopecode-channel", { status: 200 });
|
|
567
|
+
},
|
|
568
|
+
websocket: {
|
|
569
|
+
open(ws) {
|
|
570
|
+
pendingConnections.add(ws);
|
|
570
571
|
},
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
process.stderr.write(
|
|
576
|
-
`lopecode-channel: ERROR — port ${PORT} is already in use.\n` +
|
|
577
|
-
`Another lopecode-channel or other service is running on this port.\n` +
|
|
578
|
-
`Kill the existing process or set LOPECODE_PORT=<other port>.\n`
|
|
579
|
-
);
|
|
580
|
-
process.exit(1);
|
|
581
|
-
}
|
|
582
|
-
throw err;
|
|
583
|
-
}
|
|
572
|
+
message: handleWsMessage,
|
|
573
|
+
close: handleWsClose,
|
|
574
|
+
},
|
|
575
|
+
});
|
|
584
576
|
|
|
577
|
+
PORT = server.port; // read actual port (important when REQUESTED_PORT is 0)
|
|
578
|
+
PAIRING_TOKEN = generateToken();
|
|
585
579
|
process.stderr.write(`lopecode-channel: pairing token: ${PAIRING_TOKEN}\n`);
|
|
586
580
|
process.stderr.write(`lopecode-channel: WebSocket server on ws://127.0.0.1:${PORT}/ws\n`);
|