webtty 1.9.0 → 1.11.0

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/dist/cli/index.js CHANGED
@@ -17,6 +17,9 @@ var __export = (target, all) => {
17
17
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
18
18
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
19
19
 
20
+ // src/cli/index.ts
21
+ import path4 from "node:path";
22
+
20
23
  // src/cli/commands.ts
21
24
  import * as childProcess2 from "node:child_process";
22
25
  import fs3 from "node:fs";
@@ -74,7 +77,8 @@ var DEFAULT_CONFIG = {
74
77
  mouseScrollSpeed: 1,
75
78
  logs: false,
76
79
  theme: DEFAULT_THEME,
77
- keyboardBindings: DEFAULT_KEYBOARD_BINDINGS
80
+ keyboardBindings: DEFAULT_KEYBOARD_BINDINGS,
81
+ env: {}
78
82
  };
79
83
  function bindingKey(b) {
80
84
  const mods = [...b.mods ?? []].sort().join("+");
@@ -156,6 +160,9 @@ function loadConfig() {
156
160
  ...p.theme && typeof p.theme === "object" && { theme: { ...DEFAULT_THEME, ...p.theme } },
157
161
  ...Array.isArray(p.keyboardBindings) && {
158
162
  keyboardBindings: mergeKeyboardBindings(DEFAULT_KEYBOARD_BINDINGS, p.keyboardBindings.filter(isValidBinding).map(normalizeBinding))
163
+ },
164
+ ...p.env && typeof p.env === "object" && !Array.isArray(p.env) && {
165
+ env: Object.fromEntries(Object.entries(p.env).filter(([k, v]) => typeof k === "string" && typeof v === "string"))
159
166
  }
160
167
  };
161
168
  }
@@ -279,7 +286,7 @@ function toBrowserHost(host) {
279
286
  return `[${host}]`;
280
287
  return host;
281
288
  }
282
- async function cmdGo(id = "main") {
289
+ async function cmdGo(id = "main", baseDir) {
283
290
  if (!await isServerRunning()) {
284
291
  await startServer();
285
292
  }
@@ -291,7 +298,7 @@ async function cmdGo(id = "main") {
291
298
  const res = await fetch(`${getBaseUrl()}/api/sessions`, {
292
299
  method: "POST",
293
300
  headers: { "Content-Type": "application/json" },
294
- body: JSON.stringify({ id })
301
+ body: JSON.stringify({ id, ...baseDir ? { baseDir } : {} })
295
302
  });
296
303
  if (!res.ok) {
297
304
  const body = await res.json();
@@ -453,6 +460,102 @@ function bytesToDisplay(buf) {
453
460
  return `\\x${b.toString(16).padStart(2, "0")}`;
454
461
  }).join(" ");
455
462
  }
463
+ async function cmdRun(id, cmd, args = []) {
464
+ if (!cmd) {
465
+ if (!await isServerRunning()) {
466
+ await startServer();
467
+ }
468
+ const check = await fetch(`${getBaseUrl()}/api/sessions/${encodeURIComponent(id)}`);
469
+ if (check.status !== 200) {
470
+ const res2 = await fetch(`${getBaseUrl()}/api/sessions`, {
471
+ method: "POST",
472
+ headers: { "Content-Type": "application/json" },
473
+ body: JSON.stringify({ id })
474
+ });
475
+ if (!res2.ok) {
476
+ const body = await res2.json();
477
+ console.error(`webtty: ${body.error ?? `failed to create session (${res2.status})`}`);
478
+ process.exit(1);
479
+ }
480
+ }
481
+ const wsUrl = `ws://127.0.0.1:${getPort()}/ws/${encodeURIComponent(id)}/pty?cols=80&rows=24`;
482
+ await new Promise((resolve, reject) => {
483
+ const ws = new WebSocket(wsUrl);
484
+ let opened = false;
485
+ ws.onopen = () => {
486
+ opened = true;
487
+ ws.close(1000);
488
+ resolve();
489
+ };
490
+ ws.onerror = () => reject(new Error("failed to connect to PTY"));
491
+ ws.onclose = (evt) => {
492
+ if (!opened)
493
+ reject(new Error(`failed to start PTY (code ${evt.code})`));
494
+ };
495
+ }).catch((err) => {
496
+ console.error(`webtty: failed to start PTY: ${err.message}`);
497
+ process.exit(1);
498
+ });
499
+ const url = `http://${toBrowserHost(loadConfig().host)}:${getPort()}/s/${id}`;
500
+ console.log(url);
501
+ return;
502
+ }
503
+ if (!await isServerRunning()) {
504
+ console.error("webtty: server is not running");
505
+ process.exit(1);
506
+ }
507
+ let res;
508
+ try {
509
+ res = await fetch(`${getBaseUrl()}/s/${encodeURIComponent(id)}/execute`, {
510
+ method: "POST",
511
+ headers: { "Content-Type": "application/json" },
512
+ body: JSON.stringify({ cmd, args })
513
+ });
514
+ } catch {
515
+ console.error("webtty: server is not running");
516
+ process.exit(1);
517
+ }
518
+ if (res.status === 404) {
519
+ console.error(`webtty: session not found: ${id}`);
520
+ process.exit(1);
521
+ }
522
+ if (res.status === 409) {
523
+ console.error(`webtty: PTY not running for session: ${id}`);
524
+ process.exit(1);
525
+ }
526
+ if (!res.ok || !res.body) {
527
+ console.error(`webtty: run failed (${res.status})`);
528
+ process.exit(1);
529
+ }
530
+ const reader = res.body.getReader();
531
+ const decoder = new TextDecoder;
532
+ let buf = "";
533
+ while (true) {
534
+ const { done, value } = await reader.read();
535
+ if (done)
536
+ break;
537
+ buf += decoder.decode(value, { stream: true });
538
+ const lines = buf.split(`
539
+ `);
540
+ buf = lines.pop() ?? "";
541
+ for (const line of lines) {
542
+ if (!line)
543
+ continue;
544
+ const msg = JSON.parse(line);
545
+ if (msg.stream === "stdout")
546
+ process.stdout.write(String(msg.data));
547
+ else if (msg.stream === "stderr")
548
+ process.stderr.write(String(msg.data));
549
+ else if ("exit" in msg) {
550
+ if (msg.error)
551
+ console.error(`webtty: ${msg.error}`);
552
+ process.exit(msg.exit ?? 1);
553
+ }
554
+ }
555
+ }
556
+ console.error("webtty: connection closed unexpectedly");
557
+ process.exit(1);
558
+ }
456
559
  function cmdKey() {
457
560
  if (!process.stdin.isTTY) {
458
561
  console.error("webtty key: requires an interactive terminal");
@@ -503,7 +606,7 @@ function cmdKey() {
503
606
  }
504
607
 
505
608
  // src/cli/index.ts
506
- var GO_ALIASES = new Set(["go", "a", "run", "attach", "open"]);
609
+ var GO_ALIASES = new Set(["go", "a", "attach", "open"]);
507
610
  function printHelp() {
508
611
  const indent = " ";
509
612
  const col = 18;
@@ -520,6 +623,7 @@ function printHelp() {
520
623
  row("ls [id]", "List all sessions, or filter by id substring"),
521
624
  row("rm <id>", "Destroy a session"),
522
625
  row("mv <id> <new-id>", "Rename a session"),
626
+ row("run <id> [cmd]", "Start PTY (no browser) or run a command in a session"),
523
627
  row("stop", "Stop the webtty server"),
524
628
  row("start", "Start the webtty server"),
525
629
  row("config", "Open the config file in $VISUAL, $EDITOR, or a default editor"),
@@ -528,11 +632,18 @@ function printHelp() {
528
632
  ].join(`
529
633
  `));
530
634
  }
531
- var [, , cmd, ...rest] = process.argv;
635
+ var allArgs = process.argv.slice(2);
636
+ var dirFlagIdx = allArgs.indexOf("--dir");
637
+ var cliBaseDir;
638
+ if (dirFlagIdx !== -1) {
639
+ cliBaseDir = path4.resolve(allArgs[dirFlagIdx + 1] ?? ".");
640
+ allArgs.splice(dirFlagIdx, 2);
641
+ }
642
+ var [cmd, ...rest] = allArgs;
532
643
  if (!cmd) {
533
- await cmdGo();
644
+ await cmdGo("main", cliBaseDir);
534
645
  } else if (GO_ALIASES.has(cmd)) {
535
- await cmdGo(rest[0]);
646
+ await cmdGo(rest[0] ?? "main", cliBaseDir);
536
647
  } else {
537
648
  switch (cmd) {
538
649
  case "ls":
@@ -557,6 +668,15 @@ if (!cmd) {
557
668
  case "config":
558
669
  cmdConfig();
559
670
  break;
671
+ case "run": {
672
+ const [id, runCmd, ...runArgs] = rest;
673
+ if (!id) {
674
+ console.error("usage: webtty run <id> [cmd [args...]]");
675
+ process.exit(1);
676
+ }
677
+ await cmdRun(id, runCmd, runArgs);
678
+ break;
679
+ }
560
680
  case "key":
561
681
  cmdKey();
562
682
  break;
@@ -572,4 +692,4 @@ Run \`webtty help\` for usage.`);
572
692
  }
573
693
  }
574
694
 
575
- //# debugId=8359C04A2D29CCC864756E2164756E21
695
+ //# debugId=5CD11DFCEDA2DDF864756E2164756E21
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/cli/commands.ts", "../src/config.ts", "../src/cli/http.ts", "../src/cli/index.ts"],
3
+ "sources": ["../src/cli/index.ts", "../src/cli/commands.ts", "../src/config.ts", "../src/cli/http.ts"],
4
4
  "sourcesContent": [
5
- "import * as childProcess from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { configDir, loadConfig } from '../config';\nimport { getBaseUrl, getPort, isServerRunning, openBrowser, startServer, stopServer } from './http';\n\n/**\n * Converts a bind host to a browser-navigable host.\n * - Bind-all addresses (`0.0.0.0`, `::`) become `localhost`.\n * - Bare IPv6 addresses are bracketed (`::1` → `[::1]`).\n * - All other values pass through unchanged.\n */\nexport function toBrowserHost(host: string): string {\n if (host === '0.0.0.0' || host === '::') return 'localhost';\n if (host.includes(':') && !host.startsWith('[')) return `[${host}]`;\n return host;\n}\n\n/**\n * Opens (or creates) session `id`, starts the server if needed, and opens the URL in the browser.\n *\n * @param id - The session ID to open (default: `'main'`).\n */\nexport async function cmdGo(id = 'main'): Promise<void> {\n if (!(await isServerRunning())) {\n await startServer();\n }\n\n let sessionId: string;\n const check = await fetch(`${getBaseUrl()}/api/sessions/${encodeURIComponent(id)}`);\n if (check.status === 200) {\n sessionId = id;\n } else {\n const res = await fetch(`${getBaseUrl()}/api/sessions`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ id }),\n });\n if (!res.ok) {\n const body = (await res.json()) as { error?: string };\n console.error(`webtty: ${body.error ?? `failed to create session (${res.status})`}`);\n process.exit(1);\n }\n const session = (await res.json()) as { id: string };\n sessionId = session.id;\n }\n\n const url = `http://${toBrowserHost(loadConfig().host)}:${getPort()}/s/${sessionId}`;\n console.log(url);\n openBrowser(url);\n}\n\n/**\n * Lists all active sessions, optionally filtered by a substring of the session ID.\n *\n * @param filter - Optional substring to filter session IDs.\n */\nexport async function cmdList(filter?: string): Promise<void> {\n let res: Response;\n try {\n res = await fetch(`${getBaseUrl()}/api/sessions`);\n } catch {\n console.log('webtty is not running');\n process.exit(1);\n }\n const all = (await res.json()) as Array<{\n id: string;\n connected: boolean;\n createdAt: number;\n }>;\n const sessions = filter ? all.filter((s) => s.id.includes(filter)) : all;\n if (sessions.length === 0) {\n console.log('no sessions');\n return;\n }\n console.log('id\\t\\t\\tconnected\\tcreated');\n for (const s of sessions) {\n const created = new Date(s.createdAt).toLocaleString();\n console.log(`${s.id}\\t\\t\\t${s.connected}\\t\\t${created}`);\n }\n}\n\n/**\n * Removes session `id` and stops the server if no sessions remain.\n *\n * @param id - The session ID to remove.\n */\nexport async function cmdRemove(id?: string): Promise<void> {\n if (!id) {\n console.error('webtty: rm requires a session id');\n process.exit(1);\n }\n let res: Response;\n try {\n res = await fetch(`${getBaseUrl()}/api/sessions/${encodeURIComponent(id)}`, {\n method: 'DELETE',\n });\n } catch {\n console.log('webtty is not running');\n process.exit(1);\n }\n if (res.status === 204) {\n console.log(`removed ${id}`);\n if (res.headers.get('x-sessions-remaining') === '0') {\n await stopServer();\n console.log('no sessions remaining webtty stopped');\n }\n } else if (res.status === 404) {\n console.error(`session ${id} not found`);\n process.exit(1);\n } else {\n console.error(`webtty rm failed (status: ${res.status})`);\n process.exit(1);\n }\n}\n\n/**\n * Renames session `id` to `newId`.\n *\n * @param id - The current session ID.\n * @param newId - The new session ID.\n */\nexport async function cmdRename(id?: string, newId?: string): Promise<void> {\n if (!id || !newId) {\n console.error('webtty: rename requires two arguments: [id] [new-id]');\n process.exit(1);\n }\n let res: Response;\n try {\n res = await fetch(`${getBaseUrl()}/api/sessions/${encodeURIComponent(id)}`, {\n method: 'PATCH',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ id: newId }),\n });\n } catch {\n console.log('webtty is not running');\n process.exit(1);\n }\n if (res.ok) {\n console.log(`renamed ${id} ${newId}`);\n } else if (res.status === 404) {\n console.error(`session ${id} not found`);\n process.exit(1);\n } else {\n const body = (await res.json()) as { error?: string };\n console.error(`webtty: ${body.error ?? `rename failed (${res.status})`}`);\n process.exit(1);\n }\n}\n\n/** Stops the server if it is running. */\nexport async function cmdStop(): Promise<void> {\n if (!(await isServerRunning())) {\n console.log('webtty is not running');\n return;\n }\n const ok = await stopServer();\n if (ok) {\n console.log('webtty stopped');\n } else {\n console.error('webtty stop failed');\n process.exit(1);\n }\n}\n\n/** Starts the server if it is not already running. */\nexport async function cmdStart(): Promise<void> {\n if (await isServerRunning()) {\n console.log('webtty is already running');\n return;\n }\n await startServer();\n console.log('webtty started');\n}\n\n/** Opens `~/.config/webtty/config.json` in `$VISUAL` / `$EDITOR`, creating it if absent. */\nexport function cmdConfig(): void {\n const dir = configDir();\n const configPath = path.join(dir, 'config.json');\n fs.mkdirSync(dir, { recursive: true });\n if (!fs.existsSync(configPath)) {\n fs.writeFileSync(configPath, '{}\\n', 'utf8');\n }\n const editor =\n process.env.VISUAL ?? process.env.EDITOR ?? (process.platform === 'win32' ? 'notepad' : 'vi');\n childProcess.spawnSync(editor, [configPath], { stdio: 'inherit' });\n}\n\nexport function bytesToChars(buf: Buffer): string {\n let out = '';\n for (const b of buf) {\n if (b === 0x1b) out += '\\\\u001b';\n else if (b === 0x0d) out += '\\\\r';\n else if (b === 0x09) out += '\\\\t';\n else if (b === 0x0a) out += '\\\\n';\n else if (b === 0x22) out += '\\\\\"';\n else if (b === 0x5c) out += '\\\\\\\\';\n else if (b >= 0x20 && b < 0x7f) out += String.fromCharCode(b);\n else out += `\\\\u${b.toString(16).padStart(4, '0')}`;\n }\n return `\"${out}\"`;\n}\n\nexport function bytesToDisplay(buf: Buffer): string {\n return Array.from(buf)\n .map((b) => {\n if (b === 0x1b) return 'ESC';\n if (b === 0x0d) return 'CR';\n if (b === 0x09) return 'TAB';\n if (b === 0x0a) return 'LF';\n if (b === 0x20) return 'SPC';\n if (b === 0x7f) return 'DEL';\n if (b > 0x20 && b < 0x7f) return String.fromCharCode(b);\n return `\\\\x${b.toString(16).padStart(2, '0')}`;\n })\n .join(' ');\n}\n\nexport function cmdKey(): void {\n if (!process.stdin.isTTY) {\n console.error('webtty key: requires an interactive terminal');\n process.exit(1);\n return;\n }\n\n const dim = '\\x1b[2m';\n const bold = '\\x1b[1m';\n const reset = '\\x1b[0m';\n\n const restoreTerminal = () => {\n try {\n process.stdin.setRawMode(false);\n } catch {}\n };\n process.once('exit', restoreTerminal);\n process.once('SIGINT', restoreTerminal);\n process.once('SIGTERM', restoreTerminal);\n\n process.stdin.setRawMode(true);\n process.stdin.resume();\n console.log('Press any key combo to see its chars value. q to quit.\\n');\n console.log(` ${dim}received${reset} → ${bold}chars${reset}`);\n console.log(` ${'─'.repeat(17)}`);\n\n let buf = Buffer.alloc(0);\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const flush = () => {\n if (buf.length === 0) return;\n const display = bytesToDisplay(buf).padEnd(8);\n console.log(` ${dim}${display}${reset} → ${bold}${bytesToChars(buf)}${reset}`);\n buf = Buffer.alloc(0);\n };\n\n process.stdin.on('data', (chunk: Buffer) => {\n if (chunk.length === 1 && chunk[0] === 0x71) {\n process.stdin.setRawMode(false);\n process.removeListener('exit', restoreTerminal);\n process.removeListener('SIGINT', restoreTerminal);\n process.removeListener('SIGTERM', restoreTerminal);\n console.log(` ${'─'.repeat(17)}\\n`);\n process.exit(0);\n }\n buf = Buffer.concat([buf, chunk]);\n if (timer) clearTimeout(timer);\n timer = setTimeout(flush, 50);\n });\n}\n",
6
- "import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\n/** A single keyboard binding: intercepts `key`+`mods` and sends `chars` to the PTY. */\nexport interface KeyboardBinding {\n /** Key name matched case-insensitively against `KeyboardEvent.key` (e.g. `\"enter\"`, `\"arrowup\"`, `\"a\"`). */\n key: string;\n /** Modifier keys that must be active — and no others — for this binding to match. Accepted values: `\"shift\"`, `\"ctrl\"`, `\"alt\"`, `\"meta\"`. Order irrelevant. Unknown values are filtered out at config load time. */\n mods?: string[];\n /** Byte sequence sent verbatim to the PTY. Standard JSON escapes apply (`\\r`, `\\uXXXX`, etc.). */\n chars: string;\n}\n\n/** Terminal color palette. All keys are optional; omitted keys fall back to the Campbell defaults. */\nexport interface Theme {\n /** Terminal background. */\n background?: string;\n /** Default text color. */\n foreground?: string;\n /** Cursor color. */\n cursor?: string;\n /** Selection highlight. */\n selection?: string;\n /** ANSI 0. */\n black?: string;\n /** ANSI 1. */\n red?: string;\n /** ANSI 2. */\n green?: string;\n /** ANSI 3. */\n yellow?: string;\n /** ANSI 4. */\n blue?: string;\n /** ANSI 5. */\n purple?: string;\n /** ANSI 6. */\n cyan?: string;\n /** ANSI 7. */\n white?: string;\n /** ANSI 8. */\n brightBlack?: string;\n /** ANSI 9. */\n brightRed?: string;\n /** ANSI 10. */\n brightGreen?: string;\n /** ANSI 11. */\n brightYellow?: string;\n /** ANSI 12. */\n brightBlue?: string;\n /** ANSI 13. */\n brightPurple?: string;\n /** ANSI 14. */\n brightCyan?: string;\n /** ANSI 15. */\n brightWhite?: string;\n}\n\n/** Right-click behavior: `\"copyPaste\"` copies selection + clears it if selection exists, otherwise native menu; `\"default\"` always shows native context menu. */\nexport type RightClickBehavior = 'default' | 'copyPaste';\n\n/** Full webtty configuration. All keys are optional in the config file; missing keys fall back to {@link DEFAULT_CONFIG}. */\nexport interface Config {\n /** HTTP listen port; env `PORT` takes precedence. */\n port: number;\n /** Bind address; use `\"0.0.0.0\"` for remote access. */\n host: string;\n /** Shell for new sessions. */\n shell: string;\n /** `$TERM` env var passed to the shell. Fixed to `xterm-256color` — the PTY child talks to webtty, not to the parent terminal. */\n term: string;\n /** `$COLORTERM` env var passed to the shell. */\n colorTerm: string;\n /** PTY history buffer in bytes; used for server-side replay on reload/reconnect. */\n scrollback: number;\n /** Initial terminal width in columns. */\n cols: number;\n /** Initial terminal height in rows. */\n rows: number;\n /** Font size in px. */\n fontSize: number;\n /** CSS font-family stack. */\n fontFamily: string;\n /** Default cursor shape. Apps override at runtime via DECSCUSR — this is the startup default only. */\n cursorStyle: 'block' | 'bar' | 'underline';\n /** Default blink state. Apps override at runtime via DECSCUSR — this is the startup default only. */\n cursorStyleBlink: boolean;\n /** Auto-copy selection to clipboard on mouseup (kitty / Windows Terminal style). */\n copyOnSelect: boolean;\n /** `\"copyPaste\"` copies selection + clears it on right-click if selection exists; `\"default\"` always shows native context menu. */\n rightClickBehavior: RightClickBehavior;\n /** Mouse wheel scroll speed multiplier for apps with mouse tracking. `1` = one SGR per tick. Values `< 1` reduce rate; values `> 1` send multiple SGRs per tick. Must be `> 0`. */\n mouseScrollSpeed: number;\n /** Write server stdout/stderr to `~/.config/webtty/server.log`. Appends on each start. */\n logs: boolean;\n /** Terminal color palette. */\n theme: Theme;\n /** Custom key-to-sequence bindings. Merged with built-in defaults by `(key, mods)` identity. */\n keyboardBindings: KeyboardBinding[];\n}\n\n/** Returns the webtty config directory: `~/.config/webtty`. */\nexport function configDir(): string {\n // os.homedir() is the authoritative home directory. process.env.HOME is used\n // as an override in tests (e.g. to isolate config state), but only when it is\n // an absolute path — guarding against accidental env pollution such as the\n // string \"undefined\" being assigned when HOME was originally unset on Windows.\n const home =\n process.env.HOME && path.isAbsolute(process.env.HOME) ? process.env.HOME : os.homedir();\n return path.join(home, '.config', 'webtty');\n}\n\nfunction getConfigPath(): string {\n return path.join(configDir(), 'config.json');\n}\n\n// NOTE: export for testing only; users should use loadConfig() and initConfig() instead\nexport const DEFAULT_THEME: Theme = {\n background: '#000000',\n foreground: '#CCCCCC',\n cursor: '#FFFFFF',\n selection: '#FFFFFF',\n black: '#0C0C0C',\n red: '#C50F1F',\n green: '#13A10E',\n yellow: '#C19C00',\n blue: '#0037DA',\n purple: '#881798',\n cyan: '#3A96DD',\n white: '#CCCCCC',\n brightBlack: '#767676',\n brightRed: '#E74856',\n brightGreen: '#16C60C',\n brightYellow: '#F9F1A5',\n brightBlue: '#3B78FF',\n brightPurple: '#B4009E',\n brightCyan: '#61D6D6',\n brightWhite: '#F2F2F2',\n};\n\n// NOTE: export for testing only; users should use loadConfig() and initConfig() instead\nexport const DEFAULT_KEYBOARD_BINDINGS: KeyboardBinding[] = [];\n\n// NOTE: export for testing only; users should use loadConfig() and initConfig() instead\nexport const DEFAULT_CONFIG: Config = {\n port: 2346,\n host: '127.0.0.1',\n shell:\n process.platform === 'win32'\n ? (process.env.COMSPEC ?? 'cmd.exe')\n : (process.env.SHELL ?? '/bin/bash'),\n term: 'xterm-256color',\n colorTerm: 'truecolor',\n scrollback: 256 * 1024,\n cols: 80,\n rows: 24,\n fontSize: 13,\n fontFamily: \"Menlo, Consolas, 'DejaVu Sans Mono', monospace\",\n cursorStyle: 'bar',\n cursorStyleBlink: true,\n copyOnSelect: true,\n rightClickBehavior: 'default' as RightClickBehavior,\n mouseScrollSpeed: 1,\n logs: false,\n theme: DEFAULT_THEME,\n keyboardBindings: DEFAULT_KEYBOARD_BINDINGS,\n};\n\nfunction bindingKey(b: KeyboardBinding): string {\n const mods = [...(b.mods ?? [])].sort().join('+');\n return `${b.key.toLowerCase()}|${mods}`;\n}\n\nconst VALID_MODS = new Set(['shift', 'ctrl', 'alt', 'meta']);\n\nfunction isValidBinding(b: unknown): b is KeyboardBinding {\n if (!b || typeof b !== 'object') return false;\n const o = b as Record<string, unknown>;\n if (typeof o.key !== 'string' || typeof o.chars !== 'string') return false;\n if (o.mods !== undefined && !Array.isArray(o.mods)) return false;\n return true;\n}\n\nfunction normalizeBinding(b: KeyboardBinding): KeyboardBinding {\n return {\n ...b,\n key: b.key.toLowerCase(),\n mods: (b.mods ?? []).map((m) => m.toLowerCase()).filter((m) => VALID_MODS.has(m)),\n };\n}\n\n/**\n * Merges user-supplied bindings over a set of defaults by `(key, mods)` identity.\n * User entries replace matching defaults; unmatched user entries are appended.\n *\n * @param defaults - Default bindings to merge over.\n * @param user - User-supplied bindings that override defaults.\n * @returns Merged bindings with user entries replacing matching defaults and new user entries appended.\n */\n// NOTE: export for testing only\nexport function mergeKeyboardBindings(\n defaults: KeyboardBinding[],\n user: KeyboardBinding[],\n): KeyboardBinding[] {\n const deduped = [...new Map(user.map((b) => [bindingKey(b), b])).values()];\n const overrides = new Map(deduped.map((b) => [bindingKey(b), b]));\n const merged = defaults.map((d) => overrides.get(bindingKey(d)) ?? d);\n const defaultKeys = new Set(defaults.map(bindingKey));\n for (const b of deduped) {\n if (!defaultKeys.has(bindingKey(b))) merged.push(b);\n }\n return merged;\n}\n\n/**\n * Load config from `~/.config/webtty/config.json`, merged over `DEFAULT_CONFIG`.\n *\n * On first run (file absent), writes the default port/host stub and returns defaults.\n * Unknown keys and keys with wrong types are silently ignored.\n * Throws if the file exists but cannot be read or contains invalid JSON.\n *\n * @returns The resolved {@link Config}, with all missing keys filled from {@link DEFAULT_CONFIG}.\n */\nexport function loadConfig(): Config {\n if (!fs.existsSync(getConfigPath())) {\n try {\n initConfig();\n } catch (err) {\n console.warn(\n `webtty: failed to write default config to ${getConfigPath()}: ${(err as Error).message}`,\n );\n return { ...DEFAULT_CONFIG };\n }\n }\n\n let raw: string;\n try {\n raw = fs.readFileSync(getConfigPath(), 'utf8');\n } catch (err) {\n throw new Error(\n `webtty: failed to read config at ${getConfigPath()}: ${(err as Error).message}`,\n );\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new Error(`webtty: invalid JSON in config file ${getConfigPath()}`);\n }\n\n const p = parsed as Partial<Config>;\n return {\n ...DEFAULT_CONFIG,\n ...(typeof p.port === 'number' && { port: p.port }),\n ...(typeof p.host === 'string' && { host: p.host }),\n ...(typeof p.shell === 'string' && { shell: p.shell }),\n ...(typeof p.term === 'string' && { term: p.term }),\n ...(typeof p.colorTerm === 'string' && { colorTerm: p.colorTerm }),\n ...(typeof p.scrollback === 'number' && { scrollback: p.scrollback }),\n ...(typeof p.cols === 'number' && { cols: p.cols }),\n ...(typeof p.rows === 'number' && { rows: p.rows }),\n ...(typeof p.fontSize === 'number' && { fontSize: p.fontSize }),\n ...(typeof p.fontFamily === 'string' && { fontFamily: p.fontFamily }),\n ...(typeof p.cursorStyle === 'string' &&\n (p.cursorStyle === 'block' || p.cursorStyle === 'bar' || p.cursorStyle === 'underline') && {\n cursorStyle: p.cursorStyle,\n }),\n ...(typeof p.cursorStyleBlink === 'boolean' && { cursorStyleBlink: p.cursorStyleBlink }),\n ...(typeof p.copyOnSelect === 'boolean' && { copyOnSelect: p.copyOnSelect }),\n ...(typeof p.rightClickBehavior === 'string' && {\n rightClickBehavior: (p.rightClickBehavior === 'copyPaste'\n ? 'copyPaste'\n : 'default') as RightClickBehavior,\n }),\n ...(typeof p.mouseScrollSpeed === 'number' &&\n p.mouseScrollSpeed > 0 && { mouseScrollSpeed: p.mouseScrollSpeed }),\n ...(typeof p.logs === 'boolean' && { logs: p.logs }),\n ...(p.theme && typeof p.theme === 'object' && { theme: { ...DEFAULT_THEME, ...p.theme } }),\n ...(Array.isArray(p.keyboardBindings) && {\n keyboardBindings: mergeKeyboardBindings(\n DEFAULT_KEYBOARD_BINDINGS,\n p.keyboardBindings.filter(isValidBinding).map(normalizeBinding),\n ),\n }),\n };\n}\n\n/**\n * Write the initial config stub (`port` + `host` only) to `~/.config/webtty/config.json`.\n *\n * Only writes the two keys that are safe to persist as defaults; all other\n * keys are intentionally omitted so future webtty versions can add new fields\n * without the stub going stale.\n */\nexport function initConfig(): void {\n fs.mkdirSync(path.dirname(getConfigPath()), { recursive: true });\n const content = JSON.stringify(\n {\n port: DEFAULT_CONFIG.port,\n host: DEFAULT_CONFIG.host,\n },\n null,\n 2,\n );\n fs.writeFileSync(getConfigPath(), content, 'utf8');\n}\n",
7
- "import * as childProcess from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { configDir, loadConfig } from '../config';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n/** Active server port, read from config on first call (env PORT is intentionally ignored). */\nexport function getPort(): number {\n return loadConfig().port;\n}\n\n/** Base URL for internal CLI↔server API calls (always 127.0.0.1, avoids IPv6 lookup). */\nexport function getBaseUrl(): string {\n return `http://127.0.0.1:${getPort()}`;\n}\n\n/** Returns the path to the server log file: `~/.config/webtty/server.log`. */\nexport function logPath(): string {\n return path.join(configDir(), 'server.log');\n}\n\n/** Returns `true` if the webtty server is reachable and responding to API requests. */\nexport async function isServerRunning(): Promise<boolean> {\n try {\n const res = await fetch(`${getBaseUrl()}/api/sessions`);\n if (!res.ok) return false;\n const body = await res.json();\n return Array.isArray(body);\n } catch {\n return false;\n }\n}\n\n/**\n * Spawns the server process detached, then polls until it is ready or `timeoutMs` expires.\n *\n * @param timeoutMs - Maximum time to wait for the server to start (default: 10000 ms).\n * @param _spawn - Spawn function override for testing (default: childProcess.spawn).\n * @throws Exits the process with code 1 if the server entry is not found or fails to start in time.\n */\nexport async function startServer(timeoutMs = 10000, _spawn = childProcess.spawn): Promise<void> {\n const isBun = typeof (globalThis as Record<string, unknown>).Bun !== 'undefined';\n\n // On Windows, Bun's net.Socket doesn't support the fd-based named-pipe\n // wrapping that node-pty's ConPTY backend requires, so node-pty fails under\n // Bun on Windows. bunx works because its #!/usr/bin/env node shim runs the\n // server with Node.js instead. Mirror that here explicitly.\n const useNode = process.platform === 'win32' && isBun;\n const serverExec = useNode ? 'node' : process.execPath;\n\n // Resolve the server entry. When useNode is true the executor is Node.js,\n // which cannot run .ts files, so we always need the compiled .js output.\n // The CLI may itself run from source (src/cli/) or from dist (dist/cli/);\n // adjust the relative path accordingly so we always land in the same dist/.\n const runningFromSrc = __filename.endsWith('.ts');\n let serverEntry: string;\n if (useNode) {\n serverEntry = runningFromSrc\n ? path.resolve(__dirname, '../../dist/server/index.js') // src/cli → dist/server\n : path.resolve(__dirname, '../server/index.js'); // dist/cli → dist/server\n } else {\n serverEntry = path.resolve(\n __dirname,\n runningFromSrc ? '../server/index.ts' : '../server/index.js',\n );\n }\n if (!fs.existsSync(serverEntry)) {\n console.error(`webtty: server entry not found at ${serverEntry}`);\n (process.exit as (code?: number) => void)(1);\n return;\n }\n\n const config = loadConfig();\n let stdio: childProcess.SpawnOptions['stdio'] = 'ignore';\n let logFd: number | undefined;\n if (config.logs) {\n const log = logPath();\n fs.mkdirSync(path.dirname(log), { recursive: true });\n logFd = fs.openSync(log, 'a');\n stdio = ['ignore', logFd, logFd];\n }\n\n const child = _spawn(serverExec, [serverEntry], {\n detached: true,\n stdio,\n env: { ...process.env, PORT: String(config.port) },\n });\n child.on('error', (err) => {\n const hint = useNode\n ? ' (Node.js must be on PATH when running webtty under Bun on Windows)'\n : '';\n console.error(`webtty: failed to start server: ${err.message}${hint}`);\n process.exit(1);\n });\n child.unref();\n if (logFd !== undefined) fs.closeSync(logFd);\n\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n if (await isServerRunning()) return;\n await new Promise((r) => setTimeout(r, 100));\n }\n console.error('webtty: server did not start in time');\n process.exit(1);\n}\n\n/**\n * Sends `POST /api/server/stop`, then polls until the server is no longer reachable.\n *\n * @param baseUrl - The server base URL (default: getBaseUrl()).\n * @param timeoutMs - Maximum time to wait for the server to stop (default: 5000 ms).\n * @returns `true` if the server stopped successfully, `false` otherwise.\n */\nexport async function stopServer(\n baseUrl: string = getBaseUrl(),\n timeoutMs = 5000,\n): Promise<boolean> {\n try {\n const res = await fetch(`${baseUrl}/api/server/stop`, { method: 'POST' });\n if (!res.ok) return false;\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n if (!(await isServerRunning())) return true;\n await new Promise((r) => setTimeout(r, 100));\n }\n return false;\n } catch {\n return false;\n }\n}\n\n/**\n * Opens `url` in the default system browser.\n * No-op in test environments or when `WEBTTY_NO_OPEN=1` is set.\n *\n * @param url - The URL to open.\n * @param _spawn - Spawn function override for testing (default: childProcess.spawn).\n */\nexport function openBrowser(url: string, _spawn = childProcess.spawn): void {\n if (process.env.WEBTTY_NO_OPEN === '1') return;\n if (process.env.NODE_ENV === 'test') return;\n if (process.platform === 'win32') {\n _spawn('cmd.exe', ['/c', 'start', '', url], { detached: true, stdio: 'ignore' }).unref();\n } else {\n const cmd = process.platform === 'darwin' ? 'open' : 'xdg-open';\n _spawn(cmd, [url], { detached: true, stdio: 'ignore' }).unref();\n }\n}\n",
8
- "import {\n cmdConfig,\n cmdGo,\n cmdKey,\n cmdList,\n cmdRemove,\n cmdRename,\n cmdStart,\n cmdStop,\n} from './commands';\n\nconst GO_ALIASES = new Set(['go', 'a', 'run', 'attach', 'open']);\n\nfunction printHelp(): void {\n const indent = ' ';\n const col = 18; // width of the widest term: \"mv <id> <new-id>\"\n const row = (term: string, desc: string) => `${indent}${term.padEnd(col)} ${desc}`;\n\n console.log(\n [\n 'Launch Terminal UI in the browser.',\n '',\n 'USAGE',\n row('webtty', 'Open main session in the browser'),\n row('webtty [command]', 'Execute a specific command'),\n '',\n 'COMMANDS',\n row('go [id]', 'Open a new or existing session in the browser'),\n row('ls [id]', 'List all sessions, or filter by id substring'),\n row('rm <id>', 'Destroy a session'),\n row('mv <id> <new-id>', 'Rename a session'),\n row('stop', 'Stop the webtty server'),\n row('start', 'Start the webtty server'),\n row('config', 'Open the config file in $VISUAL, $EDITOR, or a default editor'),\n row('key', 'Capture a key combo and print its chars value for keyboardBindings'),\n row('help', 'Show this help message'),\n ].join('\\n'),\n );\n}\n\nconst [, , cmd, ...rest] = process.argv;\n\nif (!cmd) {\n await cmdGo();\n} else if (GO_ALIASES.has(cmd)) {\n await cmdGo(rest[0]);\n} else {\n switch (cmd) {\n case 'ls':\n case 'list':\n await cmdList(rest[0]);\n break;\n case 'rm':\n case 'remove':\n await cmdRemove(rest[0]);\n break;\n case 'mv':\n case 'move':\n case 'rename':\n await cmdRename(rest[0], rest[1]);\n break;\n case 'stop':\n await cmdStop();\n break;\n case 'start':\n await cmdStart();\n break;\n case 'config':\n cmdConfig();\n break;\n case 'key':\n cmdKey();\n break;\n case 'help':\n case '--help':\n case '-h':\n printHelp();\n break;\n default:\n console.error(`webtty: unknown command '${cmd}'\\nRun \\`webtty help\\` for usage.`);\n process.exit(1);\n }\n}\n"
5
+ "import path from 'node:path';\nimport {\n cmdConfig,\n cmdGo,\n cmdKey,\n cmdList,\n cmdRemove,\n cmdRename,\n cmdRun,\n cmdStart,\n cmdStop,\n} from './commands';\n\nconst GO_ALIASES = new Set(['go', 'a', 'attach', 'open']);\n\nfunction printHelp(): void {\n const indent = ' ';\n const col = 18; // width of the widest term: \"mv <id> <new-id>\"\n const row = (term: string, desc: string) => `${indent}${term.padEnd(col)} ${desc}`;\n\n console.log(\n [\n 'Launch Terminal UI in the browser.',\n '',\n 'USAGE',\n row('webtty', 'Open main session in the browser'),\n row('webtty [command]', 'Execute a specific command'),\n '',\n 'COMMANDS',\n row('go [id]', 'Open a new or existing session in the browser'),\n row('ls [id]', 'List all sessions, or filter by id substring'),\n row('rm <id>', 'Destroy a session'),\n row('mv <id> <new-id>', 'Rename a session'),\n row('run <id> [cmd]', 'Start PTY (no browser) or run a command in a session'),\n row('stop', 'Stop the webtty server'),\n row('start', 'Start the webtty server'),\n row('config', 'Open the config file in $VISUAL, $EDITOR, or a default editor'),\n row('key', 'Capture a key combo and print its chars value for keyboardBindings'),\n row('help', 'Show this help message'),\n ].join('\\n'),\n );\n}\n\nconst allArgs = process.argv.slice(2);\nconst dirFlagIdx = allArgs.indexOf('--dir');\nlet cliBaseDir: string | undefined;\nif (dirFlagIdx !== -1) {\n cliBaseDir = path.resolve(allArgs[dirFlagIdx + 1] ?? '.');\n allArgs.splice(dirFlagIdx, 2);\n}\nconst [cmd, ...rest] = allArgs;\n\nif (!cmd) {\n await cmdGo('main', cliBaseDir);\n} else if (GO_ALIASES.has(cmd)) {\n await cmdGo(rest[0] ?? 'main', cliBaseDir);\n} else {\n switch (cmd) {\n case 'ls':\n case 'list':\n await cmdList(rest[0]);\n break;\n case 'rm':\n case 'remove':\n await cmdRemove(rest[0]);\n break;\n case 'mv':\n case 'move':\n case 'rename':\n await cmdRename(rest[0], rest[1]);\n break;\n case 'stop':\n await cmdStop();\n break;\n case 'start':\n await cmdStart();\n break;\n case 'config':\n cmdConfig();\n break;\n case 'run': {\n const [id, runCmd, ...runArgs] = rest;\n if (!id) {\n console.error('usage: webtty run <id> [cmd [args...]]');\n process.exit(1);\n }\n await cmdRun(id, runCmd, runArgs);\n break;\n }\n case 'key':\n cmdKey();\n break;\n case 'help':\n case '--help':\n case '-h':\n printHelp();\n break;\n default:\n console.error(`webtty: unknown command '${cmd}'\\nRun \\`webtty help\\` for usage.`);\n process.exit(1);\n }\n}\n",
6
+ "import * as childProcess from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { configDir, loadConfig } from '../config';\nimport { getBaseUrl, getPort, isServerRunning, openBrowser, startServer, stopServer } from './http';\n\n/**\n * Converts a bind host to a browser-navigable host.\n * - Bind-all addresses (`0.0.0.0`, `::`) become `localhost`.\n * - Bare IPv6 addresses are bracketed (`::1` → `[::1]`).\n * - All other values pass through unchanged.\n */\nexport function toBrowserHost(host: string): string {\n if (host === '0.0.0.0' || host === '::') return 'localhost';\n if (host.includes(':') && !host.startsWith('[')) return `[${host}]`;\n return host;\n}\n\n/**\n * Opens (or creates) session `id`, starts the server if needed, and opens the URL in the browser.\n *\n * @param id - The session ID to open (default: `'main'`).\n * @param baseDir - Working directory for the PTY shell (optional).\n */\nexport async function cmdGo(id = 'main', baseDir?: string): Promise<void> {\n if (!(await isServerRunning())) {\n await startServer();\n }\n\n let sessionId: string;\n const check = await fetch(`${getBaseUrl()}/api/sessions/${encodeURIComponent(id)}`);\n if (check.status === 200) {\n sessionId = id;\n } else {\n const res = await fetch(`${getBaseUrl()}/api/sessions`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ id, ...(baseDir ? { baseDir } : {}) }),\n });\n if (!res.ok) {\n const body = (await res.json()) as { error?: string };\n console.error(`webtty: ${body.error ?? `failed to create session (${res.status})`}`);\n process.exit(1);\n }\n const session = (await res.json()) as { id: string };\n sessionId = session.id;\n }\n\n const url = `http://${toBrowserHost(loadConfig().host)}:${getPort()}/s/${sessionId}`;\n console.log(url);\n openBrowser(url);\n}\n\n/**\n * Lists all active sessions, optionally filtered by a substring of the session ID.\n *\n * @param filter - Optional substring to filter session IDs.\n */\nexport async function cmdList(filter?: string): Promise<void> {\n let res: Response;\n try {\n res = await fetch(`${getBaseUrl()}/api/sessions`);\n } catch {\n console.log('webtty is not running');\n process.exit(1);\n }\n const all = (await res.json()) as Array<{\n id: string;\n connected: boolean;\n createdAt: number;\n }>;\n const sessions = filter ? all.filter((s) => s.id.includes(filter)) : all;\n if (sessions.length === 0) {\n console.log('no sessions');\n return;\n }\n console.log('id\\t\\t\\tconnected\\tcreated');\n for (const s of sessions) {\n const created = new Date(s.createdAt).toLocaleString();\n console.log(`${s.id}\\t\\t\\t${s.connected}\\t\\t${created}`);\n }\n}\n\n/**\n * Removes session `id` and stops the server if no sessions remain.\n *\n * @param id - The session ID to remove.\n */\nexport async function cmdRemove(id?: string): Promise<void> {\n if (!id) {\n console.error('webtty: rm requires a session id');\n process.exit(1);\n }\n let res: Response;\n try {\n res = await fetch(`${getBaseUrl()}/api/sessions/${encodeURIComponent(id)}`, {\n method: 'DELETE',\n });\n } catch {\n console.log('webtty is not running');\n process.exit(1);\n }\n if (res.status === 204) {\n console.log(`removed ${id}`);\n if (res.headers.get('x-sessions-remaining') === '0') {\n await stopServer();\n console.log('no sessions remaining — webtty stopped');\n }\n } else if (res.status === 404) {\n console.error(`session ${id} not found`);\n process.exit(1);\n } else {\n console.error(`webtty rm failed (status: ${res.status})`);\n process.exit(1);\n }\n}\n\n/**\n * Renames session `id` to `newId`.\n *\n * @param id - The current session ID.\n * @param newId - The new session ID.\n */\nexport async function cmdRename(id?: string, newId?: string): Promise<void> {\n if (!id || !newId) {\n console.error('webtty: rename requires two arguments: [id] [new-id]');\n process.exit(1);\n }\n let res: Response;\n try {\n res = await fetch(`${getBaseUrl()}/api/sessions/${encodeURIComponent(id)}`, {\n method: 'PATCH',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ id: newId }),\n });\n } catch {\n console.log('webtty is not running');\n process.exit(1);\n }\n if (res.ok) {\n console.log(`renamed ${id} → ${newId}`);\n } else if (res.status === 404) {\n console.error(`session ${id} not found`);\n process.exit(1);\n } else {\n const body = (await res.json()) as { error?: string };\n console.error(`webtty: ${body.error ?? `rename failed (${res.status})`}`);\n process.exit(1);\n }\n}\n\n/** Stops the server if it is running. */\nexport async function cmdStop(): Promise<void> {\n if (!(await isServerRunning())) {\n console.log('webtty is not running');\n return;\n }\n const ok = await stopServer();\n if (ok) {\n console.log('webtty stopped');\n } else {\n console.error('webtty stop failed');\n process.exit(1);\n }\n}\n\n/** Starts the server if it is not already running. */\nexport async function cmdStart(): Promise<void> {\n if (await isServerRunning()) {\n console.log('webtty is already running');\n return;\n }\n await startServer();\n console.log('webtty started');\n}\n\n/** Opens `~/.config/webtty/config.json` in `$VISUAL` / `$EDITOR`, creating it if absent. */\nexport function cmdConfig(): void {\n const dir = configDir();\n const configPath = path.join(dir, 'config.json');\n fs.mkdirSync(dir, { recursive: true });\n if (!fs.existsSync(configPath)) {\n fs.writeFileSync(configPath, '{}\\n', 'utf8');\n }\n const editor =\n process.env.VISUAL ?? process.env.EDITOR ?? (process.platform === 'win32' ? 'notepad' : 'vi');\n childProcess.spawnSync(editor, [configPath], { stdio: 'inherit' });\n}\n\nexport function bytesToChars(buf: Buffer): string {\n let out = '';\n for (const b of buf) {\n if (b === 0x1b) out += '\\\\u001b';\n else if (b === 0x0d) out += '\\\\r';\n else if (b === 0x09) out += '\\\\t';\n else if (b === 0x0a) out += '\\\\n';\n else if (b === 0x22) out += '\\\\\"';\n else if (b === 0x5c) out += '\\\\\\\\';\n else if (b >= 0x20 && b < 0x7f) out += String.fromCharCode(b);\n else out += `\\\\u${b.toString(16).padStart(4, '0')}`;\n }\n return `\"${out}\"`;\n}\n\nexport function bytesToDisplay(buf: Buffer): string {\n return Array.from(buf)\n .map((b) => {\n if (b === 0x1b) return 'ESC';\n if (b === 0x0d) return 'CR';\n if (b === 0x09) return 'TAB';\n if (b === 0x0a) return 'LF';\n if (b === 0x20) return 'SPC';\n if (b === 0x7f) return 'DEL';\n if (b > 0x20 && b < 0x7f) return String.fromCharCode(b);\n return `\\\\x${b.toString(16).padStart(2, '0')}`;\n })\n .join(' ');\n}\n\nexport async function cmdRun(id: string, cmd?: string, args: string[] = []): Promise<void> {\n if (!cmd) {\n // Warm-up mode: start server + session + PTY, no browser\n if (!(await isServerRunning())) {\n await startServer();\n }\n\n const check = await fetch(`${getBaseUrl()}/api/sessions/${encodeURIComponent(id)}`);\n if (check.status !== 200) {\n const res = await fetch(`${getBaseUrl()}/api/sessions`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ id }),\n });\n if (!res.ok) {\n const body = (await res.json()) as { error?: string };\n console.error(`webtty: ${body.error ?? `failed to create session (${res.status})`}`);\n process.exit(1);\n }\n }\n\n const wsUrl = `ws://127.0.0.1:${getPort()}/ws/${encodeURIComponent(id)}/pty?cols=80&rows=24`;\n await new Promise<void>((resolve, reject) => {\n const ws = new WebSocket(wsUrl);\n let opened = false;\n ws.onopen = () => {\n opened = true;\n ws.close(1000);\n resolve();\n };\n ws.onerror = () => reject(new Error('failed to connect to PTY'));\n ws.onclose = (evt) => {\n if (!opened) reject(new Error(`failed to start PTY (code ${(evt as CloseEvent).code})`));\n };\n }).catch((err: Error) => {\n console.error(`webtty: failed to start PTY: ${err.message}`);\n process.exit(1);\n });\n\n const url = `http://${toBrowserHost(loadConfig().host)}:${getPort()}/s/${id}`;\n console.log(url);\n return;\n }\n\n // Exec mode: headless command execution\n if (!(await isServerRunning())) {\n console.error('webtty: server is not running');\n process.exit(1);\n }\n\n let res: Response;\n try {\n res = await fetch(`${getBaseUrl()}/s/${encodeURIComponent(id)}/execute`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ cmd, args }),\n });\n } catch {\n console.error('webtty: server is not running');\n process.exit(1);\n }\n\n if (res.status === 404) {\n console.error(`webtty: session not found: ${id}`);\n process.exit(1);\n }\n if (res.status === 409) {\n console.error(`webtty: PTY not running for session: ${id}`);\n process.exit(1);\n }\n if (!res.ok || !res.body) {\n console.error(`webtty: run failed (${res.status})`);\n process.exit(1);\n }\n\n const reader = res.body.getReader();\n const decoder = new TextDecoder();\n let buf = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buf += decoder.decode(value, { stream: true });\n const lines = buf.split('\\n');\n buf = lines.pop() ?? '';\n for (const line of lines) {\n if (!line) continue;\n const msg = JSON.parse(line) as Record<string, unknown>;\n if (msg.stream === 'stdout') process.stdout.write(String(msg.data));\n else if (msg.stream === 'stderr') process.stderr.write(String(msg.data));\n else if ('exit' in msg) {\n if (msg.error) console.error(`webtty: ${msg.error}`);\n process.exit((msg.exit as number) ?? 1);\n }\n }\n }\n console.error('webtty: connection closed unexpectedly');\n process.exit(1);\n}\n\nexport function cmdKey(): void {\n if (!process.stdin.isTTY) {\n console.error('webtty key: requires an interactive terminal');\n process.exit(1);\n return;\n }\n\n const dim = '\\x1b[2m';\n const bold = '\\x1b[1m';\n const reset = '\\x1b[0m';\n\n const restoreTerminal = () => {\n try {\n process.stdin.setRawMode(false);\n } catch {}\n };\n process.once('exit', restoreTerminal);\n process.once('SIGINT', restoreTerminal);\n process.once('SIGTERM', restoreTerminal);\n\n process.stdin.setRawMode(true);\n process.stdin.resume();\n console.log('Press any key combo to see its chars value. q to quit.\\n');\n console.log(` ${dim}received${reset} → ${bold}chars${reset}`);\n console.log(` ${'─'.repeat(17)}`);\n\n let buf = Buffer.alloc(0);\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const flush = () => {\n if (buf.length === 0) return;\n const display = bytesToDisplay(buf).padEnd(8);\n console.log(` ${dim}${display}${reset} → ${bold}${bytesToChars(buf)}${reset}`);\n buf = Buffer.alloc(0);\n };\n\n process.stdin.on('data', (chunk: Buffer) => {\n if (chunk.length === 1 && chunk[0] === 0x71) {\n process.stdin.setRawMode(false);\n process.removeListener('exit', restoreTerminal);\n process.removeListener('SIGINT', restoreTerminal);\n process.removeListener('SIGTERM', restoreTerminal);\n console.log(` ${'─'.repeat(17)}\\n`);\n process.exit(0);\n }\n buf = Buffer.concat([buf, chunk]);\n if (timer) clearTimeout(timer);\n timer = setTimeout(flush, 50);\n });\n}\n",
7
+ "import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\n/** A single keyboard binding: intercepts `key`+`mods` and sends `chars` to the PTY. */\nexport interface KeyboardBinding {\n /** Key name matched case-insensitively against `KeyboardEvent.key` (e.g. `\"enter\"`, `\"arrowup\"`, `\"a\"`). */\n key: string;\n /** Modifier keys that must be active — and no others — for this binding to match. Accepted values: `\"shift\"`, `\"ctrl\"`, `\"alt\"`, `\"meta\"`. Order irrelevant. Unknown values are filtered out at config load time. */\n mods?: string[];\n /** Byte sequence sent verbatim to the PTY. Standard JSON escapes apply (`\\r`, `\\uXXXX`, etc.). */\n chars: string;\n}\n\n/** Terminal color palette. All keys are optional; omitted keys fall back to the Campbell defaults. */\nexport interface Theme {\n /** Terminal background. */\n background?: string;\n /** Default text color. */\n foreground?: string;\n /** Cursor color. */\n cursor?: string;\n /** Selection highlight. */\n selection?: string;\n /** ANSI 0. */\n black?: string;\n /** ANSI 1. */\n red?: string;\n /** ANSI 2. */\n green?: string;\n /** ANSI 3. */\n yellow?: string;\n /** ANSI 4. */\n blue?: string;\n /** ANSI 5. */\n purple?: string;\n /** ANSI 6. */\n cyan?: string;\n /** ANSI 7. */\n white?: string;\n /** ANSI 8. */\n brightBlack?: string;\n /** ANSI 9. */\n brightRed?: string;\n /** ANSI 10. */\n brightGreen?: string;\n /** ANSI 11. */\n brightYellow?: string;\n /** ANSI 12. */\n brightBlue?: string;\n /** ANSI 13. */\n brightPurple?: string;\n /** ANSI 14. */\n brightCyan?: string;\n /** ANSI 15. */\n brightWhite?: string;\n}\n\n/** Right-click behavior: `\"copyPaste\"` copies selection + clears it if selection exists, otherwise native menu; `\"default\"` always shows native context menu. */\nexport type RightClickBehavior = 'default' | 'copyPaste';\n\n/** Full webtty configuration. All keys are optional in the config file; missing keys fall back to {@link DEFAULT_CONFIG}. */\nexport interface Config {\n /** HTTP listen port; env `PORT` takes precedence. */\n port: number;\n /** Bind address; use `\"0.0.0.0\"` for remote access. */\n host: string;\n /** Shell for new sessions. */\n shell: string;\n /** `$TERM` env var passed to the shell. Fixed to `xterm-256color` — the PTY child talks to webtty, not to the parent terminal. */\n term: string;\n /** `$COLORTERM` env var passed to the shell. */\n colorTerm: string;\n /** PTY history buffer in bytes; used for server-side replay on reload/reconnect. */\n scrollback: number;\n /** Initial terminal width in columns. */\n cols: number;\n /** Initial terminal height in rows. */\n rows: number;\n /** Font size in px. */\n fontSize: number;\n /** CSS font-family stack. */\n fontFamily: string;\n /** Default cursor shape. Apps override at runtime via DECSCUSR — this is the startup default only. */\n cursorStyle: 'block' | 'bar' | 'underline';\n /** Default blink state. Apps override at runtime via DECSCUSR — this is the startup default only. */\n cursorStyleBlink: boolean;\n /** Auto-copy selection to clipboard on mouseup (kitty / Windows Terminal style). */\n copyOnSelect: boolean;\n /** `\"copyPaste\"` copies selection + clears it on right-click if selection exists; `\"default\"` always shows native context menu. */\n rightClickBehavior: RightClickBehavior;\n /** Mouse wheel scroll speed multiplier for apps with mouse tracking. `1` = one SGR per tick. Values `< 1` reduce rate; values `> 1` send multiple SGRs per tick. Must be `> 0`. */\n mouseScrollSpeed: number;\n /** Write server stdout/stderr to `~/.config/webtty/server.log`. Appends on each start. */\n logs: boolean;\n /** Terminal color palette. */\n theme: Theme;\n /** Custom key-to-sequence bindings. Merged with built-in defaults by `(key, mods)` identity. */\n keyboardBindings: KeyboardBinding[];\n /** Additional environment variables injected into PTY shells and execute commands. Merges over `process.env`; `TERM` and `COLORTERM` always take precedence for PTY spawns. */\n env: Record<string, string>;\n}\n\n/** Returns the webtty config directory: `~/.config/webtty`. */\nexport function configDir(): string {\n // os.homedir() is the authoritative home directory. process.env.HOME is used\n // as an override in tests (e.g. to isolate config state), but only when it is\n // an absolute path — guarding against accidental env pollution such as the\n // string \"undefined\" being assigned when HOME was originally unset on Windows.\n const home =\n process.env.HOME && path.isAbsolute(process.env.HOME) ? process.env.HOME : os.homedir();\n return path.join(home, '.config', 'webtty');\n}\n\nfunction getConfigPath(): string {\n return path.join(configDir(), 'config.json');\n}\n\n// NOTE: export for testing only; users should use loadConfig() and initConfig() instead\nexport const DEFAULT_THEME: Theme = {\n background: '#000000',\n foreground: '#CCCCCC',\n cursor: '#FFFFFF',\n selection: '#FFFFFF',\n black: '#0C0C0C',\n red: '#C50F1F',\n green: '#13A10E',\n yellow: '#C19C00',\n blue: '#0037DA',\n purple: '#881798',\n cyan: '#3A96DD',\n white: '#CCCCCC',\n brightBlack: '#767676',\n brightRed: '#E74856',\n brightGreen: '#16C60C',\n brightYellow: '#F9F1A5',\n brightBlue: '#3B78FF',\n brightPurple: '#B4009E',\n brightCyan: '#61D6D6',\n brightWhite: '#F2F2F2',\n};\n\n// NOTE: export for testing only; users should use loadConfig() and initConfig() instead\nexport const DEFAULT_KEYBOARD_BINDINGS: KeyboardBinding[] = [];\n\n// NOTE: export for testing only; users should use loadConfig() and initConfig() instead\nexport const DEFAULT_CONFIG: Config = {\n port: 2346,\n host: '127.0.0.1',\n shell:\n process.platform === 'win32'\n ? (process.env.COMSPEC ?? 'cmd.exe')\n : (process.env.SHELL ?? '/bin/bash'),\n term: 'xterm-256color',\n colorTerm: 'truecolor',\n scrollback: 256 * 1024,\n cols: 80,\n rows: 24,\n fontSize: 13,\n fontFamily: \"Menlo, Consolas, 'DejaVu Sans Mono', monospace\",\n cursorStyle: 'bar',\n cursorStyleBlink: true,\n copyOnSelect: true,\n rightClickBehavior: 'default' as RightClickBehavior,\n mouseScrollSpeed: 1,\n logs: false,\n theme: DEFAULT_THEME,\n keyboardBindings: DEFAULT_KEYBOARD_BINDINGS,\n env: {},\n};\n\nfunction bindingKey(b: KeyboardBinding): string {\n const mods = [...(b.mods ?? [])].sort().join('+');\n return `${b.key.toLowerCase()}|${mods}`;\n}\n\nconst VALID_MODS = new Set(['shift', 'ctrl', 'alt', 'meta']);\n\nfunction isValidBinding(b: unknown): b is KeyboardBinding {\n if (!b || typeof b !== 'object') return false;\n const o = b as Record<string, unknown>;\n if (typeof o.key !== 'string' || typeof o.chars !== 'string') return false;\n if (o.mods !== undefined && !Array.isArray(o.mods)) return false;\n return true;\n}\n\nfunction normalizeBinding(b: KeyboardBinding): KeyboardBinding {\n return {\n ...b,\n key: b.key.toLowerCase(),\n mods: (b.mods ?? []).map((m) => m.toLowerCase()).filter((m) => VALID_MODS.has(m)),\n };\n}\n\n/**\n * Merges user-supplied bindings over a set of defaults by `(key, mods)` identity.\n * User entries replace matching defaults; unmatched user entries are appended.\n *\n * @param defaults - Default bindings to merge over.\n * @param user - User-supplied bindings that override defaults.\n * @returns Merged bindings with user entries replacing matching defaults and new user entries appended.\n */\n// NOTE: export for testing only\nexport function mergeKeyboardBindings(\n defaults: KeyboardBinding[],\n user: KeyboardBinding[],\n): KeyboardBinding[] {\n const deduped = [...new Map(user.map((b) => [bindingKey(b), b])).values()];\n const overrides = new Map(deduped.map((b) => [bindingKey(b), b]));\n const merged = defaults.map((d) => overrides.get(bindingKey(d)) ?? d);\n const defaultKeys = new Set(defaults.map(bindingKey));\n for (const b of deduped) {\n if (!defaultKeys.has(bindingKey(b))) merged.push(b);\n }\n return merged;\n}\n\n/**\n * Load config from `~/.config/webtty/config.json`, merged over `DEFAULT_CONFIG`.\n *\n * On first run (file absent), writes the default port/host stub and returns defaults.\n * Unknown keys and keys with wrong types are silently ignored.\n * Throws if the file exists but cannot be read or contains invalid JSON.\n *\n * @returns The resolved {@link Config}, with all missing keys filled from {@link DEFAULT_CONFIG}.\n */\nexport function loadConfig(): Config {\n if (!fs.existsSync(getConfigPath())) {\n try {\n initConfig();\n } catch (err) {\n console.warn(\n `webtty: failed to write default config to ${getConfigPath()}: ${(err as Error).message}`,\n );\n return { ...DEFAULT_CONFIG };\n }\n }\n\n let raw: string;\n try {\n raw = fs.readFileSync(getConfigPath(), 'utf8');\n } catch (err) {\n throw new Error(\n `webtty: failed to read config at ${getConfigPath()}: ${(err as Error).message}`,\n );\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new Error(`webtty: invalid JSON in config file ${getConfigPath()}`);\n }\n\n const p = parsed as Partial<Config>;\n return {\n ...DEFAULT_CONFIG,\n ...(typeof p.port === 'number' && { port: p.port }),\n ...(typeof p.host === 'string' && { host: p.host }),\n ...(typeof p.shell === 'string' && { shell: p.shell }),\n ...(typeof p.term === 'string' && { term: p.term }),\n ...(typeof p.colorTerm === 'string' && { colorTerm: p.colorTerm }),\n ...(typeof p.scrollback === 'number' && { scrollback: p.scrollback }),\n ...(typeof p.cols === 'number' && { cols: p.cols }),\n ...(typeof p.rows === 'number' && { rows: p.rows }),\n ...(typeof p.fontSize === 'number' && { fontSize: p.fontSize }),\n ...(typeof p.fontFamily === 'string' && { fontFamily: p.fontFamily }),\n ...(typeof p.cursorStyle === 'string' &&\n (p.cursorStyle === 'block' || p.cursorStyle === 'bar' || p.cursorStyle === 'underline') && {\n cursorStyle: p.cursorStyle,\n }),\n ...(typeof p.cursorStyleBlink === 'boolean' && { cursorStyleBlink: p.cursorStyleBlink }),\n ...(typeof p.copyOnSelect === 'boolean' && { copyOnSelect: p.copyOnSelect }),\n ...(typeof p.rightClickBehavior === 'string' && {\n rightClickBehavior: (p.rightClickBehavior === 'copyPaste'\n ? 'copyPaste'\n : 'default') as RightClickBehavior,\n }),\n ...(typeof p.mouseScrollSpeed === 'number' &&\n p.mouseScrollSpeed > 0 && { mouseScrollSpeed: p.mouseScrollSpeed }),\n ...(typeof p.logs === 'boolean' && { logs: p.logs }),\n ...(p.theme && typeof p.theme === 'object' && { theme: { ...DEFAULT_THEME, ...p.theme } }),\n ...(Array.isArray(p.keyboardBindings) && {\n keyboardBindings: mergeKeyboardBindings(\n DEFAULT_KEYBOARD_BINDINGS,\n p.keyboardBindings.filter(isValidBinding).map(normalizeBinding),\n ),\n }),\n ...(p.env &&\n typeof p.env === 'object' &&\n !Array.isArray(p.env) && {\n env: Object.fromEntries(\n Object.entries(p.env as Record<string, unknown>).filter(\n ([k, v]) => typeof k === 'string' && typeof v === 'string',\n ) as [string, string][],\n ),\n }),\n };\n}\n\n/**\n * Write the initial config stub (`port` + `host` only) to `~/.config/webtty/config.json`.\n *\n * Only writes the two keys that are safe to persist as defaults; all other\n * keys are intentionally omitted so future webtty versions can add new fields\n * without the stub going stale.\n */\nexport function initConfig(): void {\n fs.mkdirSync(path.dirname(getConfigPath()), { recursive: true });\n const content = JSON.stringify(\n {\n port: DEFAULT_CONFIG.port,\n host: DEFAULT_CONFIG.host,\n },\n null,\n 2,\n );\n fs.writeFileSync(getConfigPath(), content, 'utf8');\n}\n",
8
+ "import * as childProcess from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { configDir, loadConfig } from '../config';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n/** Active server port, read from config on first call (env PORT is intentionally ignored). */\nexport function getPort(): number {\n return loadConfig().port;\n}\n\n/** Base URL for internal CLI↔server API calls (always 127.0.0.1, avoids IPv6 lookup). */\nexport function getBaseUrl(): string {\n return `http://127.0.0.1:${getPort()}`;\n}\n\n/** Returns the path to the server log file: `~/.config/webtty/server.log`. */\nexport function logPath(): string {\n return path.join(configDir(), 'server.log');\n}\n\n/** Returns `true` if the webtty server is reachable and responding to API requests. */\nexport async function isServerRunning(): Promise<boolean> {\n try {\n const res = await fetch(`${getBaseUrl()}/api/sessions`);\n if (!res.ok) return false;\n const body = await res.json();\n return Array.isArray(body);\n } catch {\n return false;\n }\n}\n\n/**\n * Spawns the server process detached, then polls until it is ready or `timeoutMs` expires.\n *\n * @param timeoutMs - Maximum time to wait for the server to start (default: 10000 ms).\n * @param _spawn - Spawn function override for testing (default: childProcess.spawn).\n * @throws Exits the process with code 1 if the server entry is not found or fails to start in time.\n */\nexport async function startServer(timeoutMs = 10000, _spawn = childProcess.spawn): Promise<void> {\n const isBun = typeof (globalThis as Record<string, unknown>).Bun !== 'undefined';\n\n // On Windows, Bun's net.Socket doesn't support the fd-based named-pipe\n // wrapping that node-pty's ConPTY backend requires, so node-pty fails under\n // Bun on Windows. bunx works because its #!/usr/bin/env node shim runs the\n // server with Node.js instead. Mirror that here explicitly.\n const useNode = process.platform === 'win32' && isBun;\n const serverExec = useNode ? 'node' : process.execPath;\n\n // Resolve the server entry. When useNode is true the executor is Node.js,\n // which cannot run .ts files, so we always need the compiled .js output.\n // The CLI may itself run from source (src/cli/) or from dist (dist/cli/);\n // adjust the relative path accordingly so we always land in the same dist/.\n const runningFromSrc = __filename.endsWith('.ts');\n let serverEntry: string;\n if (useNode) {\n serverEntry = runningFromSrc\n ? path.resolve(__dirname, '../../dist/server/index.js') // src/cli → dist/server\n : path.resolve(__dirname, '../server/index.js'); // dist/cli dist/server\n } else {\n serverEntry = path.resolve(\n __dirname,\n runningFromSrc ? '../server/index.ts' : '../server/index.js',\n );\n }\n if (!fs.existsSync(serverEntry)) {\n console.error(`webtty: server entry not found at ${serverEntry}`);\n (process.exit as (code?: number) => void)(1);\n return;\n }\n\n const config = loadConfig();\n let stdio: childProcess.SpawnOptions['stdio'] = 'ignore';\n let logFd: number | undefined;\n if (config.logs) {\n const log = logPath();\n fs.mkdirSync(path.dirname(log), { recursive: true });\n logFd = fs.openSync(log, 'a');\n stdio = ['ignore', logFd, logFd];\n }\n\n const child = _spawn(serverExec, [serverEntry], {\n detached: true,\n stdio,\n env: { ...process.env, PORT: String(config.port) },\n });\n child.on('error', (err) => {\n const hint = useNode\n ? ' (Node.js must be on PATH when running webtty under Bun on Windows)'\n : '';\n console.error(`webtty: failed to start server: ${err.message}${hint}`);\n process.exit(1);\n });\n child.unref();\n if (logFd !== undefined) fs.closeSync(logFd);\n\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n if (await isServerRunning()) return;\n await new Promise((r) => setTimeout(r, 100));\n }\n console.error('webtty: server did not start in time');\n process.exit(1);\n}\n\n/**\n * Sends `POST /api/server/stop`, then polls until the server is no longer reachable.\n *\n * @param baseUrl - The server base URL (default: getBaseUrl()).\n * @param timeoutMs - Maximum time to wait for the server to stop (default: 5000 ms).\n * @returns `true` if the server stopped successfully, `false` otherwise.\n */\nexport async function stopServer(\n baseUrl: string = getBaseUrl(),\n timeoutMs = 5000,\n): Promise<boolean> {\n try {\n const res = await fetch(`${baseUrl}/api/server/stop`, { method: 'POST' });\n if (!res.ok) return false;\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n if (!(await isServerRunning())) return true;\n await new Promise((r) => setTimeout(r, 100));\n }\n return false;\n } catch {\n return false;\n }\n}\n\n/**\n * Opens `url` in the default system browser.\n * No-op in test environments or when `WEBTTY_NO_OPEN=1` is set.\n *\n * @param url - The URL to open.\n * @param _spawn - Spawn function override for testing (default: childProcess.spawn).\n */\nexport function openBrowser(url: string, _spawn = childProcess.spawn): void {\n if (process.env.WEBTTY_NO_OPEN === '1') return;\n if (process.env.NODE_ENV === 'test') return;\n if (process.platform === 'win32') {\n _spawn('cmd.exe', ['/c', 'start', '', url], { detached: true, stdio: 'ignore' }).unref();\n } else {\n const cmd = process.platform === 'darwin' ? 'open' : 'xdg-open';\n _spawn(cmd, [url], { detached: true, stdio: 'ignore' }).unref();\n }\n}\n"
9
9
  ],
10
- "mappings": "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",
11
- "debugId": "8359C04A2D29CCC864756E2164756E21",
10
+ "mappings": "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",
11
+ "debugId": "5CD11DFCEDA2DDF864756E2164756E21",
12
12
  "names": []
13
13
  }
@@ -22,8 +22,7 @@ var exports_bun = {};
22
22
  __export(exports_bun, {
23
23
  spawn: () => spawn
24
24
  });
25
- import { homedir } from "node:os";
26
- function spawn(shell, cols, rows, term, colorTerm) {
25
+ function spawn(shell, cols, rows, term, colorTerm, cwd, env) {
27
26
  let onDataCb;
28
27
  let onExitCb;
29
28
  const proc = Bun.spawn([shell], {
@@ -34,8 +33,8 @@ function spawn(shell, cols, rows, term, colorTerm) {
34
33
  onDataCb?.(Buffer.from(data).toString("utf8"));
35
34
  }
36
35
  },
37
- cwd: homedir(),
38
- env: { ...process.env, TERM: term, COLORTERM: colorTerm }
36
+ cwd,
37
+ env: { ...process.env, ...env, TERM: term, COLORTERM: colorTerm }
39
38
  });
40
39
  proc.exited.then((exitCode) => {
41
40
  onExitCb?.({ exitCode: exitCode ?? 0 });
@@ -59,23 +58,21 @@ function spawn(shell, cols, rows, term, colorTerm) {
59
58
  }
60
59
  };
61
60
  }
62
- var init_bun = () => {};
63
61
 
64
62
  // src/pty/node.ts
65
63
  var exports_node = {};
66
64
  __export(exports_node, {
67
65
  spawn: () => spawn2
68
66
  });
69
- import { homedir as homedir2 } from "node:os";
70
67
  import nodePty from "@lydell/node-pty";
71
- function spawn2(shell, cols, rows, term, colorTerm) {
68
+ function spawn2(shell, cols, rows, term, colorTerm, cwd, env) {
72
69
  const shellArgs = process.platform === "win32" && /cmd\.exe$/i.test(shell) ? ["/d"] : [];
73
70
  const ptyProc = nodePty.spawn(shell, shellArgs, {
74
71
  name: term,
75
72
  cols,
76
73
  rows,
77
- cwd: homedir2(),
78
- env: { ...process.env, TERM: term, COLORTERM: colorTerm }
74
+ cwd,
75
+ env: { ...process.env, ...env, TERM: term, COLORTERM: colorTerm }
79
76
  });
80
77
  return {
81
78
  pid: ptyProc.pid,
@@ -99,7 +96,7 @@ function spawn2(shell, cols, rows, term, colorTerm) {
99
96
  var init_node = () => {};
100
97
 
101
98
  // src/server/index.ts
102
- import fs3 from "node:fs";
99
+ import fs4 from "node:fs";
103
100
  import http from "node:http";
104
101
  import path4 from "node:path";
105
102
  import { fileURLToPath as fileURLToPath2 } from "node:url";
@@ -156,7 +153,8 @@ var DEFAULT_CONFIG = {
156
153
  mouseScrollSpeed: 1,
157
154
  logs: false,
158
155
  theme: DEFAULT_THEME,
159
- keyboardBindings: DEFAULT_KEYBOARD_BINDINGS
156
+ keyboardBindings: DEFAULT_KEYBOARD_BINDINGS,
157
+ env: {}
160
158
  };
161
159
  function bindingKey(b) {
162
160
  const mods = [...b.mods ?? []].sort().join("+");
@@ -238,6 +236,9 @@ function loadConfig() {
238
236
  ...p.theme && typeof p.theme === "object" && { theme: { ...DEFAULT_THEME, ...p.theme } },
239
237
  ...Array.isArray(p.keyboardBindings) && {
240
238
  keyboardBindings: mergeKeyboardBindings(DEFAULT_KEYBOARD_BINDINGS, p.keyboardBindings.filter(isValidBinding).map(normalizeBinding))
239
+ },
240
+ ...p.env && typeof p.env === "object" && !Array.isArray(p.env) && {
241
+ env: Object.fromEntries(Object.entries(p.env).filter(([k, v]) => typeof k === "string" && typeof v === "string"))
241
242
  }
242
243
  };
243
244
  }
@@ -251,9 +252,13 @@ function initConfig() {
251
252
  }
252
253
 
253
254
  // src/server/routes.ts
255
+ import { spawn as spawn3 } from "node:child_process";
256
+ import fs3 from "node:fs";
257
+ import { homedir as homedir2 } from "node:os";
254
258
  import path3 from "node:path";
255
259
 
256
260
  // src/server/session.ts
261
+ import { homedir } from "node:os";
257
262
  var sessionRegistry = new Map;
258
263
  var lastUsedId = null;
259
264
  function setLastUsedId(id) {
@@ -266,10 +271,11 @@ function isValidId(id) {
266
271
  function generateId() {
267
272
  return Math.floor(Math.random() * 4294967295).toString(16).padStart(8, "0");
268
273
  }
269
- function createSession(id) {
274
+ function createSession(id, baseDir = homedir()) {
270
275
  const session = {
271
276
  id,
272
277
  createdAt: Date.now(),
278
+ baseDir,
273
279
  pty: null,
274
280
  clients: new Set,
275
281
  subscribers: new Set,
@@ -282,6 +288,7 @@ function sessionToJson(s) {
282
288
  return {
283
289
  id: s.id,
284
290
  createdAt: s.createdAt,
291
+ baseDir: s.baseDir,
285
292
  connected: s.clients.size > 0,
286
293
  pid: s.pty?.pid ?? null
287
294
  };
@@ -350,9 +357,9 @@ import { WebSocketServer } from "ws";
350
357
  // src/pty/index.ts
351
358
  var isBun = !!process.versions.bun && process.platform !== "win32";
352
359
  console.log(`pty: ${isBun ? "Bun.Terminal" : "node-pty"}`);
353
- var { spawn: _spawn } = await (isBun ? Promise.resolve().then(() => (init_bun(), exports_bun)) : Promise.resolve().then(() => (init_node(), exports_node)));
354
- function spawnForSession(cols, rows, shell, term, colorTerm) {
355
- return _spawn(shell, cols, rows, term, colorTerm);
360
+ var { spawn: _spawn } = await (isBun ? Promise.resolve().then(() => exports_bun) : Promise.resolve().then(() => (init_node(), exports_node)));
361
+ function spawnForSession(cols, rows, shell, term, colorTerm, cwd, env) {
362
+ return _spawn(shell, cols, rows, term, colorTerm, cwd, env);
356
363
  }
357
364
 
358
365
  // src/server/websocket.ts
@@ -494,7 +501,7 @@ function createWebSocketServer(httpServer) {
494
501
  setLastUsedId(id);
495
502
  if (!session.pty) {
496
503
  const config = loadConfig();
497
- session.pty = spawnForSession(cols, rows, config.shell, config.term, config.colorTerm);
504
+ session.pty = spawnForSession(cols, rows, config.shell, config.term, config.colorTerm, session.baseDir, config.env);
498
505
  session.pty.onData((data) => {
499
506
  session.scrollback = (session.scrollback + data).slice(-config.scrollback);
500
507
  for (const client of session.clients) {
@@ -556,6 +563,15 @@ function createWebSocketServer(httpServer) {
556
563
 
557
564
  // src/server/routes.ts
558
565
  var MAX_BODY = 64 * 1024;
566
+ var CORS_HEADERS = {
567
+ "Access-Control-Allow-Origin": "*",
568
+ "Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS",
569
+ "Access-Control-Allow-Headers": "Content-Type"
570
+ };
571
+ function setCors(res) {
572
+ for (const [k, v] of Object.entries(CORS_HEADERS))
573
+ res.setHeader(k, v);
574
+ }
559
575
  function decodeId(raw) {
560
576
  try {
561
577
  return decodeURIComponent(raw);
@@ -586,6 +602,12 @@ function readJson(req) {
586
602
  async function handleRequest(req, res, distPath, wasmPath, clientDistPath, onStop) {
587
603
  const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "127.0.0.1"}`);
588
604
  const pathname = url.pathname;
605
+ setCors(res);
606
+ if (req.method === "OPTIONS") {
607
+ res.writeHead(204);
608
+ res.end();
609
+ return;
610
+ }
589
611
  if (req.method === "POST" && pathname === "/api/server/stop") {
590
612
  res.writeHead(200, { "Content-Type": "text/plain" });
591
613
  res.end("stopping");
@@ -640,7 +662,23 @@ async function handleRequest(req, res, distPath, wasmPath, clientDistPath, onSto
640
662
  res.end(JSON.stringify({ error: `session already exists: ${id}` }));
641
663
  return;
642
664
  }
643
- const session = createSession(id);
665
+ const baseDir = body.baseDir ?? homedir2();
666
+ if (!path3.isAbsolute(baseDir)) {
667
+ res.writeHead(400, { "Content-Type": "application/json" });
668
+ res.end(JSON.stringify({ error: "baseDir must be an absolute path" }));
669
+ return;
670
+ }
671
+ if (!fs3.existsSync(baseDir)) {
672
+ res.writeHead(400, { "Content-Type": "application/json" });
673
+ res.end(JSON.stringify({ error: `baseDir does not exist: ${baseDir}` }));
674
+ return;
675
+ }
676
+ if (!fs3.statSync(baseDir).isDirectory()) {
677
+ res.writeHead(400, { "Content-Type": "application/json" });
678
+ res.end(JSON.stringify({ error: `baseDir is not a directory: ${baseDir}` }));
679
+ return;
680
+ }
681
+ const session = createSession(id, baseDir);
644
682
  res.writeHead(201, { "Content-Type": "application/json" });
645
683
  res.end(JSON.stringify(sessionToJson(session)));
646
684
  return;
@@ -804,6 +842,106 @@ async function handleRequest(req, res, distPath, wasmPath, clientDistPath, onSto
804
842
  });
805
843
  return;
806
844
  }
845
+ const executeMatch = pathname.match(/^\/s\/([^/]+)\/execute$/);
846
+ if (req.method === "POST" && executeMatch) {
847
+ const id = decodeId(executeMatch[1]);
848
+ if (!id) {
849
+ res.writeHead(400);
850
+ res.end("Bad Request");
851
+ return;
852
+ }
853
+ if (!(req.headers["content-type"] ?? "").startsWith("application/json")) {
854
+ res.writeHead(400);
855
+ res.end("Bad Request");
856
+ return;
857
+ }
858
+ const session = sessionRegistry.get(id);
859
+ if (!session) {
860
+ res.writeHead(404);
861
+ res.end("Not Found");
862
+ return;
863
+ }
864
+ if (session.pty === null) {
865
+ res.writeHead(409);
866
+ res.end("PTY not running");
867
+ return;
868
+ }
869
+ let execBody;
870
+ try {
871
+ execBody = await readJson(req);
872
+ } catch (err) {
873
+ const status = err.status === 413 ? 413 : 400;
874
+ res.writeHead(status);
875
+ res.end(status === 413 ? "Payload Too Large" : "invalid JSON");
876
+ return;
877
+ }
878
+ const { cmd, args, stdin } = execBody;
879
+ if (typeof cmd !== "string" || cmd.length === 0) {
880
+ res.writeHead(400);
881
+ res.end("invalid body");
882
+ return;
883
+ }
884
+ if (!Array.isArray(args) || !args.every((a) => typeof a === "string")) {
885
+ res.writeHead(400);
886
+ res.end("invalid body");
887
+ return;
888
+ }
889
+ let child;
890
+ try {
891
+ const execConfig = loadConfig();
892
+ child = spawn3(cmd, args, {
893
+ env: { ...process.env, ...execConfig.env },
894
+ cwd: session.baseDir
895
+ });
896
+ } catch (err) {
897
+ res.writeHead(500);
898
+ res.end(`spawn error: ${String(err)}`);
899
+ return;
900
+ }
901
+ res.writeHead(200, {
902
+ "Content-Type": "application/x-ndjson",
903
+ "Cache-Control": "no-cache"
904
+ });
905
+ let done = false;
906
+ child.on("error", (err) => {
907
+ if (done)
908
+ return;
909
+ done = true;
910
+ const msg = err.message;
911
+ const code = err.code;
912
+ res.write(`${JSON.stringify({ stream: "stderr", data: `${msg}
913
+ ` })}
914
+ `);
915
+ res.write(`${JSON.stringify({ exit: 1, error: msg, ...code ? { code } : {} })}
916
+ `);
917
+ res.end();
918
+ });
919
+ if (typeof stdin === "string") {
920
+ child.stdin?.write(stdin);
921
+ }
922
+ child.stdin?.end();
923
+ child.stdout?.on("data", (chunk) => {
924
+ res.write(`${JSON.stringify({ stream: "stdout", data: chunk.toString() })}
925
+ `);
926
+ });
927
+ child.stderr?.on("data", (chunk) => {
928
+ res.write(`${JSON.stringify({ stream: "stderr", data: chunk.toString() })}
929
+ `);
930
+ });
931
+ child.on("close", (code) => {
932
+ if (done)
933
+ return;
934
+ done = true;
935
+ res.write(`${JSON.stringify({ exit: code ?? 1 })}
936
+ `);
937
+ res.end();
938
+ });
939
+ req.on("close", () => {
940
+ if (!done)
941
+ child.kill();
942
+ });
943
+ return;
944
+ }
807
945
  const pidMatch = pathname.match(/^\/p\/(\d+)$/);
808
946
  if (req.method === "GET" && pidMatch) {
809
947
  const pid = parseInt(pidMatch[1], 10);
@@ -826,8 +964,8 @@ async function handleRequest(req, res, distPath, wasmPath, clientDistPath, onSto
826
964
  const relativePath = pathname.slice(6);
827
965
  const ownFile = path3.resolve(clientDistPath, relativePath);
828
966
  if (ownFile.startsWith(clientDistPath + path3.sep)) {
829
- const fs3 = await import("node:fs");
830
- if (fs3.existsSync(ownFile)) {
967
+ const fs4 = await import("node:fs");
968
+ if (fs4.existsSync(ownFile)) {
831
969
  serveFile(ownFile, res);
832
970
  return;
833
971
  }
@@ -860,7 +998,7 @@ var { distPath, wasmPath } = findGhosttyWeb();
860
998
  var projectRoot = path4.resolve(__dirname3, "..", "..");
861
999
  var builtDistPath = path4.join(projectRoot, "dist");
862
1000
  var srcClientPath = path4.join(projectRoot, "src", "client");
863
- var clientDistPath = fs3.existsSync(path4.join(builtDistPath, "client.html")) ? builtDistPath : srcClientPath;
1001
+ var clientDistPath = fs4.existsSync(path4.join(builtDistPath, "client.html")) ? builtDistPath : srcClientPath;
864
1002
  function shutdown() {
865
1003
  closeAllSessions();
866
1004
  wss.close();
@@ -871,8 +1009,20 @@ function shutdown() {
871
1009
  exit();
872
1010
  });
873
1011
  }
1012
+ process.on("uncaughtException", (err) => {
1013
+ console.error("[webtty] uncaughtException:", err);
1014
+ });
1015
+ process.on("unhandledRejection", (reason) => {
1016
+ console.error("[webtty] unhandledRejection:", reason);
1017
+ });
874
1018
  var httpServer = http.createServer((req, res) => {
875
- handleRequest(req, res, distPath, wasmPath, clientDistPath, shutdown);
1019
+ handleRequest(req, res, distPath, wasmPath, clientDistPath, shutdown).catch((err) => {
1020
+ console.error("[webtty] unhandled route error:", err);
1021
+ if (!res.headersSent) {
1022
+ res.writeHead(500);
1023
+ res.end("Internal Server Error");
1024
+ }
1025
+ });
876
1026
  });
877
1027
  var wss = createWebSocketServer(httpServer);
878
1028
  setLastSessionClosedHandler(shutdown);
@@ -886,4 +1036,4 @@ httpServer.listen(HTTP_PORT, HTTP_HOST, () => {
886
1036
  console.log(`listening on http://${HTTP_HOST_DISPLAY}:${HTTP_PORT}`);
887
1037
  });
888
1038
 
889
- //# debugId=493EB9B10F4BA8D664756E2164756E21
1039
+ //# debugId=17F1B5AF7114819864756E2164756E21
@@ -2,17 +2,17 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/pty/bun.ts", "../src/pty/node.ts", "../src/server/index.ts", "../src/config.ts", "../src/server/routes.ts", "../src/server/session.ts", "../src/server/static.ts", "../src/server/websocket.ts", "../src/pty/index.ts"],
4
4
  "sourcesContent": [
5
- "import { homedir } from 'node:os';\nimport type { PtyProcess } from './types';\n\n/**\n * Spawns a PTY-backed shell using Bun's native `Bun.spawn` terminal API.\n *\n * @param shell - Shell executable path (e.g., `/bin/bash`).\n * @param cols - Terminal width in columns.\n * @param rows - Terminal height in rows.\n * @param term - `$TERM` environment variable (e.g., `xterm-256color`).\n * @param colorTerm - `$COLORTERM` environment variable (e.g., `truecolor`).\n * @returns A {@link PtyProcess} handle for reading/writing and managing the PTY.\n */\nexport function spawn(\n shell: string,\n cols: number,\n rows: number,\n term: string,\n colorTerm: string,\n): PtyProcess {\n let onDataCb: ((data: string) => void) | undefined;\n let onExitCb: ((e: { exitCode: number }) => void) | undefined;\n\n const proc = Bun.spawn([shell], {\n terminal: {\n cols,\n rows,\n data(_term: unknown, data: Uint8Array) {\n onDataCb?.(Buffer.from(data).toString('utf8'));\n },\n },\n cwd: homedir(),\n env: { ...process.env, TERM: term, COLORTERM: colorTerm },\n });\n\n proc.exited.then((exitCode) => {\n onExitCb?.({ exitCode: exitCode ?? 0 });\n });\n\n return {\n pid: proc.pid,\n onData(cb) {\n onDataCb = cb;\n },\n onExit(cb) {\n onExitCb = cb;\n },\n write(data) {\n proc.terminal?.write(data);\n },\n resize(cols, rows) {\n proc.terminal?.resize(cols, rows);\n },\n kill() {\n proc.kill();\n },\n };\n}\n",
6
- "import { homedir } from 'node:os';\nimport nodePty from '@lydell/node-pty';\nimport type { PtyProcess } from './types';\n\n/**\n * Spawns a PTY-backed shell using the `@lydell/node-pty` native addon.\n *\n * @param shell - Shell executable path (e.g., `/bin/bash`).\n * @param cols - Terminal width in columns.\n * @param rows - Terminal height in rows.\n * @param term - `$TERM` environment variable (e.g., `xterm-256color`).\n * @param colorTerm - `$COLORTERM` environment variable (e.g., `truecolor`).\n * @returns A {@link PtyProcess} handle for reading/writing and managing the PTY.\n */\nexport function spawn(\n shell: string,\n cols: number,\n rows: number,\n term: string,\n colorTerm: string,\n): PtyProcess {\n // On Windows, cmd.exe may have an AutoRun registry key that launches shell\n // enhancers (e.g. clink). These take over the ConPTY pipe and cause\n // ERR_SOCKET_CLOSED on the first write. Pass /d to disable AutoRun.\n const shellArgs = process.platform === 'win32' && /cmd\\.exe$/i.test(shell) ? ['/d'] : [];\n\n const ptyProc = nodePty.spawn(shell, shellArgs, {\n name: term,\n cols,\n rows,\n cwd: homedir(),\n env: { ...process.env, TERM: term, COLORTERM: colorTerm },\n });\n\n return {\n pid: ptyProc.pid,\n onData(cb) {\n ptyProc.onData(cb);\n },\n onExit(cb) {\n ptyProc.onExit(cb);\n },\n write(data) {\n ptyProc.write(data);\n },\n resize(cols, rows) {\n ptyProc.resize(cols, rows);\n },\n kill() {\n ptyProc.kill();\n },\n };\n}\n",
7
- "import fs from 'node:fs';\nimport http from 'node:http';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { loadConfig } from '../config';\nimport { handleRequest } from './routes';\nimport { findGhosttyWeb } from './static';\nimport { closeAllSessions, createWebSocketServer, setLastSessionClosedHandler } from './websocket';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nconst config = loadConfig();\nconst HTTP_PORT = Number(process.env.PORT) || config.port;\n// 'localhost' resolves to ::1 (IPv6) on modern macOS/Node; bind to 127.0.0.1 instead\n// but keep 'localhost' as the display host so browser URLs use it as intended.\nconst HTTP_HOST_DISPLAY = config.host;\nconst HTTP_HOST = config.host === 'localhost' ? '127.0.0.1' : config.host;\n\nconst { distPath, wasmPath } = findGhosttyWeb();\nconst projectRoot = path.resolve(__dirname, '..', '..');\nconst builtDistPath = path.join(projectRoot, 'dist');\nconst srcClientPath = path.join(projectRoot, 'src', 'client');\nconst clientDistPath = fs.existsSync(path.join(builtDistPath, 'client.html'))\n ? builtDistPath\n : srcClientPath;\n\nfunction shutdown() {\n closeAllSessions();\n wss.close();\n const exit = () => process.exit(0);\n const shutdownTimeout = setTimeout(exit, 1000);\n httpServer.close(() => {\n clearTimeout(shutdownTimeout);\n exit();\n });\n}\n\nconst httpServer = http.createServer((req, res) => {\n handleRequest(req, res, distPath, wasmPath, clientDistPath, shutdown);\n});\n\nconst wss = createWebSocketServer(httpServer);\nsetLastSessionClosedHandler(shutdown);\n\nprocess.on('SIGINT', () => {\n console.log('\\n\\nShutting down...');\n shutdown();\n});\n\nhttpServer.listen(HTTP_PORT, HTTP_HOST, () => {\n console.log(`listening on http://${HTTP_HOST_DISPLAY}:${HTTP_PORT}`);\n});\n",
8
- "import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\n/** A single keyboard binding: intercepts `key`+`mods` and sends `chars` to the PTY. */\nexport interface KeyboardBinding {\n /** Key name matched case-insensitively against `KeyboardEvent.key` (e.g. `\"enter\"`, `\"arrowup\"`, `\"a\"`). */\n key: string;\n /** Modifier keys that must be active — and no others — for this binding to match. Accepted values: `\"shift\"`, `\"ctrl\"`, `\"alt\"`, `\"meta\"`. Order irrelevant. Unknown values are filtered out at config load time. */\n mods?: string[];\n /** Byte sequence sent verbatim to the PTY. Standard JSON escapes apply (`\\r`, `\\uXXXX`, etc.). */\n chars: string;\n}\n\n/** Terminal color palette. All keys are optional; omitted keys fall back to the Campbell defaults. */\nexport interface Theme {\n /** Terminal background. */\n background?: string;\n /** Default text color. */\n foreground?: string;\n /** Cursor color. */\n cursor?: string;\n /** Selection highlight. */\n selection?: string;\n /** ANSI 0. */\n black?: string;\n /** ANSI 1. */\n red?: string;\n /** ANSI 2. */\n green?: string;\n /** ANSI 3. */\n yellow?: string;\n /** ANSI 4. */\n blue?: string;\n /** ANSI 5. */\n purple?: string;\n /** ANSI 6. */\n cyan?: string;\n /** ANSI 7. */\n white?: string;\n /** ANSI 8. */\n brightBlack?: string;\n /** ANSI 9. */\n brightRed?: string;\n /** ANSI 10. */\n brightGreen?: string;\n /** ANSI 11. */\n brightYellow?: string;\n /** ANSI 12. */\n brightBlue?: string;\n /** ANSI 13. */\n brightPurple?: string;\n /** ANSI 14. */\n brightCyan?: string;\n /** ANSI 15. */\n brightWhite?: string;\n}\n\n/** Right-click behavior: `\"copyPaste\"` copies selection + clears it if selection exists, otherwise native menu; `\"default\"` always shows native context menu. */\nexport type RightClickBehavior = 'default' | 'copyPaste';\n\n/** Full webtty configuration. All keys are optional in the config file; missing keys fall back to {@link DEFAULT_CONFIG}. */\nexport interface Config {\n /** HTTP listen port; env `PORT` takes precedence. */\n port: number;\n /** Bind address; use `\"0.0.0.0\"` for remote access. */\n host: string;\n /** Shell for new sessions. */\n shell: string;\n /** `$TERM` env var passed to the shell. Fixed to `xterm-256color` — the PTY child talks to webtty, not to the parent terminal. */\n term: string;\n /** `$COLORTERM` env var passed to the shell. */\n colorTerm: string;\n /** PTY history buffer in bytes; used for server-side replay on reload/reconnect. */\n scrollback: number;\n /** Initial terminal width in columns. */\n cols: number;\n /** Initial terminal height in rows. */\n rows: number;\n /** Font size in px. */\n fontSize: number;\n /** CSS font-family stack. */\n fontFamily: string;\n /** Default cursor shape. Apps override at runtime via DECSCUSR — this is the startup default only. */\n cursorStyle: 'block' | 'bar' | 'underline';\n /** Default blink state. Apps override at runtime via DECSCUSR — this is the startup default only. */\n cursorStyleBlink: boolean;\n /** Auto-copy selection to clipboard on mouseup (kitty / Windows Terminal style). */\n copyOnSelect: boolean;\n /** `\"copyPaste\"` copies selection + clears it on right-click if selection exists; `\"default\"` always shows native context menu. */\n rightClickBehavior: RightClickBehavior;\n /** Mouse wheel scroll speed multiplier for apps with mouse tracking. `1` = one SGR per tick. Values `< 1` reduce rate; values `> 1` send multiple SGRs per tick. Must be `> 0`. */\n mouseScrollSpeed: number;\n /** Write server stdout/stderr to `~/.config/webtty/server.log`. Appends on each start. */\n logs: boolean;\n /** Terminal color palette. */\n theme: Theme;\n /** Custom key-to-sequence bindings. Merged with built-in defaults by `(key, mods)` identity. */\n keyboardBindings: KeyboardBinding[];\n}\n\n/** Returns the webtty config directory: `~/.config/webtty`. */\nexport function configDir(): string {\n // os.homedir() is the authoritative home directory. process.env.HOME is used\n // as an override in tests (e.g. to isolate config state), but only when it is\n // an absolute path — guarding against accidental env pollution such as the\n // string \"undefined\" being assigned when HOME was originally unset on Windows.\n const home =\n process.env.HOME && path.isAbsolute(process.env.HOME) ? process.env.HOME : os.homedir();\n return path.join(home, '.config', 'webtty');\n}\n\nfunction getConfigPath(): string {\n return path.join(configDir(), 'config.json');\n}\n\n// NOTE: export for testing only; users should use loadConfig() and initConfig() instead\nexport const DEFAULT_THEME: Theme = {\n background: '#000000',\n foreground: '#CCCCCC',\n cursor: '#FFFFFF',\n selection: '#FFFFFF',\n black: '#0C0C0C',\n red: '#C50F1F',\n green: '#13A10E',\n yellow: '#C19C00',\n blue: '#0037DA',\n purple: '#881798',\n cyan: '#3A96DD',\n white: '#CCCCCC',\n brightBlack: '#767676',\n brightRed: '#E74856',\n brightGreen: '#16C60C',\n brightYellow: '#F9F1A5',\n brightBlue: '#3B78FF',\n brightPurple: '#B4009E',\n brightCyan: '#61D6D6',\n brightWhite: '#F2F2F2',\n};\n\n// NOTE: export for testing only; users should use loadConfig() and initConfig() instead\nexport const DEFAULT_KEYBOARD_BINDINGS: KeyboardBinding[] = [];\n\n// NOTE: export for testing only; users should use loadConfig() and initConfig() instead\nexport const DEFAULT_CONFIG: Config = {\n port: 2346,\n host: '127.0.0.1',\n shell:\n process.platform === 'win32'\n ? (process.env.COMSPEC ?? 'cmd.exe')\n : (process.env.SHELL ?? '/bin/bash'),\n term: 'xterm-256color',\n colorTerm: 'truecolor',\n scrollback: 256 * 1024,\n cols: 80,\n rows: 24,\n fontSize: 13,\n fontFamily: \"Menlo, Consolas, 'DejaVu Sans Mono', monospace\",\n cursorStyle: 'bar',\n cursorStyleBlink: true,\n copyOnSelect: true,\n rightClickBehavior: 'default' as RightClickBehavior,\n mouseScrollSpeed: 1,\n logs: false,\n theme: DEFAULT_THEME,\n keyboardBindings: DEFAULT_KEYBOARD_BINDINGS,\n};\n\nfunction bindingKey(b: KeyboardBinding): string {\n const mods = [...(b.mods ?? [])].sort().join('+');\n return `${b.key.toLowerCase()}|${mods}`;\n}\n\nconst VALID_MODS = new Set(['shift', 'ctrl', 'alt', 'meta']);\n\nfunction isValidBinding(b: unknown): b is KeyboardBinding {\n if (!b || typeof b !== 'object') return false;\n const o = b as Record<string, unknown>;\n if (typeof o.key !== 'string' || typeof o.chars !== 'string') return false;\n if (o.mods !== undefined && !Array.isArray(o.mods)) return false;\n return true;\n}\n\nfunction normalizeBinding(b: KeyboardBinding): KeyboardBinding {\n return {\n ...b,\n key: b.key.toLowerCase(),\n mods: (b.mods ?? []).map((m) => m.toLowerCase()).filter((m) => VALID_MODS.has(m)),\n };\n}\n\n/**\n * Merges user-supplied bindings over a set of defaults by `(key, mods)` identity.\n * User entries replace matching defaults; unmatched user entries are appended.\n *\n * @param defaults - Default bindings to merge over.\n * @param user - User-supplied bindings that override defaults.\n * @returns Merged bindings with user entries replacing matching defaults and new user entries appended.\n */\n// NOTE: export for testing only\nexport function mergeKeyboardBindings(\n defaults: KeyboardBinding[],\n user: KeyboardBinding[],\n): KeyboardBinding[] {\n const deduped = [...new Map(user.map((b) => [bindingKey(b), b])).values()];\n const overrides = new Map(deduped.map((b) => [bindingKey(b), b]));\n const merged = defaults.map((d) => overrides.get(bindingKey(d)) ?? d);\n const defaultKeys = new Set(defaults.map(bindingKey));\n for (const b of deduped) {\n if (!defaultKeys.has(bindingKey(b))) merged.push(b);\n }\n return merged;\n}\n\n/**\n * Load config from `~/.config/webtty/config.json`, merged over `DEFAULT_CONFIG`.\n *\n * On first run (file absent), writes the default port/host stub and returns defaults.\n * Unknown keys and keys with wrong types are silently ignored.\n * Throws if the file exists but cannot be read or contains invalid JSON.\n *\n * @returns The resolved {@link Config}, with all missing keys filled from {@link DEFAULT_CONFIG}.\n */\nexport function loadConfig(): Config {\n if (!fs.existsSync(getConfigPath())) {\n try {\n initConfig();\n } catch (err) {\n console.warn(\n `webtty: failed to write default config to ${getConfigPath()}: ${(err as Error).message}`,\n );\n return { ...DEFAULT_CONFIG };\n }\n }\n\n let raw: string;\n try {\n raw = fs.readFileSync(getConfigPath(), 'utf8');\n } catch (err) {\n throw new Error(\n `webtty: failed to read config at ${getConfigPath()}: ${(err as Error).message}`,\n );\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new Error(`webtty: invalid JSON in config file ${getConfigPath()}`);\n }\n\n const p = parsed as Partial<Config>;\n return {\n ...DEFAULT_CONFIG,\n ...(typeof p.port === 'number' && { port: p.port }),\n ...(typeof p.host === 'string' && { host: p.host }),\n ...(typeof p.shell === 'string' && { shell: p.shell }),\n ...(typeof p.term === 'string' && { term: p.term }),\n ...(typeof p.colorTerm === 'string' && { colorTerm: p.colorTerm }),\n ...(typeof p.scrollback === 'number' && { scrollback: p.scrollback }),\n ...(typeof p.cols === 'number' && { cols: p.cols }),\n ...(typeof p.rows === 'number' && { rows: p.rows }),\n ...(typeof p.fontSize === 'number' && { fontSize: p.fontSize }),\n ...(typeof p.fontFamily === 'string' && { fontFamily: p.fontFamily }),\n ...(typeof p.cursorStyle === 'string' &&\n (p.cursorStyle === 'block' || p.cursorStyle === 'bar' || p.cursorStyle === 'underline') && {\n cursorStyle: p.cursorStyle,\n }),\n ...(typeof p.cursorStyleBlink === 'boolean' && { cursorStyleBlink: p.cursorStyleBlink }),\n ...(typeof p.copyOnSelect === 'boolean' && { copyOnSelect: p.copyOnSelect }),\n ...(typeof p.rightClickBehavior === 'string' && {\n rightClickBehavior: (p.rightClickBehavior === 'copyPaste'\n ? 'copyPaste'\n : 'default') as RightClickBehavior,\n }),\n ...(typeof p.mouseScrollSpeed === 'number' &&\n p.mouseScrollSpeed > 0 && { mouseScrollSpeed: p.mouseScrollSpeed }),\n ...(typeof p.logs === 'boolean' && { logs: p.logs }),\n ...(p.theme && typeof p.theme === 'object' && { theme: { ...DEFAULT_THEME, ...p.theme } }),\n ...(Array.isArray(p.keyboardBindings) && {\n keyboardBindings: mergeKeyboardBindings(\n DEFAULT_KEYBOARD_BINDINGS,\n p.keyboardBindings.filter(isValidBinding).map(normalizeBinding),\n ),\n }),\n };\n}\n\n/**\n * Write the initial config stub (`port` + `host` only) to `~/.config/webtty/config.json`.\n *\n * Only writes the two keys that are safe to persist as defaults; all other\n * keys are intentionally omitted so future webtty versions can add new fields\n * without the stub going stale.\n */\nexport function initConfig(): void {\n fs.mkdirSync(path.dirname(getConfigPath()), { recursive: true });\n const content = JSON.stringify(\n {\n port: DEFAULT_CONFIG.port,\n host: DEFAULT_CONFIG.host,\n },\n null,\n 2,\n );\n fs.writeFileSync(getConfigPath(), content, 'utf8');\n}\n",
9
- "import type http from 'node:http';\nimport path from 'node:path';\nimport { loadConfig } from '../config';\nimport {\n createSession,\n generateId,\n isValidId,\n lastUsedId,\n sessionRegistry,\n sessionToJson,\n setLastUsedId,\n} from './session';\nimport { serveFile } from './static';\nimport { broadcastToSubscribers, closeSession } from './websocket';\n\nconst MAX_BODY = 64 * 1024;\n\nfunction decodeId(raw: string): string | null {\n try {\n return decodeURIComponent(raw);\n } catch {\n return null;\n }\n}\n\n/**\n * Reads and JSON-parses the request body (max 64 KB).\n *\n * @param req - The incoming HTTP request.\n * @returns A promise resolving to the parsed JSON object, or an empty object if body is empty.\n * @throws {Error} with `status: 413` if body exceeds 64 KB.\n * @throws {Error} if the body contains invalid JSON.\n */\nexport function readJson(req: http.IncomingMessage): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let body = '';\n req.on('data', (chunk) => {\n body += chunk;\n if (body.length > MAX_BODY) {\n req.destroy();\n reject(Object.assign(new Error('payload too large'), { status: 413 }));\n }\n });\n req.on('end', () => {\n try {\n resolve(body ? JSON.parse(body) : {});\n } catch {\n reject(new Error('invalid JSON'));\n }\n });\n req.on('error', reject);\n });\n}\n\n/**\n * Main HTTP request handler for the webtty server.\n * Dispatches all REST API routes and serves static client assets.\n *\n * @param req - The incoming HTTP request.\n * @param res - The HTTP response object.\n * @param distPath - Path to the server-side dist directory.\n * @param wasmPath - Path to the ghostty-vt.wasm file.\n * @param clientDistPath - Path to the client dist directory.\n * @param onStop - Callback invoked when `POST /api/server/stop` is received.\n */\nexport async function handleRequest(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n distPath: string,\n wasmPath: string,\n clientDistPath: string,\n onStop: () => void,\n): Promise<void> {\n const url = new URL(req.url ?? '/', `http://${req.headers.host ?? '127.0.0.1'}`);\n const pathname = url.pathname;\n\n if (req.method === 'POST' && pathname === '/api/server/stop') {\n res.writeHead(200, { 'Content-Type': 'text/plain' });\n res.end('stopping');\n onStop();\n return;\n }\n\n if (req.method === 'GET' && pathname === '/api/config') {\n const config = loadConfig();\n // Whitelist client-safe keys — avoid exposing server-side config (shell, host, logs, etc.)\n const clientConfig = {\n cols: config.cols,\n rows: config.rows,\n fontSize: config.fontSize,\n fontFamily: config.fontFamily,\n cursorStyle: config.cursorStyle,\n cursorStyleBlink: config.cursorStyleBlink,\n scrollback: config.scrollback,\n theme: config.theme,\n copyOnSelect: config.copyOnSelect,\n rightClickBehavior: config.rightClickBehavior,\n mouseScrollSpeed: config.mouseScrollSpeed,\n keyboardBindings: config.keyboardBindings,\n };\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(clientConfig));\n return;\n }\n\n if (pathname === '/api/sessions') {\n if (req.method === 'GET') {\n const list = [...sessionRegistry.values()].map(sessionToJson);\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(list));\n return;\n }\n\n if (req.method === 'POST') {\n let body: { id?: string };\n try {\n body = (await readJson(req)) as { id?: string };\n } catch (err) {\n const status = (err as { status?: number }).status === 413 ? 413 : 400;\n res.writeHead(status);\n res.end(status === 413 ? 'Payload Too Large' : 'invalid JSON');\n return;\n }\n\n const id = body.id ?? generateId();\n if (!isValidId(id)) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: `invalid id: ${id}` }));\n return;\n }\n if (sessionRegistry.has(id)) {\n res.writeHead(409, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: `session already exists: ${id}` }));\n return;\n }\n const session = createSession(id);\n res.writeHead(201, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(sessionToJson(session)));\n return;\n }\n }\n\n const sessionMatch = pathname.match(/^\\/api\\/sessions\\/([^/]+)$/);\n if (sessionMatch) {\n const id = decodeId(sessionMatch[1]);\n if (!id) {\n res.writeHead(400);\n res.end('Bad Request');\n return;\n }\n\n if (req.method === 'GET') {\n const session = sessionRegistry.get(id);\n if (!session) {\n res.writeHead(404);\n res.end('Not Found');\n return;\n }\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(sessionToJson(session)));\n return;\n }\n\n if (req.method === 'PATCH') {\n let body: { id?: string };\n try {\n body = (await readJson(req)) as { id?: string };\n } catch (err) {\n const status = (err as { status?: number }).status === 413 ? 413 : 400;\n res.writeHead(status);\n res.end(status === 413 ? 'Payload Too Large' : 'invalid JSON');\n return;\n }\n\n const session = sessionRegistry.get(id);\n if (!session) {\n res.writeHead(404);\n res.end('Not Found');\n return;\n }\n\n const newId = body.id;\n if (!newId) {\n res.writeHead(400);\n res.end('missing id');\n return;\n }\n if (!isValidId(newId)) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: `invalid id: ${newId}` }));\n return;\n }\n if (sessionRegistry.has(newId)) {\n res.writeHead(409, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: `session already exists: ${newId}` }));\n return;\n }\n sessionRegistry.delete(id);\n session.id = newId;\n sessionRegistry.set(newId, session);\n if (lastUsedId === id) setLastUsedId(newId);\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(sessionToJson(session)));\n return;\n }\n\n if (req.method === 'DELETE') {\n const session = sessionRegistry.get(id);\n if (!session) {\n res.writeHead(404);\n res.end('Not Found');\n return;\n }\n sessionRegistry.delete(id);\n if (lastUsedId === id) setLastUsedId(null);\n closeSession(session);\n res.writeHead(204, { 'X-Sessions-Remaining': String(sessionRegistry.size) });\n res.end();\n return;\n }\n }\n\n if (req.method === 'GET' && (pathname === '/' || pathname === '/index.html')) {\n let targetId = lastUsedId ?? null;\n if (!targetId || !sessionRegistry.has(targetId)) {\n if (!sessionRegistry.has('main')) createSession('main');\n targetId = 'main';\n }\n res.writeHead(302, { Location: `/s/${targetId}` });\n res.end();\n return;\n }\n\n const clientMatch = pathname.match(/^\\/s\\/([^/]+)$/);\n if (req.method === 'GET' && clientMatch) {\n const id = decodeId(clientMatch[1]);\n if (!id || !isValidId(id) || !sessionRegistry.has(id)) {\n res.writeHead(404);\n res.end('Not Found');\n return;\n }\n const clientHtml = path.resolve(clientDistPath, 'client.html');\n serveFile(clientHtml, res);\n return;\n }\n\n const publishMatch = pathname.match(/^\\/s\\/([^/]+)\\/publish$/);\n if (req.method === 'POST' && publishMatch) {\n const id = decodeId(publishMatch[1]);\n if (!id) {\n res.writeHead(400);\n res.end('Bad Request');\n return;\n }\n if (!(req.headers['content-type'] ?? '').startsWith('application/json')) {\n res.writeHead(400);\n res.end('Bad Request');\n return;\n }\n const session = sessionRegistry.get(id);\n if (!session) {\n res.writeHead(404);\n res.end('Not Found');\n return;\n }\n if (session.pty === null) {\n res.writeHead(409);\n res.end('PTY not running');\n return;\n }\n let buf = '';\n req.on('data', (chunk: Buffer) => {\n buf += chunk.toString('utf8');\n const lines = buf.split('\\n');\n buf = lines.pop() ?? '';\n if (buf.length > MAX_BODY) buf = '';\n for (const line of lines) {\n const trimmed = line.replace(/\\r$/, '');\n if (!trimmed) continue;\n try {\n JSON.parse(trimmed);\n broadcastToSubscribers(session, trimmed);\n } catch {\n // skip invalid JSON lines\n }\n }\n });\n req.on('end', () => {\n const trimmed = buf.replace(/\\r$/, '');\n if (trimmed) {\n try {\n JSON.parse(trimmed);\n broadcastToSubscribers(session, trimmed);\n } catch {\n // skip invalid JSON\n }\n }\n res.writeHead(204);\n res.end();\n });\n req.on('error', () => {\n res.writeHead(500);\n res.end();\n });\n return;\n }\n\n const pidMatch = pathname.match(/^\\/p\\/(\\d+)$/);\n if (req.method === 'GET' && pidMatch) {\n const pid = parseInt(pidMatch[1], 10);\n if (!Number.isFinite(pid) || pid <= 0) {\n res.writeHead(404);\n res.end('Not Found');\n return;\n }\n const session = [...sessionRegistry.values()].find((s) => s.pty?.pid === pid);\n if (!session) {\n res.writeHead(404);\n res.end('Not Found');\n return;\n }\n res.writeHead(302, { Location: `/s/${session.id}` });\n res.end();\n return;\n }\n\n if (pathname.startsWith('/dist/')) {\n const relativePath = pathname.slice(6);\n const ownFile = path.resolve(clientDistPath, relativePath);\n if (ownFile.startsWith(clientDistPath + path.sep)) {\n const fs = await import('node:fs');\n if (fs.existsSync(ownFile)) {\n serveFile(ownFile, res);\n return;\n }\n }\n const filePath = path.resolve(distPath, relativePath);\n if (\n !filePath.startsWith(path.resolve(distPath) + path.sep) &&\n filePath !== path.resolve(distPath)\n ) {\n res.writeHead(404);\n res.end('Not Found');\n return;\n }\n serveFile(filePath, res);\n return;\n }\n\n if (pathname === '/ghostty-vt.wasm') {\n serveFile(wasmPath, res);\n return;\n }\n\n res.writeHead(404);\n res.end('Not Found');\n}\n",
10
- "import type { WebSocket as WS } from 'ws';\nimport type { PtyProcess } from '../pty';\n\n/** A running (or recently created) terminal session. */\nexport interface Session {\n /** Unique session identifier. */\n id: string;\n /** Unix timestamp (ms) when the session was created. */\n createdAt: number;\n /** The underlying PTY process, or `null` if no shell has been spawned yet. */\n pty: PtyProcess | null;\n /** All currently connected WebSocket clients for this session. */\n clients: Set<WS>;\n /** All currently connected event subscribers for this session. */\n subscribers: Set<WS>;\n /** Accumulated PTY output retained for replay when a new client joins. */\n scrollback: string;\n}\n\n/** All active sessions, keyed by session ID. */\nexport const sessionRegistry = new Map<string, Session>();\n\n/** ID of the most recently opened session, used for `GET /` redirect. */\nexport let lastUsedId: string | null = null;\n\n/** Updates {@link lastUsedId}. */\nexport function setLastUsedId(id: string | null): void {\n lastUsedId = id;\n}\n\nconst ID_RE = /^[a-z0-9\\-_.]{1,64}$/;\n\n/**\n * Returns `true` if `id` is a valid session identifier (lowercase alphanumeric + `-_.`, max 64 chars).\n *\n * @param id - The session ID to validate.\n * @returns `true` if the ID matches the valid format, `false` otherwise.\n */\nexport function isValidId(id: string): boolean {\n return ID_RE.test(id);\n}\n\n/**\n * Generates a random 8-character hex session ID.\n *\n * @returns A random session ID.\n */\nexport function generateId(): string {\n return Math.floor(Math.random() * 0xffffffff)\n .toString(16)\n .padStart(8, '0');\n}\n\n/**\n * Creates a new session, registers it in {@link sessionRegistry}, and returns it.\n *\n * @param id - The session ID.\n * @returns The newly created {@link Session}.\n */\nexport function createSession(id: string): Session {\n const session: Session = {\n id,\n createdAt: Date.now(),\n pty: null,\n clients: new Set(),\n subscribers: new Set(),\n scrollback: '',\n };\n sessionRegistry.set(id, session);\n return session;\n}\n\n/**\n * Returns a plain JSON-safe representation of a session for API responses.\n *\n * @param s - The session to serialize.\n * @returns A JSON-safe object with session ID, creation timestamp, connection status, and PTY PID (or `null` if no PTY has been spawned yet).\n */\nexport function sessionToJson(s: Session) {\n return {\n id: s.id,\n createdAt: s.createdAt,\n connected: s.clients.size > 0,\n pid: s.pty?.pid ?? null,\n };\n}\n",
5
+ "import type { PtyProcess } from './types';\n\n/**\n * Spawns a PTY-backed shell using Bun's native `Bun.spawn` terminal API.\n *\n * @param shell - Shell executable path (e.g., `/bin/bash`).\n * @param cols - Terminal width in columns.\n * @param rows - Terminal height in rows.\n * @param term - `$TERM` environment variable (e.g., `xterm-256color`).\n * @param colorTerm - `$COLORTERM` environment variable (e.g., `truecolor`).\n * @param cwd - Working directory for the shell.\n * @returns A {@link PtyProcess} handle for reading/writing and managing the PTY.\n */\nexport function spawn(\n shell: string,\n cols: number,\n rows: number,\n term: string,\n colorTerm: string,\n cwd: string,\n env: Record<string, string>,\n): PtyProcess {\n let onDataCb: ((data: string) => void) | undefined;\n let onExitCb: ((e: { exitCode: number }) => void) | undefined;\n\n const proc = Bun.spawn([shell], {\n terminal: {\n cols,\n rows,\n data(_term: unknown, data: Uint8Array) {\n onDataCb?.(Buffer.from(data).toString('utf8'));\n },\n },\n cwd,\n env: { ...process.env, ...env, TERM: term, COLORTERM: colorTerm },\n });\n\n proc.exited.then((exitCode) => {\n onExitCb?.({ exitCode: exitCode ?? 0 });\n });\n\n return {\n pid: proc.pid,\n onData(cb) {\n onDataCb = cb;\n },\n onExit(cb) {\n onExitCb = cb;\n },\n write(data) {\n proc.terminal?.write(data);\n },\n resize(cols, rows) {\n proc.terminal?.resize(cols, rows);\n },\n kill() {\n proc.kill();\n },\n };\n}\n",
6
+ "import nodePty from '@lydell/node-pty';\nimport type { PtyProcess } from './types';\n\n/**\n * Spawns a PTY-backed shell using the `@lydell/node-pty` native addon.\n *\n * @param shell - Shell executable path (e.g., `/bin/bash`).\n * @param cols - Terminal width in columns.\n * @param rows - Terminal height in rows.\n * @param term - `$TERM` environment variable (e.g., `xterm-256color`).\n * @param colorTerm - `$COLORTERM` environment variable (e.g., `truecolor`).\n * @param cwd - Working directory for the shell.\n * @returns A {@link PtyProcess} handle for reading/writing and managing the PTY.\n */\nexport function spawn(\n shell: string,\n cols: number,\n rows: number,\n term: string,\n colorTerm: string,\n cwd: string,\n env: Record<string, string>,\n): PtyProcess {\n // On Windows, cmd.exe may have an AutoRun registry key that launches shell\n // enhancers (e.g. clink). These take over the ConPTY pipe and cause\n // ERR_SOCKET_CLOSED on the first write. Pass /d to disable AutoRun.\n const shellArgs = process.platform === 'win32' && /cmd\\.exe$/i.test(shell) ? ['/d'] : [];\n\n const ptyProc = nodePty.spawn(shell, shellArgs, {\n name: term,\n cols,\n rows,\n cwd,\n env: { ...process.env, ...env, TERM: term, COLORTERM: colorTerm },\n });\n\n return {\n pid: ptyProc.pid,\n onData(cb) {\n ptyProc.onData(cb);\n },\n onExit(cb) {\n ptyProc.onExit(cb);\n },\n write(data) {\n ptyProc.write(data);\n },\n resize(cols, rows) {\n ptyProc.resize(cols, rows);\n },\n kill() {\n ptyProc.kill();\n },\n };\n}\n",
7
+ "import fs from 'node:fs';\nimport http from 'node:http';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { loadConfig } from '../config';\nimport { handleRequest } from './routes';\nimport { findGhosttyWeb } from './static';\nimport { closeAllSessions, createWebSocketServer, setLastSessionClosedHandler } from './websocket';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nconst config = loadConfig();\nconst HTTP_PORT = Number(process.env.PORT) || config.port;\n// 'localhost' resolves to ::1 (IPv6) on modern macOS/Node; bind to 127.0.0.1 instead\n// but keep 'localhost' as the display host so browser URLs use it as intended.\nconst HTTP_HOST_DISPLAY = config.host;\nconst HTTP_HOST = config.host === 'localhost' ? '127.0.0.1' : config.host;\n\nconst { distPath, wasmPath } = findGhosttyWeb();\nconst projectRoot = path.resolve(__dirname, '..', '..');\nconst builtDistPath = path.join(projectRoot, 'dist');\nconst srcClientPath = path.join(projectRoot, 'src', 'client');\nconst clientDistPath = fs.existsSync(path.join(builtDistPath, 'client.html'))\n ? builtDistPath\n : srcClientPath;\n\nfunction shutdown() {\n closeAllSessions();\n wss.close();\n const exit = () => process.exit(0);\n const shutdownTimeout = setTimeout(exit, 1000);\n httpServer.close(() => {\n clearTimeout(shutdownTimeout);\n exit();\n });\n}\n\nprocess.on('uncaughtException', (err) => {\n console.error('[webtty] uncaughtException:', err);\n});\nprocess.on('unhandledRejection', (reason) => {\n console.error('[webtty] unhandledRejection:', reason);\n});\n\nconst httpServer = http.createServer((req, res) => {\n handleRequest(req, res, distPath, wasmPath, clientDistPath, shutdown).catch((err) => {\n console.error('[webtty] unhandled route error:', err);\n if (!res.headersSent) {\n res.writeHead(500);\n res.end('Internal Server Error');\n }\n });\n});\n\nconst wss = createWebSocketServer(httpServer);\nsetLastSessionClosedHandler(shutdown);\n\nprocess.on('SIGINT', () => {\n console.log('\\n\\nShutting down...');\n shutdown();\n});\n\nhttpServer.listen(HTTP_PORT, HTTP_HOST, () => {\n console.log(`listening on http://${HTTP_HOST_DISPLAY}:${HTTP_PORT}`);\n});\n",
8
+ "import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\n/** A single keyboard binding: intercepts `key`+`mods` and sends `chars` to the PTY. */\nexport interface KeyboardBinding {\n /** Key name matched case-insensitively against `KeyboardEvent.key` (e.g. `\"enter\"`, `\"arrowup\"`, `\"a\"`). */\n key: string;\n /** Modifier keys that must be active — and no others — for this binding to match. Accepted values: `\"shift\"`, `\"ctrl\"`, `\"alt\"`, `\"meta\"`. Order irrelevant. Unknown values are filtered out at config load time. */\n mods?: string[];\n /** Byte sequence sent verbatim to the PTY. Standard JSON escapes apply (`\\r`, `\\uXXXX`, etc.). */\n chars: string;\n}\n\n/** Terminal color palette. All keys are optional; omitted keys fall back to the Campbell defaults. */\nexport interface Theme {\n /** Terminal background. */\n background?: string;\n /** Default text color. */\n foreground?: string;\n /** Cursor color. */\n cursor?: string;\n /** Selection highlight. */\n selection?: string;\n /** ANSI 0. */\n black?: string;\n /** ANSI 1. */\n red?: string;\n /** ANSI 2. */\n green?: string;\n /** ANSI 3. */\n yellow?: string;\n /** ANSI 4. */\n blue?: string;\n /** ANSI 5. */\n purple?: string;\n /** ANSI 6. */\n cyan?: string;\n /** ANSI 7. */\n white?: string;\n /** ANSI 8. */\n brightBlack?: string;\n /** ANSI 9. */\n brightRed?: string;\n /** ANSI 10. */\n brightGreen?: string;\n /** ANSI 11. */\n brightYellow?: string;\n /** ANSI 12. */\n brightBlue?: string;\n /** ANSI 13. */\n brightPurple?: string;\n /** ANSI 14. */\n brightCyan?: string;\n /** ANSI 15. */\n brightWhite?: string;\n}\n\n/** Right-click behavior: `\"copyPaste\"` copies selection + clears it if selection exists, otherwise native menu; `\"default\"` always shows native context menu. */\nexport type RightClickBehavior = 'default' | 'copyPaste';\n\n/** Full webtty configuration. All keys are optional in the config file; missing keys fall back to {@link DEFAULT_CONFIG}. */\nexport interface Config {\n /** HTTP listen port; env `PORT` takes precedence. */\n port: number;\n /** Bind address; use `\"0.0.0.0\"` for remote access. */\n host: string;\n /** Shell for new sessions. */\n shell: string;\n /** `$TERM` env var passed to the shell. Fixed to `xterm-256color` — the PTY child talks to webtty, not to the parent terminal. */\n term: string;\n /** `$COLORTERM` env var passed to the shell. */\n colorTerm: string;\n /** PTY history buffer in bytes; used for server-side replay on reload/reconnect. */\n scrollback: number;\n /** Initial terminal width in columns. */\n cols: number;\n /** Initial terminal height in rows. */\n rows: number;\n /** Font size in px. */\n fontSize: number;\n /** CSS font-family stack. */\n fontFamily: string;\n /** Default cursor shape. Apps override at runtime via DECSCUSR — this is the startup default only. */\n cursorStyle: 'block' | 'bar' | 'underline';\n /** Default blink state. Apps override at runtime via DECSCUSR — this is the startup default only. */\n cursorStyleBlink: boolean;\n /** Auto-copy selection to clipboard on mouseup (kitty / Windows Terminal style). */\n copyOnSelect: boolean;\n /** `\"copyPaste\"` copies selection + clears it on right-click if selection exists; `\"default\"` always shows native context menu. */\n rightClickBehavior: RightClickBehavior;\n /** Mouse wheel scroll speed multiplier for apps with mouse tracking. `1` = one SGR per tick. Values `< 1` reduce rate; values `> 1` send multiple SGRs per tick. Must be `> 0`. */\n mouseScrollSpeed: number;\n /** Write server stdout/stderr to `~/.config/webtty/server.log`. Appends on each start. */\n logs: boolean;\n /** Terminal color palette. */\n theme: Theme;\n /** Custom key-to-sequence bindings. Merged with built-in defaults by `(key, mods)` identity. */\n keyboardBindings: KeyboardBinding[];\n /** Additional environment variables injected into PTY shells and execute commands. Merges over `process.env`; `TERM` and `COLORTERM` always take precedence for PTY spawns. */\n env: Record<string, string>;\n}\n\n/** Returns the webtty config directory: `~/.config/webtty`. */\nexport function configDir(): string {\n // os.homedir() is the authoritative home directory. process.env.HOME is used\n // as an override in tests (e.g. to isolate config state), but only when it is\n // an absolute path — guarding against accidental env pollution such as the\n // string \"undefined\" being assigned when HOME was originally unset on Windows.\n const home =\n process.env.HOME && path.isAbsolute(process.env.HOME) ? process.env.HOME : os.homedir();\n return path.join(home, '.config', 'webtty');\n}\n\nfunction getConfigPath(): string {\n return path.join(configDir(), 'config.json');\n}\n\n// NOTE: export for testing only; users should use loadConfig() and initConfig() instead\nexport const DEFAULT_THEME: Theme = {\n background: '#000000',\n foreground: '#CCCCCC',\n cursor: '#FFFFFF',\n selection: '#FFFFFF',\n black: '#0C0C0C',\n red: '#C50F1F',\n green: '#13A10E',\n yellow: '#C19C00',\n blue: '#0037DA',\n purple: '#881798',\n cyan: '#3A96DD',\n white: '#CCCCCC',\n brightBlack: '#767676',\n brightRed: '#E74856',\n brightGreen: '#16C60C',\n brightYellow: '#F9F1A5',\n brightBlue: '#3B78FF',\n brightPurple: '#B4009E',\n brightCyan: '#61D6D6',\n brightWhite: '#F2F2F2',\n};\n\n// NOTE: export for testing only; users should use loadConfig() and initConfig() instead\nexport const DEFAULT_KEYBOARD_BINDINGS: KeyboardBinding[] = [];\n\n// NOTE: export for testing only; users should use loadConfig() and initConfig() instead\nexport const DEFAULT_CONFIG: Config = {\n port: 2346,\n host: '127.0.0.1',\n shell:\n process.platform === 'win32'\n ? (process.env.COMSPEC ?? 'cmd.exe')\n : (process.env.SHELL ?? '/bin/bash'),\n term: 'xterm-256color',\n colorTerm: 'truecolor',\n scrollback: 256 * 1024,\n cols: 80,\n rows: 24,\n fontSize: 13,\n fontFamily: \"Menlo, Consolas, 'DejaVu Sans Mono', monospace\",\n cursorStyle: 'bar',\n cursorStyleBlink: true,\n copyOnSelect: true,\n rightClickBehavior: 'default' as RightClickBehavior,\n mouseScrollSpeed: 1,\n logs: false,\n theme: DEFAULT_THEME,\n keyboardBindings: DEFAULT_KEYBOARD_BINDINGS,\n env: {},\n};\n\nfunction bindingKey(b: KeyboardBinding): string {\n const mods = [...(b.mods ?? [])].sort().join('+');\n return `${b.key.toLowerCase()}|${mods}`;\n}\n\nconst VALID_MODS = new Set(['shift', 'ctrl', 'alt', 'meta']);\n\nfunction isValidBinding(b: unknown): b is KeyboardBinding {\n if (!b || typeof b !== 'object') return false;\n const o = b as Record<string, unknown>;\n if (typeof o.key !== 'string' || typeof o.chars !== 'string') return false;\n if (o.mods !== undefined && !Array.isArray(o.mods)) return false;\n return true;\n}\n\nfunction normalizeBinding(b: KeyboardBinding): KeyboardBinding {\n return {\n ...b,\n key: b.key.toLowerCase(),\n mods: (b.mods ?? []).map((m) => m.toLowerCase()).filter((m) => VALID_MODS.has(m)),\n };\n}\n\n/**\n * Merges user-supplied bindings over a set of defaults by `(key, mods)` identity.\n * User entries replace matching defaults; unmatched user entries are appended.\n *\n * @param defaults - Default bindings to merge over.\n * @param user - User-supplied bindings that override defaults.\n * @returns Merged bindings with user entries replacing matching defaults and new user entries appended.\n */\n// NOTE: export for testing only\nexport function mergeKeyboardBindings(\n defaults: KeyboardBinding[],\n user: KeyboardBinding[],\n): KeyboardBinding[] {\n const deduped = [...new Map(user.map((b) => [bindingKey(b), b])).values()];\n const overrides = new Map(deduped.map((b) => [bindingKey(b), b]));\n const merged = defaults.map((d) => overrides.get(bindingKey(d)) ?? d);\n const defaultKeys = new Set(defaults.map(bindingKey));\n for (const b of deduped) {\n if (!defaultKeys.has(bindingKey(b))) merged.push(b);\n }\n return merged;\n}\n\n/**\n * Load config from `~/.config/webtty/config.json`, merged over `DEFAULT_CONFIG`.\n *\n * On first run (file absent), writes the default port/host stub and returns defaults.\n * Unknown keys and keys with wrong types are silently ignored.\n * Throws if the file exists but cannot be read or contains invalid JSON.\n *\n * @returns The resolved {@link Config}, with all missing keys filled from {@link DEFAULT_CONFIG}.\n */\nexport function loadConfig(): Config {\n if (!fs.existsSync(getConfigPath())) {\n try {\n initConfig();\n } catch (err) {\n console.warn(\n `webtty: failed to write default config to ${getConfigPath()}: ${(err as Error).message}`,\n );\n return { ...DEFAULT_CONFIG };\n }\n }\n\n let raw: string;\n try {\n raw = fs.readFileSync(getConfigPath(), 'utf8');\n } catch (err) {\n throw new Error(\n `webtty: failed to read config at ${getConfigPath()}: ${(err as Error).message}`,\n );\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new Error(`webtty: invalid JSON in config file ${getConfigPath()}`);\n }\n\n const p = parsed as Partial<Config>;\n return {\n ...DEFAULT_CONFIG,\n ...(typeof p.port === 'number' && { port: p.port }),\n ...(typeof p.host === 'string' && { host: p.host }),\n ...(typeof p.shell === 'string' && { shell: p.shell }),\n ...(typeof p.term === 'string' && { term: p.term }),\n ...(typeof p.colorTerm === 'string' && { colorTerm: p.colorTerm }),\n ...(typeof p.scrollback === 'number' && { scrollback: p.scrollback }),\n ...(typeof p.cols === 'number' && { cols: p.cols }),\n ...(typeof p.rows === 'number' && { rows: p.rows }),\n ...(typeof p.fontSize === 'number' && { fontSize: p.fontSize }),\n ...(typeof p.fontFamily === 'string' && { fontFamily: p.fontFamily }),\n ...(typeof p.cursorStyle === 'string' &&\n (p.cursorStyle === 'block' || p.cursorStyle === 'bar' || p.cursorStyle === 'underline') && {\n cursorStyle: p.cursorStyle,\n }),\n ...(typeof p.cursorStyleBlink === 'boolean' && { cursorStyleBlink: p.cursorStyleBlink }),\n ...(typeof p.copyOnSelect === 'boolean' && { copyOnSelect: p.copyOnSelect }),\n ...(typeof p.rightClickBehavior === 'string' && {\n rightClickBehavior: (p.rightClickBehavior === 'copyPaste'\n ? 'copyPaste'\n : 'default') as RightClickBehavior,\n }),\n ...(typeof p.mouseScrollSpeed === 'number' &&\n p.mouseScrollSpeed > 0 && { mouseScrollSpeed: p.mouseScrollSpeed }),\n ...(typeof p.logs === 'boolean' && { logs: p.logs }),\n ...(p.theme && typeof p.theme === 'object' && { theme: { ...DEFAULT_THEME, ...p.theme } }),\n ...(Array.isArray(p.keyboardBindings) && {\n keyboardBindings: mergeKeyboardBindings(\n DEFAULT_KEYBOARD_BINDINGS,\n p.keyboardBindings.filter(isValidBinding).map(normalizeBinding),\n ),\n }),\n ...(p.env &&\n typeof p.env === 'object' &&\n !Array.isArray(p.env) && {\n env: Object.fromEntries(\n Object.entries(p.env as Record<string, unknown>).filter(\n ([k, v]) => typeof k === 'string' && typeof v === 'string',\n ) as [string, string][],\n ),\n }),\n };\n}\n\n/**\n * Write the initial config stub (`port` + `host` only) to `~/.config/webtty/config.json`.\n *\n * Only writes the two keys that are safe to persist as defaults; all other\n * keys are intentionally omitted so future webtty versions can add new fields\n * without the stub going stale.\n */\nexport function initConfig(): void {\n fs.mkdirSync(path.dirname(getConfigPath()), { recursive: true });\n const content = JSON.stringify(\n {\n port: DEFAULT_CONFIG.port,\n host: DEFAULT_CONFIG.host,\n },\n null,\n 2,\n );\n fs.writeFileSync(getConfigPath(), content, 'utf8');\n}\n",
9
+ "import { spawn } from 'node:child_process';\nimport fs from 'node:fs';\nimport type http from 'node:http';\nimport { homedir } from 'node:os';\nimport path from 'node:path';\nimport { loadConfig } from '../config';\nimport {\n createSession,\n generateId,\n isValidId,\n lastUsedId,\n sessionRegistry,\n sessionToJson,\n setLastUsedId,\n} from './session';\nimport { serveFile } from './static';\nimport { broadcastToSubscribers, closeSession } from './websocket';\n\nconst MAX_BODY = 64 * 1024;\n\nconst CORS_HEADERS = {\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, PATCH, DELETE, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type',\n};\n\nfunction setCors(res: http.ServerResponse): void {\n for (const [k, v] of Object.entries(CORS_HEADERS)) res.setHeader(k, v);\n}\n\nfunction decodeId(raw: string): string | null {\n try {\n return decodeURIComponent(raw);\n } catch {\n return null;\n }\n}\n\n/**\n * Reads and JSON-parses the request body (max 64 KB).\n *\n * @param req - The incoming HTTP request.\n * @returns A promise resolving to the parsed JSON object, or an empty object if body is empty.\n * @throws {Error} with `status: 413` if body exceeds 64 KB.\n * @throws {Error} if the body contains invalid JSON.\n */\nexport function readJson(req: http.IncomingMessage): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let body = '';\n req.on('data', (chunk) => {\n body += chunk;\n if (body.length > MAX_BODY) {\n req.destroy();\n reject(Object.assign(new Error('payload too large'), { status: 413 }));\n }\n });\n req.on('end', () => {\n try {\n resolve(body ? JSON.parse(body) : {});\n } catch {\n reject(new Error('invalid JSON'));\n }\n });\n req.on('error', reject);\n });\n}\n\n/**\n * Main HTTP request handler for the webtty server.\n * Dispatches all REST API routes and serves static client assets.\n *\n * @param req - The incoming HTTP request.\n * @param res - The HTTP response object.\n * @param distPath - Path to the server-side dist directory.\n * @param wasmPath - Path to the ghostty-vt.wasm file.\n * @param clientDistPath - Path to the client dist directory.\n * @param onStop - Callback invoked when `POST /api/server/stop` is received.\n */\nexport async function handleRequest(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n distPath: string,\n wasmPath: string,\n clientDistPath: string,\n onStop: () => void,\n): Promise<void> {\n const url = new URL(req.url ?? '/', `http://${req.headers.host ?? '127.0.0.1'}`);\n const pathname = url.pathname;\n\n setCors(res);\n\n if (req.method === 'OPTIONS') {\n res.writeHead(204);\n res.end();\n return;\n }\n\n if (req.method === 'POST' && pathname === '/api/server/stop') {\n res.writeHead(200, { 'Content-Type': 'text/plain' });\n res.end('stopping');\n onStop();\n return;\n }\n\n if (req.method === 'GET' && pathname === '/api/config') {\n const config = loadConfig();\n // Whitelist client-safe keys — avoid exposing server-side config (shell, host, logs, etc.)\n const clientConfig = {\n cols: config.cols,\n rows: config.rows,\n fontSize: config.fontSize,\n fontFamily: config.fontFamily,\n cursorStyle: config.cursorStyle,\n cursorStyleBlink: config.cursorStyleBlink,\n scrollback: config.scrollback,\n theme: config.theme,\n copyOnSelect: config.copyOnSelect,\n rightClickBehavior: config.rightClickBehavior,\n mouseScrollSpeed: config.mouseScrollSpeed,\n keyboardBindings: config.keyboardBindings,\n };\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(clientConfig));\n return;\n }\n\n if (pathname === '/api/sessions') {\n if (req.method === 'GET') {\n const list = [...sessionRegistry.values()].map(sessionToJson);\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(list));\n return;\n }\n\n if (req.method === 'POST') {\n let body: { id?: string; baseDir?: string };\n try {\n body = (await readJson(req)) as { id?: string; baseDir?: string };\n } catch (err) {\n const status = (err as { status?: number }).status === 413 ? 413 : 400;\n res.writeHead(status);\n res.end(status === 413 ? 'Payload Too Large' : 'invalid JSON');\n return;\n }\n\n const id = body.id ?? generateId();\n if (!isValidId(id)) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: `invalid id: ${id}` }));\n return;\n }\n if (sessionRegistry.has(id)) {\n res.writeHead(409, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: `session already exists: ${id}` }));\n return;\n }\n const baseDir = body.baseDir ?? homedir();\n if (!path.isAbsolute(baseDir)) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'baseDir must be an absolute path' }));\n return;\n }\n if (!fs.existsSync(baseDir)) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: `baseDir does not exist: ${baseDir}` }));\n return;\n }\n if (!fs.statSync(baseDir).isDirectory()) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: `baseDir is not a directory: ${baseDir}` }));\n return;\n }\n const session = createSession(id, baseDir);\n res.writeHead(201, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(sessionToJson(session)));\n return;\n }\n }\n\n const sessionMatch = pathname.match(/^\\/api\\/sessions\\/([^/]+)$/);\n if (sessionMatch) {\n const id = decodeId(sessionMatch[1]);\n if (!id) {\n res.writeHead(400);\n res.end('Bad Request');\n return;\n }\n\n if (req.method === 'GET') {\n const session = sessionRegistry.get(id);\n if (!session) {\n res.writeHead(404);\n res.end('Not Found');\n return;\n }\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(sessionToJson(session)));\n return;\n }\n\n if (req.method === 'PATCH') {\n let body: { id?: string };\n try {\n body = (await readJson(req)) as { id?: string };\n } catch (err) {\n const status = (err as { status?: number }).status === 413 ? 413 : 400;\n res.writeHead(status);\n res.end(status === 413 ? 'Payload Too Large' : 'invalid JSON');\n return;\n }\n\n const session = sessionRegistry.get(id);\n if (!session) {\n res.writeHead(404);\n res.end('Not Found');\n return;\n }\n\n const newId = body.id;\n if (!newId) {\n res.writeHead(400);\n res.end('missing id');\n return;\n }\n if (!isValidId(newId)) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: `invalid id: ${newId}` }));\n return;\n }\n if (sessionRegistry.has(newId)) {\n res.writeHead(409, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: `session already exists: ${newId}` }));\n return;\n }\n sessionRegistry.delete(id);\n session.id = newId;\n sessionRegistry.set(newId, session);\n if (lastUsedId === id) setLastUsedId(newId);\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(sessionToJson(session)));\n return;\n }\n\n if (req.method === 'DELETE') {\n const session = sessionRegistry.get(id);\n if (!session) {\n res.writeHead(404);\n res.end('Not Found');\n return;\n }\n sessionRegistry.delete(id);\n if (lastUsedId === id) setLastUsedId(null);\n closeSession(session);\n res.writeHead(204, { 'X-Sessions-Remaining': String(sessionRegistry.size) });\n res.end();\n return;\n }\n }\n\n if (req.method === 'GET' && (pathname === '/' || pathname === '/index.html')) {\n let targetId = lastUsedId ?? null;\n if (!targetId || !sessionRegistry.has(targetId)) {\n if (!sessionRegistry.has('main')) createSession('main');\n targetId = 'main';\n }\n res.writeHead(302, { Location: `/s/${targetId}` });\n res.end();\n return;\n }\n\n const clientMatch = pathname.match(/^\\/s\\/([^/]+)$/);\n if (req.method === 'GET' && clientMatch) {\n const id = decodeId(clientMatch[1]);\n if (!id || !isValidId(id) || !sessionRegistry.has(id)) {\n res.writeHead(404);\n res.end('Not Found');\n return;\n }\n const clientHtml = path.resolve(clientDistPath, 'client.html');\n serveFile(clientHtml, res);\n return;\n }\n\n const publishMatch = pathname.match(/^\\/s\\/([^/]+)\\/publish$/);\n if (req.method === 'POST' && publishMatch) {\n const id = decodeId(publishMatch[1]);\n if (!id) {\n res.writeHead(400);\n res.end('Bad Request');\n return;\n }\n if (!(req.headers['content-type'] ?? '').startsWith('application/json')) {\n res.writeHead(400);\n res.end('Bad Request');\n return;\n }\n const session = sessionRegistry.get(id);\n if (!session) {\n res.writeHead(404);\n res.end('Not Found');\n return;\n }\n if (session.pty === null) {\n res.writeHead(409);\n res.end('PTY not running');\n return;\n }\n let buf = '';\n req.on('data', (chunk: Buffer) => {\n buf += chunk.toString('utf8');\n const lines = buf.split('\\n');\n buf = lines.pop() ?? '';\n if (buf.length > MAX_BODY) buf = '';\n for (const line of lines) {\n const trimmed = line.replace(/\\r$/, '');\n if (!trimmed) continue;\n try {\n JSON.parse(trimmed);\n broadcastToSubscribers(session, trimmed);\n } catch {\n // skip invalid JSON lines\n }\n }\n });\n req.on('end', () => {\n const trimmed = buf.replace(/\\r$/, '');\n if (trimmed) {\n try {\n JSON.parse(trimmed);\n broadcastToSubscribers(session, trimmed);\n } catch {\n // skip invalid JSON\n }\n }\n res.writeHead(204);\n res.end();\n });\n req.on('error', () => {\n res.writeHead(500);\n res.end();\n });\n return;\n }\n\n const executeMatch = pathname.match(/^\\/s\\/([^/]+)\\/execute$/);\n if (req.method === 'POST' && executeMatch) {\n const id = decodeId(executeMatch[1]);\n if (!id) {\n res.writeHead(400);\n res.end('Bad Request');\n return;\n }\n if (!(req.headers['content-type'] ?? '').startsWith('application/json')) {\n res.writeHead(400);\n res.end('Bad Request');\n return;\n }\n const session = sessionRegistry.get(id);\n if (!session) {\n res.writeHead(404);\n res.end('Not Found');\n return;\n }\n if (session.pty === null) {\n res.writeHead(409);\n res.end('PTY not running');\n return;\n }\n let execBody: unknown;\n try {\n execBody = await readJson(req);\n } catch (err) {\n const status = (err as { status?: number }).status === 413 ? 413 : 400;\n res.writeHead(status);\n res.end(status === 413 ? 'Payload Too Large' : 'invalid JSON');\n return;\n }\n const { cmd, args, stdin } = execBody as { cmd?: unknown; args?: unknown; stdin?: unknown };\n if (typeof cmd !== 'string' || cmd.length === 0) {\n res.writeHead(400);\n res.end('invalid body');\n return;\n }\n if (!Array.isArray(args) || !args.every((a) => typeof a === 'string')) {\n res.writeHead(400);\n res.end('invalid body');\n return;\n }\n let child: ReturnType<typeof spawn>;\n try {\n const execConfig = loadConfig();\n child = spawn(cmd, args as string[], {\n env: { ...process.env, ...execConfig.env },\n cwd: session.baseDir,\n });\n } catch (err) {\n res.writeHead(500);\n res.end(`spawn error: ${String(err)}`);\n return;\n }\n res.writeHead(200, {\n 'Content-Type': 'application/x-ndjson',\n 'Cache-Control': 'no-cache',\n });\n let done = false;\n child.on('error', (err) => {\n if (done) return;\n done = true;\n const msg = err.message;\n const code = (err as NodeJS.ErrnoException).code;\n res.write(`${JSON.stringify({ stream: 'stderr', data: `${msg}\\n` })}\\n`);\n res.write(`${JSON.stringify({ exit: 1, error: msg, ...(code ? { code } : {}) })}\\n`);\n res.end();\n });\n if (typeof stdin === 'string') {\n child.stdin?.write(stdin);\n }\n child.stdin?.end();\n child.stdout?.on('data', (chunk: Buffer) => {\n res.write(`${JSON.stringify({ stream: 'stdout', data: chunk.toString() })}\\n`);\n });\n child.stderr?.on('data', (chunk: Buffer) => {\n res.write(`${JSON.stringify({ stream: 'stderr', data: chunk.toString() })}\\n`);\n });\n child.on('close', (code: number | null) => {\n if (done) return;\n done = true;\n res.write(`${JSON.stringify({ exit: code ?? 1 })}\\n`);\n res.end();\n });\n req.on('close', () => {\n if (!done) child.kill();\n });\n return;\n }\n\n const pidMatch = pathname.match(/^\\/p\\/(\\d+)$/);\n if (req.method === 'GET' && pidMatch) {\n const pid = parseInt(pidMatch[1], 10);\n if (!Number.isFinite(pid) || pid <= 0) {\n res.writeHead(404);\n res.end('Not Found');\n return;\n }\n const session = [...sessionRegistry.values()].find((s) => s.pty?.pid === pid);\n if (!session) {\n res.writeHead(404);\n res.end('Not Found');\n return;\n }\n res.writeHead(302, { Location: `/s/${session.id}` });\n res.end();\n return;\n }\n\n if (pathname.startsWith('/dist/')) {\n const relativePath = pathname.slice(6);\n const ownFile = path.resolve(clientDistPath, relativePath);\n if (ownFile.startsWith(clientDistPath + path.sep)) {\n const fs = await import('node:fs');\n if (fs.existsSync(ownFile)) {\n serveFile(ownFile, res);\n return;\n }\n }\n const filePath = path.resolve(distPath, relativePath);\n if (\n !filePath.startsWith(path.resolve(distPath) + path.sep) &&\n filePath !== path.resolve(distPath)\n ) {\n res.writeHead(404);\n res.end('Not Found');\n return;\n }\n serveFile(filePath, res);\n return;\n }\n\n if (pathname === '/ghostty-vt.wasm') {\n serveFile(wasmPath, res);\n return;\n }\n\n res.writeHead(404);\n res.end('Not Found');\n}\n",
10
+ "import { homedir } from 'node:os';\nimport type { WebSocket as WS } from 'ws';\nimport type { PtyProcess } from '../pty';\n\n/** A running (or recently created) terminal session. */\nexport interface Session {\n /** Unique session identifier. */\n id: string;\n /** Unix timestamp (ms) when the session was created. */\n createdAt: number;\n /** Working directory for the PTY shell. */\n baseDir: string;\n /** The underlying PTY process, or `null` if no shell has been spawned yet. */\n pty: PtyProcess | null;\n /** All currently connected WebSocket clients for this session. */\n clients: Set<WS>;\n /** All currently connected event subscribers for this session. */\n subscribers: Set<WS>;\n /** Accumulated PTY output retained for replay when a new client joins. */\n scrollback: string;\n}\n\n/** All active sessions, keyed by session ID. */\nexport const sessionRegistry = new Map<string, Session>();\n\n/** ID of the most recently opened session, used for `GET /` redirect. */\nexport let lastUsedId: string | null = null;\n\n/** Updates {@link lastUsedId}. */\nexport function setLastUsedId(id: string | null): void {\n lastUsedId = id;\n}\n\nconst ID_RE = /^[a-z0-9\\-_.]{1,64}$/;\n\n/**\n * Returns `true` if `id` is a valid session identifier (lowercase alphanumeric + `-_.`, max 64 chars).\n *\n * @param id - The session ID to validate.\n * @returns `true` if the ID matches the valid format, `false` otherwise.\n */\nexport function isValidId(id: string): boolean {\n return ID_RE.test(id);\n}\n\n/**\n * Generates a random 8-character hex session ID.\n *\n * @returns A random session ID.\n */\nexport function generateId(): string {\n return Math.floor(Math.random() * 0xffffffff)\n .toString(16)\n .padStart(8, '0');\n}\n\n/**\n * Creates a new session, registers it in {@link sessionRegistry}, and returns it.\n *\n * @param id - The session ID.\n * @param baseDir - Working directory for the PTY shell (default: `homedir()`).\n * @returns The newly created {@link Session}.\n */\nexport function createSession(id: string, baseDir = homedir()): Session {\n const session: Session = {\n id,\n createdAt: Date.now(),\n baseDir,\n pty: null,\n clients: new Set(),\n subscribers: new Set(),\n scrollback: '',\n };\n sessionRegistry.set(id, session);\n return session;\n}\n\n/**\n * Returns a plain JSON-safe representation of a session for API responses.\n *\n * @param s - The session to serialize.\n * @returns A JSON-safe object with session ID, creation timestamp, connection status, and PTY PID (or `null` if no PTY has been spawned yet).\n */\nexport function sessionToJson(s: Session) {\n return {\n id: s.id,\n createdAt: s.createdAt,\n baseDir: s.baseDir,\n connected: s.clients.size > 0,\n pid: s.pty?.pid ?? null,\n };\n}\n",
11
11
  "import fs from 'node:fs';\nimport type http from 'node:http';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nconst require = createRequire(import.meta.url);\n\n/** File extension → MIME type map used by {@link serveFile}. */\nexport const MIME_TYPES: Record<string, string> = {\n '.html': 'text/html',\n '.js': 'application/javascript',\n '.mjs': 'application/javascript',\n '.css': 'text/css',\n '.json': 'application/json',\n '.wasm': 'application/wasm',\n '.png': 'image/png',\n '.svg': 'image/svg+xml',\n '.ico': 'image/x-icon',\n};\n\n/**\n * Returns the MIME type for `filePath` based on its extension, defaulting to `application/octet-stream`.\n *\n * @param filePath - The file path to determine the MIME type for.\n * @returns The MIME type string.\n */\nexport function mimeType(filePath: string): string {\n const ext = path.extname(filePath);\n return MIME_TYPES[ext] ?? 'application/octet-stream';\n}\n\n/**\n * Strips the `dist/…` suffix from a ghostty-web main entry path to get the package root.\n *\n * @param mainPath - The main entry path from ghostty-web package.\n * @returns The package root directory path.\n */\nexport function ghosttyWebRootFromMain(mainPath: string): string {\n return mainPath.replace(/[/\\\\]dist[/\\\\].*$/, '');\n}\n\n/**\n * Locates the ghostty-web package, preferring assets bundled in `dist/` (npm install)\n * over `node_modules/` (local dev). Exits the process if the package cannot be found.\n *\n * @returns An object with `distPath` (ghostty-web dist directory) and `wasmPath` (ghostty-vt.wasm file).\n * @throws Exits the process with code 1 if ghostty-web cannot be found.\n */\nexport function findGhosttyWeb(): { distPath: string; wasmPath: string } {\n // Prefer assets bundled into dist/ — present when installed via npx/npm.\n const bundledDist = path.join(__dirname, '..', '..', 'dist');\n const bundledWasm = path.join(bundledDist, 'ghostty-vt.wasm');\n if (fs.existsSync(path.join(bundledDist, 'ghostty-web.js')) && fs.existsSync(bundledWasm)) {\n return { distPath: bundledDist, wasmPath: bundledWasm };\n }\n\n // Fall back to node_modules — present during local development.\n try {\n const ghosttyWebMain = require.resolve('ghostty-web') as string;\n const ghosttyWebRoot = ghosttyWebRootFromMain(ghosttyWebMain);\n const distPath = path.join(ghosttyWebRoot, 'dist');\n const wasmPath = path.join(ghosttyWebRoot, 'ghostty-vt.wasm');\n if (fs.existsSync(path.join(distPath, 'ghostty-web.js')) && fs.existsSync(wasmPath)) {\n return { distPath, wasmPath };\n }\n } catch {\n // fall through\n }\n console.error('Error: Could not find ghostty-web package.');\n process.exit(1);\n}\n\n/**\n * Reads `filePath` from disk and writes it to `res` with the correct Content-Type header.\n * Responds with 404 if the file cannot be read.\n *\n * @param filePath - The file path to serve.\n * @param res - The HTTP response object.\n */\nexport function serveFile(filePath: string, res: http.ServerResponse): void {\n const contentType = mimeType(filePath);\n fs.readFile(filePath, (err, data) => {\n if (err) {\n res.writeHead(404);\n res.end('Not Found');\n return;\n }\n res.writeHead(200, { 'Content-Type': contentType });\n res.end(data);\n });\n}\n",
12
- "import type http from 'node:http';\nimport type { WebSocket as WS } from 'ws';\nimport { WebSocketServer } from 'ws';\nimport { loadConfig } from '../config';\nimport { spawnForSession } from '../pty';\nimport type { Session } from './session';\nimport { sessionRegistry, setLastUsedId } from './session';\n\nconst WS_CLOSE = {\n SERVER_STOPPED: 1001 as const, // RFC 6455: server going away\n BAD_REQUEST: 1008 as const, // RFC 6455: policy violation / bad data\n SESSION_GONE: 4001 as const, // app-level: session deleted or shell exited\n PTY_NOT_RUNNING: 4002 as const, // app-level: PTY not running\n} as const;\n\nfunction closeClients(session: Session, code: number, reason: string): void {\n session.pty?.kill();\n for (const client of session.clients) client.close(code, reason);\n for (const sub of session.subscribers) sub.close(code, reason);\n session.subscribers.clear();\n}\n\n/**\n * Closes all WebSocket clients for `session` with the SESSION_GONE close code.\n *\n * @param session - The session to close.\n */\nexport function closeSession(session: Session): void {\n closeClients(session, WS_CLOSE.SESSION_GONE, 'session deleted');\n}\n\n/** Closes all WebSocket clients across every active session with the SERVER_STOPPED close code. */\nexport function closeAllSessions(): void {\n for (const session of sessionRegistry.values()) {\n closeClients(session, WS_CLOSE.SERVER_STOPPED, 'server stopped');\n }\n}\n\n/**\n * Sends `line` to all open event subscribers of `session`.\n *\n * @param session - The session whose subscribers to notify.\n * @param line - The line to send.\n */\nexport function broadcastToSubscribers(session: Session, line: string): void {\n for (const sub of session.subscribers) {\n if (sub.readyState === sub.OPEN) sub.send(line);\n }\n}\n\nlet onLastSessionClosed: (() => void) | null = null;\n\n/**\n * Registers a callback invoked once the last session closes (e.g. to stop the HTTP server).\n *\n * @param handler - Callback to invoke when the last session closes.\n */\nexport function setLastSessionClosedHandler(handler: () => void): void {\n onLastSessionClosed = handler;\n}\n\nconst DIM = '\\x1b[2m';\nconst RESET = '\\x1b[0m';\nconst BOLD = '\\x1b[1m';\nconst ITALIC = '\\x1b[3m';\nconst YELLOW = '\\x1b[1;33m';\nconst CYAN = '\\x1b[1;36m';\n\nconst isBun = process.execPath.includes('bun');\nconst PKG_RUNNER = isBun ? 'bunx' : 'npx';\n\nfunction sessionBanner(): string {\n const W = 48;\n const pipe = (content: string) => `${CYAN}║${RESET}${content}${CYAN}║${RESET}`;\n const blank = pipe(' '.repeat(W));\n\n const titleVis = ' [ webtty ] Terminal UI in the browser';\n const titleStr = ` ${DIM}[${RESET} ${BOLD}${YELLOW}webtty${RESET} ${DIM}] Terminal UI in the browser${RESET}`;\n const titleLine = pipe(titleStr + ' '.repeat(W - titleVis.length));\n\n const cmd = `${PKG_RUNNER} webtty help`;\n const helpVis = ` Run \\`${cmd}\\` for more information.`;\n const helpStr = ` ${DIM}Run \\`${RESET}${ITALIC}${cmd}${RESET}${DIM}\\` for more information.${RESET}`;\n const helpLine = pipe(helpStr + ' '.repeat(W - helpVis.length));\n\n return [\n `${CYAN}╔${'═'.repeat(W)}╗${RESET}`,\n '\\r\\n',\n blank,\n '\\r\\n',\n titleLine,\n '\\r\\n',\n blank,\n '\\r\\n',\n helpLine,\n '\\r\\n',\n blank,\n '\\r\\n',\n `${CYAN}╚${'═'.repeat(W)}╝${RESET}`,\n '\\r\\n',\n '\\r\\n',\n ].join('');\n}\n\nfunction handleSubscribe(ws: WS, id: string): void {\n const session = sessionRegistry.get(id);\n if (!session) {\n ws.close(WS_CLOSE.SESSION_GONE, 'session deleted');\n return;\n }\n if (session.pty === null) {\n ws.close(WS_CLOSE.PTY_NOT_RUNNING, 'PTY not running');\n return;\n }\n session.subscribers.add(ws);\n ws.on('close', () => {\n session.subscribers.delete(ws);\n });\n ws.on('error', () => {});\n}\n\n/**\n * Attaches a WebSocket server to `httpServer`, handling PTY I/O, session management,\n * scrollback replay, and terminal resize for all `/ws/:id/pty` connections,\n * and event subscriptions for `/ws/:id/events` connections.\n *\n * @param httpServer - The HTTP server to attach the WebSocket server to.\n * @returns The configured {@link WebSocketServer}.\n */\nexport function createWebSocketServer(httpServer: http.Server): WebSocketServer {\n const wss = new WebSocketServer({ noServer: true });\n\n httpServer.on('upgrade', (req, socket, head) => {\n const url = new URL(req.url ?? '/', `http://${req.headers.host ?? '127.0.0.1'}`);\n if (\n url.pathname.match(/^\\/ws\\/([^/]+)\\/pty$/) ||\n url.pathname.match(/^\\/ws\\/([^/]+)\\/events$/)\n ) {\n wss.handleUpgrade(req, socket, head, (ws) => wss.emit('connection', ws, req));\n } else {\n socket.destroy();\n }\n });\n\n wss.on('connection', (ws: WS, req: http.IncomingMessage) => {\n const url = new URL(req.url ?? '/', `http://${req.headers.host ?? '127.0.0.1'}`);\n const ptyMatch = url.pathname.match(/^\\/ws\\/([^/]+)\\/pty$/);\n const eventsMatch = url.pathname.match(/^\\/ws\\/([^/]+)\\/events$/);\n const match = ptyMatch ?? eventsMatch;\n\n if (!match) {\n ws.close();\n return;\n }\n\n let id: string;\n try {\n id = decodeURIComponent(match[1]);\n } catch {\n ws.close(WS_CLOSE.BAD_REQUEST, 'Bad Request');\n return;\n }\n\n if (eventsMatch) {\n handleSubscribe(ws, id);\n return;\n }\n\n const cols = Math.max(\n 1,\n Math.min(1000, Number.parseInt(url.searchParams.get('cols') ?? '80', 10) || 80),\n );\n const rows = Math.max(\n 1,\n Math.min(500, Number.parseInt(url.searchParams.get('rows') ?? '24', 10) || 24),\n );\n\n if (!sessionRegistry.has(id)) {\n ws.close(WS_CLOSE.SESSION_GONE, 'session deleted');\n return;\n }\n\n const session = sessionRegistry.get(id);\n if (!session) {\n ws.close(WS_CLOSE.SESSION_GONE, 'session deleted');\n return;\n }\n session.clients.add(ws);\n setLastUsedId(id);\n\n if (!session.pty) {\n const config = loadConfig();\n session.pty = spawnForSession(cols, rows, config.shell, config.term, config.colorTerm);\n\n session.pty.onData((data: string) => {\n session.scrollback = (session.scrollback + data).slice(-config.scrollback);\n for (const client of session.clients) {\n if (client.readyState === client.OPEN) {\n client.send(data, { binary: false });\n }\n }\n });\n\n session.pty.onExit(() => {\n sessionRegistry.delete(session.id);\n for (const client of session.clients) {\n if (client.readyState === client.OPEN) {\n client.close(WS_CLOSE.SESSION_GONE, 'shell exited');\n }\n }\n for (const sub of session.subscribers) {\n if (sub.readyState === sub.OPEN) {\n sub.close(WS_CLOSE.SESSION_GONE, 'shell exited');\n }\n }\n session.subscribers.clear();\n session.pty = null;\n if (sessionRegistry.size === 0) onLastSessionClosed?.();\n });\n\n const banner = sessionBanner();\n ws.send(banner);\n session.scrollback = banner;\n session.pty.write('\\n');\n } else {\n if (session.scrollback) {\n ws.send(session.scrollback, { binary: false });\n }\n session.pty.resize(cols, rows);\n }\n\n ws.on('message', (data: Buffer) => {\n const message = data.toString('utf8');\n if (message.startsWith('{')) {\n try {\n const msg = JSON.parse(message) as { type: string; cols: number; rows: number };\n if (msg.type === 'resize') {\n const c = Math.max(1, Math.min(1000, Math.trunc(msg.cols) || 80));\n const r = Math.max(1, Math.min(500, Math.trunc(msg.rows) || 24));\n session.pty?.resize(c, r);\n return;\n }\n } catch {\n // fall through\n }\n }\n session.pty?.write(message);\n });\n\n ws.on('close', () => {\n session.clients.delete(ws);\n });\n\n ws.on('error', () => {});\n });\n\n return wss;\n}\n",
13
- "export type { PtyProcess } from './types';\n\n// Bun.Terminal does not implement PTY on Windows (Bun.spawn({ terminal })\n// is a no-op there), so fall back to node-pty on that platform.\n// On all other platforms, prefer Bun.Terminal when running under Bun.\nconst isBun = !!process.versions.bun && process.platform !== 'win32';\nconsole.log(`pty: ${isBun ? 'Bun.Terminal' : 'node-pty'}`);\n\nconst { spawn: _spawn } = await (isBun ? import('./bun') : import('./node'));\n\nexport const spawn = _spawn;\n\n/**\n * Convenience wrapper: spawns a PTY using session-oriented parameters from config.\n *\n * @param cols - Terminal width in columns.\n * @param rows - Terminal height in rows.\n * @param shell - Shell executable path (e.g., `/bin/bash`).\n * @param term - `$TERM` environment variable (e.g., `xterm-256color`).\n * @param colorTerm - `$COLORTERM` environment variable (e.g., `truecolor`).\n * @returns A {@link PtyProcess} handle for reading/writing and managing the PTY.\n */\nexport function spawnForSession(\n cols: number,\n rows: number,\n shell: string,\n term: string,\n colorTerm: string,\n) {\n return _spawn(shell, cols, rows, term, colorTerm);\n}\n"
12
+ "import type http from 'node:http';\nimport type { WebSocket as WS } from 'ws';\nimport { WebSocketServer } from 'ws';\nimport { loadConfig } from '../config';\nimport { spawnForSession } from '../pty';\nimport type { Session } from './session';\nimport { sessionRegistry, setLastUsedId } from './session';\n\nconst WS_CLOSE = {\n SERVER_STOPPED: 1001 as const, // RFC 6455: server going away\n BAD_REQUEST: 1008 as const, // RFC 6455: policy violation / bad data\n SESSION_GONE: 4001 as const, // app-level: session deleted or shell exited\n PTY_NOT_RUNNING: 4002 as const, // app-level: PTY not running\n} as const;\n\nfunction closeClients(session: Session, code: number, reason: string): void {\n session.pty?.kill();\n for (const client of session.clients) client.close(code, reason);\n for (const sub of session.subscribers) sub.close(code, reason);\n session.subscribers.clear();\n}\n\n/**\n * Closes all WebSocket clients for `session` with the SESSION_GONE close code.\n *\n * @param session - The session to close.\n */\nexport function closeSession(session: Session): void {\n closeClients(session, WS_CLOSE.SESSION_GONE, 'session deleted');\n}\n\n/** Closes all WebSocket clients across every active session with the SERVER_STOPPED close code. */\nexport function closeAllSessions(): void {\n for (const session of sessionRegistry.values()) {\n closeClients(session, WS_CLOSE.SERVER_STOPPED, 'server stopped');\n }\n}\n\n/**\n * Sends `line` to all open event subscribers of `session`.\n *\n * @param session - The session whose subscribers to notify.\n * @param line - The line to send.\n */\nexport function broadcastToSubscribers(session: Session, line: string): void {\n for (const sub of session.subscribers) {\n if (sub.readyState === sub.OPEN) sub.send(line);\n }\n}\n\nlet onLastSessionClosed: (() => void) | null = null;\n\n/**\n * Registers a callback invoked once the last session closes (e.g. to stop the HTTP server).\n *\n * @param handler - Callback to invoke when the last session closes.\n */\nexport function setLastSessionClosedHandler(handler: () => void): void {\n onLastSessionClosed = handler;\n}\n\nconst DIM = '\\x1b[2m';\nconst RESET = '\\x1b[0m';\nconst BOLD = '\\x1b[1m';\nconst ITALIC = '\\x1b[3m';\nconst YELLOW = '\\x1b[1;33m';\nconst CYAN = '\\x1b[1;36m';\n\nconst isBun = process.execPath.includes('bun');\nconst PKG_RUNNER = isBun ? 'bunx' : 'npx';\n\nfunction sessionBanner(): string {\n const W = 48;\n const pipe = (content: string) => `${CYAN}║${RESET}${content}${CYAN}║${RESET}`;\n const blank = pipe(' '.repeat(W));\n\n const titleVis = ' [ webtty ] Terminal UI in the browser';\n const titleStr = ` ${DIM}[${RESET} ${BOLD}${YELLOW}webtty${RESET} ${DIM}] Terminal UI in the browser${RESET}`;\n const titleLine = pipe(titleStr + ' '.repeat(W - titleVis.length));\n\n const cmd = `${PKG_RUNNER} webtty help`;\n const helpVis = ` Run \\`${cmd}\\` for more information.`;\n const helpStr = ` ${DIM}Run \\`${RESET}${ITALIC}${cmd}${RESET}${DIM}\\` for more information.${RESET}`;\n const helpLine = pipe(helpStr + ' '.repeat(W - helpVis.length));\n\n return [\n `${CYAN}╔${'═'.repeat(W)}╗${RESET}`,\n '\\r\\n',\n blank,\n '\\r\\n',\n titleLine,\n '\\r\\n',\n blank,\n '\\r\\n',\n helpLine,\n '\\r\\n',\n blank,\n '\\r\\n',\n `${CYAN}╚${'═'.repeat(W)}╝${RESET}`,\n '\\r\\n',\n '\\r\\n',\n ].join('');\n}\n\nfunction handleSubscribe(ws: WS, id: string): void {\n const session = sessionRegistry.get(id);\n if (!session) {\n ws.close(WS_CLOSE.SESSION_GONE, 'session deleted');\n return;\n }\n if (session.pty === null) {\n ws.close(WS_CLOSE.PTY_NOT_RUNNING, 'PTY not running');\n return;\n }\n session.subscribers.add(ws);\n ws.on('close', () => {\n session.subscribers.delete(ws);\n });\n ws.on('error', () => {});\n}\n\n/**\n * Attaches a WebSocket server to `httpServer`, handling PTY I/O, session management,\n * scrollback replay, and terminal resize for all `/ws/:id/pty` connections,\n * and event subscriptions for `/ws/:id/events` connections.\n *\n * @param httpServer - The HTTP server to attach the WebSocket server to.\n * @returns The configured {@link WebSocketServer}.\n */\nexport function createWebSocketServer(httpServer: http.Server): WebSocketServer {\n const wss = new WebSocketServer({ noServer: true });\n\n httpServer.on('upgrade', (req, socket, head) => {\n const url = new URL(req.url ?? '/', `http://${req.headers.host ?? '127.0.0.1'}`);\n if (\n url.pathname.match(/^\\/ws\\/([^/]+)\\/pty$/) ||\n url.pathname.match(/^\\/ws\\/([^/]+)\\/events$/)\n ) {\n wss.handleUpgrade(req, socket, head, (ws) => wss.emit('connection', ws, req));\n } else {\n socket.destroy();\n }\n });\n\n wss.on('connection', (ws: WS, req: http.IncomingMessage) => {\n const url = new URL(req.url ?? '/', `http://${req.headers.host ?? '127.0.0.1'}`);\n const ptyMatch = url.pathname.match(/^\\/ws\\/([^/]+)\\/pty$/);\n const eventsMatch = url.pathname.match(/^\\/ws\\/([^/]+)\\/events$/);\n const match = ptyMatch ?? eventsMatch;\n\n if (!match) {\n ws.close();\n return;\n }\n\n let id: string;\n try {\n id = decodeURIComponent(match[1]);\n } catch {\n ws.close(WS_CLOSE.BAD_REQUEST, 'Bad Request');\n return;\n }\n\n if (eventsMatch) {\n handleSubscribe(ws, id);\n return;\n }\n\n const cols = Math.max(\n 1,\n Math.min(1000, Number.parseInt(url.searchParams.get('cols') ?? '80', 10) || 80),\n );\n const rows = Math.max(\n 1,\n Math.min(500, Number.parseInt(url.searchParams.get('rows') ?? '24', 10) || 24),\n );\n\n if (!sessionRegistry.has(id)) {\n ws.close(WS_CLOSE.SESSION_GONE, 'session deleted');\n return;\n }\n\n const session = sessionRegistry.get(id);\n if (!session) {\n ws.close(WS_CLOSE.SESSION_GONE, 'session deleted');\n return;\n }\n session.clients.add(ws);\n setLastUsedId(id);\n\n if (!session.pty) {\n const config = loadConfig();\n session.pty = spawnForSession(\n cols,\n rows,\n config.shell,\n config.term,\n config.colorTerm,\n session.baseDir,\n config.env,\n );\n\n session.pty.onData((data: string) => {\n session.scrollback = (session.scrollback + data).slice(-config.scrollback);\n for (const client of session.clients) {\n if (client.readyState === client.OPEN) {\n client.send(data, { binary: false });\n }\n }\n });\n\n session.pty.onExit(() => {\n sessionRegistry.delete(session.id);\n for (const client of session.clients) {\n if (client.readyState === client.OPEN) {\n client.close(WS_CLOSE.SESSION_GONE, 'shell exited');\n }\n }\n for (const sub of session.subscribers) {\n if (sub.readyState === sub.OPEN) {\n sub.close(WS_CLOSE.SESSION_GONE, 'shell exited');\n }\n }\n session.subscribers.clear();\n session.pty = null;\n if (sessionRegistry.size === 0) onLastSessionClosed?.();\n });\n\n const banner = sessionBanner();\n ws.send(banner);\n session.scrollback = banner;\n session.pty.write('\\n');\n } else {\n if (session.scrollback) {\n ws.send(session.scrollback, { binary: false });\n }\n session.pty.resize(cols, rows);\n }\n\n ws.on('message', (data: Buffer) => {\n const message = data.toString('utf8');\n if (message.startsWith('{')) {\n try {\n const msg = JSON.parse(message) as { type: string; cols: number; rows: number };\n if (msg.type === 'resize') {\n const c = Math.max(1, Math.min(1000, Math.trunc(msg.cols) || 80));\n const r = Math.max(1, Math.min(500, Math.trunc(msg.rows) || 24));\n session.pty?.resize(c, r);\n return;\n }\n } catch {\n // fall through\n }\n }\n session.pty?.write(message);\n });\n\n ws.on('close', () => {\n session.clients.delete(ws);\n });\n\n ws.on('error', () => {});\n });\n\n return wss;\n}\n",
13
+ "export type { PtyProcess } from './types';\n\n// Bun.Terminal does not implement PTY on Windows (Bun.spawn({ terminal })\n// is a no-op there), so fall back to node-pty on that platform.\n// On all other platforms, prefer Bun.Terminal when running under Bun.\nconst isBun = !!process.versions.bun && process.platform !== 'win32';\nconsole.log(`pty: ${isBun ? 'Bun.Terminal' : 'node-pty'}`);\n\nconst { spawn: _spawn } = await (isBun ? import('./bun') : import('./node'));\n\nexport const spawn = _spawn;\n\n/**\n * Convenience wrapper: spawns a PTY using session-oriented parameters from config.\n *\n * @param cols - Terminal width in columns.\n * @param rows - Terminal height in rows.\n * @param shell - Shell executable path (e.g., `/bin/bash`).\n * @param term - `$TERM` environment variable (e.g., `xterm-256color`).\n * @param colorTerm - `$COLORTERM` environment variable (e.g., `truecolor`).\n * @param cwd - Working directory for the shell.\n * @returns A {@link PtyProcess} handle for reading/writing and managing the PTY.\n */\nexport function spawnForSession(\n cols: number,\n rows: number,\n shell: string,\n term: string,\n colorTerm: string,\n cwd: string,\n env: Record<string, string>,\n) {\n return _spawn(shell, cols, rows, term, colorTerm, cwd, env);\n}\n"
14
14
  ],
15
- "mappings": "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",
16
- "debugId": "493EB9B10F4BA8D664756E2164756E21",
15
+ "mappings": "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",
16
+ "debugId": "17F1B5AF7114819864756E2164756E21",
17
17
  "names": []
18
18
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webtty",
3
- "version": "1.9.0",
3
+ "version": "1.11.0",
4
4
  "description": "Web TTY for running CLI/TUI applications in a browser tab, across platforms",
5
5
  "license": "MIT",
6
6
  "bin": {