@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.
- package/lopecode-channel.ts +46 -55
- package/package.json +1 -1
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[] };
|
|
@@ -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
|
|
65
|
-
1. Call get_pairing_token to get the token
|
|
66
|
-
2.
|
|
67
|
-
3.
|
|
68
|
-
4.
|
|
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
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
if (
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
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
|
-
|
|
570
|
-
|
|
571
|
-
|
|
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`);
|