@lopecode/channel 0.1.0 → 0.1.2

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.
Files changed (2) hide show
  1. package/lopecode-channel.ts +46 -55
  2. package/package.json +1 -1
@@ -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[] };
@@ -59,13 +60,16 @@ const mcp = new Server(
59
60
  },
60
61
  instructions: `You are connected to Lopecode notebooks via the lopecode channel.
61
62
 
62
- ## Starting a notebook
63
+ ## Starting a lopecode notebook
63
64
 
64
- When the user asks to start/open/create a notebook, or when no notebooks are connected and collaboration would benefit from one:
65
- 1. Call get_pairing_token to get the token
66
- 2. Open the browser with: open '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=TOKEN'
67
- 3. The notebook auto-connects wait for the connected notification
68
- 4. Send a welcome message via reply
65
+ When the user asks to start/open a lopecode notebook, or start a pairing/collaboration session:
66
+ 1. Call get_pairing_token to get the token (format: LOPE-PORT-XXXX)
67
+ 2. Construct the URL: 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=TOKEN
68
+ 3. Open the browser: run open 'URL' (macOS) or xdg-open 'URL' (Linux)
69
+ 4. Wait for the connected notification
70
+ 5. Send a welcome message via reply
71
+
72
+ If channels are not enabled, tell the user to restart with: claude --channels server:lopecode
69
73
 
70
74
  ## Message formats
71
75
 
@@ -531,53 +535,40 @@ function handleWsClose(ws: ServerWebSocket<unknown>) {
531
535
  await mcp.connect(new StdioServerTransport());
532
536
 
533
537
  // Start WebSocket + HTTP server
534
- try {
535
- Bun.serve({
536
- port: PORT,
537
- hostname: "127.0.0.1",
538
- fetch(req, server) {
539
- const url = new URL(req.url);
540
- if (url.pathname === "/ws") {
541
- if (server.upgrade(req)) return;
542
- return new Response("WebSocket upgrade failed", { status: 400 });
543
- }
544
- // Health check
545
- if (url.pathname === "/health") {
546
- return new Response(JSON.stringify({
547
- paired: pairedConnections.size,
548
- pending: pendingConnections.size,
549
- }), { headers: { "content-type": "application/json" } });
550
- }
551
- // Root: redirect to blank notebook with auto-connect token
552
- if (url.pathname === "/") {
553
- 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}`;
554
- return new Response(null, {
555
- status: 302,
556
- headers: { Location: notebookUrl },
557
- });
558
- }
559
- return new Response("lopecode-channel", { status: 200 });
560
- },
561
- websocket: {
562
- open(ws) {
563
- pendingConnections.add(ws);
564
- },
565
- message: handleWsMessage,
566
- close: handleWsClose,
538
+ const server = Bun.serve({
539
+ port: REQUESTED_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
+ if (url.pathname === "/health") {
548
+ return new Response(JSON.stringify({
549
+ paired: pairedConnections.size,
550
+ pending: pendingConnections.size,
551
+ }), { headers: { "content-type": "application/json" } });
552
+ }
553
+ if (url.pathname === "/") {
554
+ 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}`;
555
+ return new Response(null, {
556
+ status: 302,
557
+ headers: { Location: notebookUrl },
558
+ });
559
+ }
560
+ return new Response("lopecode-channel", { status: 200 });
561
+ },
562
+ websocket: {
563
+ open(ws) {
564
+ pendingConnections.add(ws);
567
565
  },
568
- });
569
- } catch (err) {
570
- const msg = err instanceof Error ? err.message : String(err);
571
- if (msg.includes("EADDRINUSE") || msg.includes("address already in use") || msg.includes("port") && msg.includes("in use")) {
572
- process.stderr.write(
573
- `lopecode-channel: ERROR — port ${PORT} is already in use.\n` +
574
- `Another lopecode-channel or other service is running on this port.\n` +
575
- `Kill the existing process or set LOPECODE_PORT=<other port>.\n`
576
- );
577
- process.exit(1);
578
- }
579
- throw err;
580
- }
566
+ message: handleWsMessage,
567
+ close: handleWsClose,
568
+ },
569
+ });
581
570
 
571
+ PORT = server.port; // read actual port (important when REQUESTED_PORT is 0)
572
+ PAIRING_TOKEN = generateToken();
582
573
  process.stderr.write(`lopecode-channel: pairing token: ${PAIRING_TOKEN}\n`);
583
574
  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.0",
3
+ "version": "0.1.2",
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": {