@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.
@@ -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
- if (!moduleName) return null; // no module filter match any module
43
- for (var v of runtime._variables) {
44
- if (v._module && v._module._name === moduleName) return v._module;
45
- if (v._name && v._name.startsWith("module ") && v._name === "module " + moduleName)
46
- return v._module;
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 targetModule = findModule(runtime, moduleName);
92
- if (!targetModule) return { ok: false, error: "Module not found: " + (moduleName || "main") };
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
- var fn;
95
- try {
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
- var existingVar = null;
103
- for (var v of runtime._variables) {
104
- if (v._name === name && v._module === targetModule) { existingVar = v; break; }
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
- try {
108
- if (existingVar) { existingVar.define(name, inputs, fn); }
109
- else { var nv = targetModule.variable({}); nv.define(name, inputs, fn); }
110
- var actualRuntime = findActualRuntime(runtime);
111
- if (actualRuntime && actualRuntime._computeNow) actualRuntime._computeNow();
112
- return { ok: true, result: { success: true, name: name, module: targetModule._name || "main", redefined: !!existingVar } };
113
- } catch (e) {
114
- return { ok: false, error: "Failed to define variable: " + e.message };
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));
@@ -16,9 +16,10 @@ import type { ServerWebSocket } from "bun";
16
16
  import { join, dirname, basename } from "path";
17
17
 
18
18
  // --- Configuration ---
19
- const PORT = Number(process.env.LOPECODE_PORT ?? 8787);
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
- const PAIRING_TOKEN = generateToken();
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: (args.inputs as string[]) || [],
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
- try {
538
- Bun.serve({
539
- port: PORT,
540
- hostname: "127.0.0.1",
541
- fetch(req, server) {
542
- const url = new URL(req.url);
543
- if (url.pathname === "/ws") {
544
- if (server.upgrade(req)) return;
545
- return new Response("WebSocket upgrade failed", { status: 400 });
546
- }
547
- // Health check
548
- if (url.pathname === "/health") {
549
- return new Response(JSON.stringify({
550
- paired: pairedConnections.size,
551
- pending: pendingConnections.size,
552
- }), { headers: { "content-type": "application/json" } });
553
- }
554
- // Root: redirect to blank notebook with auto-connect token
555
- if (url.pathname === "/") {
556
- 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}`;
557
- return new Response(null, {
558
- status: 302,
559
- headers: { Location: notebookUrl },
560
- });
561
- }
562
- return new Response("lopecode-channel", { status: 200 });
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
- } catch (err) {
573
- const msg = err instanceof Error ? err.message : String(err);
574
- if (msg.includes("EADDRINUSE") || msg.includes("address already in use") || msg.includes("port") && msg.includes("in use")) {
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`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lopecode/channel",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Pair program with Claude inside Lopecode notebooks. MCP server bridging browser notebooks and Claude Code.",
5
5
  "type": "module",
6
6
  "bin": {