@lopecode/channel 0.1.1 → 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 +37 -49
  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[] };
@@ -534,53 +535,40 @@ function handleWsClose(ws: ServerWebSocket<unknown>) {
534
535
  await mcp.connect(new StdioServerTransport());
535
536
 
536
537
  // 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,
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);
570
565
  },
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
- }
566
+ message: handleWsMessage,
567
+ close: handleWsClose,
568
+ },
569
+ });
584
570
 
571
+ PORT = server.port; // read actual port (important when REQUESTED_PORT is 0)
572
+ PAIRING_TOKEN = generateToken();
585
573
  process.stderr.write(`lopecode-channel: pairing token: ${PAIRING_TOKEN}\n`);
586
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.1",
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": {