webtty 1.8.2 → 1.9.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
@@ -27,7 +27,8 @@ import fs from "node:fs";
27
27
  import os from "node:os";
28
28
  import path from "node:path";
29
29
  function configDir() {
30
- return path.join(process.env.HOME ?? os.homedir(), ".config", "webtty");
30
+ const home = process.env.HOME && path.isAbsolute(process.env.HOME) ? process.env.HOME : os.homedir();
31
+ return path.join(home, ".config", "webtty");
31
32
  }
32
33
  function getConfigPath() {
33
34
  return path.join(configDir(), "config.json");
@@ -174,14 +175,18 @@ import path2 from "node:path";
174
175
  import { fileURLToPath } from "node:url";
175
176
  var __filename2 = fileURLToPath(import.meta.url);
176
177
  var __dirname2 = path2.dirname(__filename2);
177
- var PORT = Number(process.env.PORT) || 2346;
178
- var BASE_URL = `http://127.0.0.1:${PORT}`;
178
+ function getPort() {
179
+ return loadConfig().port;
180
+ }
181
+ function getBaseUrl() {
182
+ return `http://127.0.0.1:${getPort()}`;
183
+ }
179
184
  function logPath() {
180
185
  return path2.join(configDir(), "server.log");
181
186
  }
182
187
  async function isServerRunning() {
183
188
  try {
184
- const res = await fetch(`${BASE_URL}/api/sessions`);
189
+ const res = await fetch(`${getBaseUrl()}/api/sessions`);
185
190
  if (!res.ok)
186
191
  return false;
187
192
  const body = await res.json();
@@ -194,8 +199,13 @@ async function startServer(timeoutMs = 1e4, _spawn = childProcess.spawn) {
194
199
  const isBun = typeof globalThis.Bun !== "undefined";
195
200
  const useNode = process.platform === "win32" && isBun;
196
201
  const serverExec = useNode ? "node" : process.execPath;
197
- const isTs = isBun && !useNode && __filename2.endsWith(".ts");
198
- const serverEntry = path2.resolve(__dirname2, isTs ? "../server/index.ts" : "../server/index.js");
202
+ const runningFromSrc = __filename2.endsWith(".ts");
203
+ let serverEntry;
204
+ if (useNode) {
205
+ serverEntry = runningFromSrc ? path2.resolve(__dirname2, "../../dist/server/index.js") : path2.resolve(__dirname2, "../server/index.js");
206
+ } else {
207
+ serverEntry = path2.resolve(__dirname2, runningFromSrc ? "../server/index.ts" : "../server/index.js");
208
+ }
199
209
  if (!fs2.existsSync(serverEntry)) {
200
210
  console.error(`webtty: server entry not found at ${serverEntry}`);
201
211
  process.exit(1);
@@ -213,7 +223,7 @@ async function startServer(timeoutMs = 1e4, _spawn = childProcess.spawn) {
213
223
  const child = _spawn(serverExec, [serverEntry], {
214
224
  detached: true,
215
225
  stdio,
216
- env: { ...process.env, PORT: String(PORT) }
226
+ env: { ...process.env, PORT: String(config.port) }
217
227
  });
218
228
  child.on("error", (err) => {
219
229
  const hint = useNode ? " (Node.js must be on PATH when running webtty under Bun on Windows)" : "";
@@ -232,7 +242,7 @@ async function startServer(timeoutMs = 1e4, _spawn = childProcess.spawn) {
232
242
  console.error("webtty: server did not start in time");
233
243
  process.exit(1);
234
244
  }
235
- async function stopServer(baseUrl = BASE_URL, timeoutMs = 5000) {
245
+ async function stopServer(baseUrl = getBaseUrl(), timeoutMs = 5000) {
236
246
  try {
237
247
  const res = await fetch(`${baseUrl}/api/server/stop`, { method: "POST" });
238
248
  if (!res.ok)
@@ -274,11 +284,11 @@ async function cmdGo(id = "main") {
274
284
  await startServer();
275
285
  }
276
286
  let sessionId;
277
- const check = await fetch(`${BASE_URL}/api/sessions/${encodeURIComponent(id)}`);
287
+ const check = await fetch(`${getBaseUrl()}/api/sessions/${encodeURIComponent(id)}`);
278
288
  if (check.status === 200) {
279
289
  sessionId = id;
280
290
  } else {
281
- const res = await fetch(`${BASE_URL}/api/sessions`, {
291
+ const res = await fetch(`${getBaseUrl()}/api/sessions`, {
282
292
  method: "POST",
283
293
  headers: { "Content-Type": "application/json" },
284
294
  body: JSON.stringify({ id })
@@ -291,14 +301,14 @@ async function cmdGo(id = "main") {
291
301
  const session = await res.json();
292
302
  sessionId = session.id;
293
303
  }
294
- const url = `http://${toBrowserHost(loadConfig().host)}:${PORT}/s/${sessionId}`;
304
+ const url = `http://${toBrowserHost(loadConfig().host)}:${getPort()}/s/${sessionId}`;
295
305
  console.log(url);
296
306
  openBrowser(url);
297
307
  }
298
308
  async function cmdList(filter) {
299
309
  let res;
300
310
  try {
301
- res = await fetch(`${BASE_URL}/api/sessions`);
311
+ res = await fetch(`${getBaseUrl()}/api/sessions`);
302
312
  } catch {
303
313
  console.log("webtty is not running");
304
314
  process.exit(1);
@@ -322,7 +332,7 @@ async function cmdRemove(id) {
322
332
  }
323
333
  let res;
324
334
  try {
325
- res = await fetch(`${BASE_URL}/api/sessions/${encodeURIComponent(id)}`, {
335
+ res = await fetch(`${getBaseUrl()}/api/sessions/${encodeURIComponent(id)}`, {
326
336
  method: "DELETE"
327
337
  });
328
338
  } catch {
@@ -350,7 +360,7 @@ async function cmdRename(id, newId) {
350
360
  }
351
361
  let res;
352
362
  try {
353
- res = await fetch(`${BASE_URL}/api/sessions/${encodeURIComponent(id)}`, {
363
+ res = await fetch(`${getBaseUrl()}/api/sessions/${encodeURIComponent(id)}`, {
354
364
  method: "PATCH",
355
365
  headers: { "Content-Type": "application/json" },
356
366
  body: JSON.stringify({ id: newId })
@@ -562,4 +572,4 @@ Run \`webtty help\` for usage.`);
562
572
  }
563
573
  }
564
574
 
565
- //# debugId=A46A5CFADFC7022864756E2164756E21
575
+ //# debugId=8359C04A2D29CCC864756E2164756E21
@@ -2,12 +2,12 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/cli/commands.ts", "../src/config.ts", "../src/cli/http.ts", "../src/cli/index.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 { BASE_URL, isServerRunning, openBrowser, PORT, 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(`${BASE_URL}/api/sessions/${encodeURIComponent(id)}`);\n if (check.status === 200) {\n sessionId = id;\n } else {\n const res = await fetch(`${BASE_URL}/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)}:${PORT}/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(`${BASE_URL}/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(`${BASE_URL}/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(`${BASE_URL}/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 return path.join(process.env.HOME ?? os.homedir(), '.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, resolved from `PORT` env or config default. */\nexport const PORT = Number(process.env.PORT) || 2346;\n\n/** Base URL for internal CLI↔server API calls (always 127.0.0.1, avoids IPv6 lookup). */\nexport const BASE_URL = `http://127.0.0.1:${PORT}`;\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(`${BASE_URL}/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 // When the executor is Node.js it cannot run .ts files directly, so always\n // resolve to the compiled .js entry regardless of whether the CLI itself is\n // running from source.\n const isTs = isBun && !useNode && __filename.endsWith('.ts');\n const serverEntry = path.resolve(__dirname, isTs ? '../server/index.ts' : '../server/index.js');\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(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: BASE_URL).\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(baseUrl: string = BASE_URL, timeoutMs = 5000): 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",
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
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"
9
9
  ],
10
- "mappings": "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",
11
- "debugId": "A46A5CFADFC7022864756E2164756E21",
10
+ "mappings": "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",
11
+ "debugId": "8359C04A2D29CCC864756E2164756E21",
12
12
  "names": []
13
13
  }
@@ -1,3 +1,3 @@
1
- import{FitAddon as k,init as F,Terminal as T}from"/dist/ghostty-web.js";var Y=new RegExp("\x1B\\[(\\d*) q","g");function L(j,H){let P=j.options.cursorStyle,Z=j.options.cursorBlink;Y.lastIndex=0;let Q=Y.exec(H);while(Q!==null){let N=Q[1]===""?0:Number(Q[1]);if(!Number.isNaN(N)&&N>=0&&N<=6)j.options.cursorStyle=N===0||N===1||N===2?"block":N===3||N===4?"underline":"bar",j.options.cursorBlink=N===0||N===1||N===3||N===5;Q=Y.exec(H)}if((j.options.cursorStyle!==P||j.options.cursorBlink!==Z)&&j.renderer&&j.wasmTerm)j.renderer.render(j.wasmTerm,!0,j.viewportY)}var G=window.location.pathname.split("/s/")[1]??"main",V=await fetch("/api/config").then((j)=>j.json());document.title=`${G} | webtty`;await F();var J=new T({cols:V.cols,rows:V.rows,cursorStyle:V.cursorStyle,cursorBlink:V.cursorStyleBlink,fontSize:V.fontSize,fontFamily:V.fontFamily,scrollback:Math.ceil(V.scrollback/80),theme:V.theme}),O=new k;J.loadAddon(O);var X=document.getElementById("terminal");if(V.theme?.background)X.style.background=V.theme.background;await J.open(X);function q(){X.style.padding="0",O.fit();let j=X.querySelector("canvas");if(!j)return;let H=Math.max(0,X.clientWidth-j.offsetWidth),P=Math.max(0,X.clientHeight-j.offsetHeight);X.style.paddingLeft=`${Math.floor(H/2)}px`,X.style.paddingRight=`${Math.ceil(H/2)}px`,X.style.paddingTop=`${Math.floor(P/2)}px`,X.style.paddingBottom=`${Math.ceil(P/2)}px`}q();new ResizeObserver(()=>q()).observe(X,{box:"border-box"});var C=window.location.protocol==="https:"?"wss:":"ws:",K;function y(){let j=`${C}//${window.location.host}/ws/${G}?cols=${J.cols}&rows=${J.rows}`;K=new WebSocket(j);let H="\x1B[2m",P="\x1B[1;33m",Z="\x1B[3m",Q="\x1B[0m",N=`${H}[${Q} ${P}webtty${Q} ${H}]${Q}`,_=($)=>`\r
2
- ${N} ${H}${Z}${$}${Q}\r
3
- `;K.onopen=()=>{K.send(JSON.stringify({type:"resize",cols:J.cols,rows:J.rows}))},K.onmessage=($)=>{L(J,$.data),J.write($.data)},K.onclose=($)=>{if($.code===4001){J.write(_("Session removed.")),setTimeout(()=>window.close(),500);return}if($.code===1001){J.write(_("Server stopped.")),setTimeout(()=>window.close(),500);return}J.write(_("Connection lost. Reconnecting in 2s...")),setTimeout(y,2000)},K.onerror=()=>{J.write(_("WebSocket error."))}}y();var W=0,M=0;J.attachCustomWheelEventHandler((j)=>{if(!J.hasMouseTracking())return!1;let H=J.renderer?.getMetrics();if(!H)return!1;let P=j.deltaY<0?-1:1;if(P!==M)W=0,M=P;W+=V.mouseScrollSpeed;let Z=Math.trunc(W);if(Z===0)return!0;W-=Z;let Q=j.target.getBoundingClientRect(),N=Math.max(1,Math.floor((j.clientX-Q.left)/H.width)+1),_=Math.max(1,Math.floor((j.clientY-Q.top)/H.height)+1),U=`\x1B[<${P<0?64:65};${N};${_}M`;if(K&&K.readyState===WebSocket.OPEN)for(let x=0;x<Z;x++)K.send(U);return!0});X.addEventListener("keydown",(j)=>{let H=j.key.toLowerCase(),P=new Set([...j.shiftKey?["shift"]:[],...j.ctrlKey?["ctrl"]:[],...j.altKey?["alt"]:[],...j.metaKey?["meta"]:[]]),Z=V.keyboardBindings.find((Q)=>{if(Q.key.toLowerCase()!==H)return!1;let N=new Set((Array.isArray(Q.mods)?Q.mods:[]).map((_)=>_.toLowerCase()));if(N.size!==P.size)return!1;for(let _ of N)if(!P.has(_))return!1;return!0});if(!Z)return;if(j.preventDefault(),j.stopPropagation(),Z.chars&&K.readyState===WebSocket.OPEN)K.send(Z.chars)},{capture:!0});var B=Math.min(32,Math.max(6,V.fontSize));window.addEventListener("keydown",(j)=>{if(!j.ctrlKey&&!j.metaKey)return;let H=j.code==="Equal"||j.code==="NumpadAdd",P=j.code==="Minus"&&!j.shiftKey||j.code==="NumpadSubtract",Z=j.code==="Digit0"&&!j.shiftKey||j.code==="Numpad0";if(!H&&!P&&!Z)return;if(j.preventDefault(),j.stopPropagation(),H)B=Math.min(32,B+1);else if(P)B=Math.max(6,B-1);else B=Math.min(32,Math.max(6,V.fontSize));J.options.fontSize=B,q()},{capture:!0});J.onData((j)=>{if(K&&K.readyState===WebSocket.OPEN)K.send(j)});J.onResize(({cols:j,rows:H})=>{if(K&&K.readyState===WebSocket.OPEN)K.send(JSON.stringify({type:"resize",cols:j,rows:H}))});if(V.copyOnSelect)J.onSelectionChange(()=>{let j=J.getSelection();if(!j)return;navigator.clipboard.writeText(j).catch(()=>{})});if(V.rightClickBehavior==="copyPaste")X.addEventListener("contextmenu",(j)=>{let H=J.getSelection();if(!H)return;j.preventDefault(),navigator.clipboard.writeText(H).catch(()=>{}),J.clearSelection()});X.addEventListener("paste",(j)=>{let H=j.clipboardData;if(!H)return;if(H.getData("text/plain"))return;if(j.preventDefault(),j.stopImmediatePropagation(),K&&K.readyState===WebSocket.OPEN)K.send("\x16")},{capture:!0});
1
+ import{FitAddon as R,init as b,Terminal as F}from"/dist/ghostty-web.js";var L=new RegExp("\x1B\\[(\\d*) q","g");function k(j,J){let V=j.options.cursorStyle,$=j.options.cursorBlink;L.lastIndex=0;let X=L.exec(J);while(X!==null){let Q=X[1]===""?0:Number(X[1]);if(!Number.isNaN(Q)&&Q>=0&&Q<=6)j.options.cursorStyle=Q===0||Q===1||Q===2?"block":Q===3||Q===4?"underline":"bar",j.options.cursorBlink=Q===0||Q===1||Q===3||Q===5;X=L.exec(J)}if((j.options.cursorStyle!==V||j.options.cursorBlink!==$)&&j.renderer&&j.wasmTerm)j.renderer.render(j.wasmTerm,!0,j.viewportY)}function y(j,J){if(J&&j.startsWith("\x1B[<32;"))return"\x1B[<35;"+j.slice(6);return j}function M(j,J){return j.startsWith("\x1B[<32;")&&j===J}var C=window.location.pathname.split("/s/")[1]??"main",Z=await fetch("/api/config").then((j)=>j.json());document.title=`${C} | webtty`;await b();var K=new F({cols:Z.cols,rows:Z.rows,cursorStyle:Z.cursorStyle,cursorBlink:Z.cursorStyleBlink,fontSize:Z.fontSize,fontFamily:Z.fontFamily,scrollback:Math.ceil(Z.scrollback/80),theme:Z.theme}),T=new R;K.loadAddon(T);var _=document.getElementById("terminal");if(Z.theme?.background)_.style.background=Z.theme.background;await K.open(_);function I(){_.style.padding="0",T.fit();let j=_.querySelector("canvas");if(!j)return;let J=Math.max(0,_.clientWidth-j.offsetWidth),V=Math.max(0,_.clientHeight-j.offsetHeight);_.style.paddingLeft=`${Math.floor(J/2)}px`,_.style.paddingRight=`${Math.ceil(J/2)}px`,_.style.paddingTop=`${Math.floor(V/2)}px`,_.style.paddingBottom=`${Math.ceil(V/2)}px`}var x=null;function U(){if(x!==null)return;x=window.requestAnimationFrame(()=>{x=null,I()})}U();new ResizeObserver(()=>U()).observe(_,{box:"border-box"});window.addEventListener("resize",U);var q=window.location.protocol==="https:"?"wss:":"ws:",N;function z(){let j=`${q}//${window.location.host}/ws/${C}/pty?cols=${K.cols}&rows=${K.rows}`;N=new WebSocket(j);let J="\x1B[2m",V="\x1B[1;33m",$="\x1B[3m",X="\x1B[0m",Q=`${J}[${X} ${V}webtty${X} ${J}]${X}`,B=(P)=>`\r
2
+ ${Q} ${J}${$}${P}${X}\r
3
+ `;N.onopen=()=>{N.send(JSON.stringify({type:"resize",cols:K.cols,rows:K.rows}))},N.onmessage=(P)=>{k(K,P.data),K.write(P.data)},N.onclose=(P)=>{if(P.code===4001){K.write(B("Session removed.")),setTimeout(()=>window.close(),500);return}if(P.code===1001){K.write(B("Server stopped.")),setTimeout(()=>window.close(),500);return}K.write(B("Connection lost. Reconnecting in 2s...")),setTimeout(z,2000)},N.onerror=()=>{K.write(B("WebSocket error."))}}z();var Y=0,A=0;K.attachCustomWheelEventHandler((j)=>{if(!K.hasMouseTracking())return!1;let J=K.renderer?.getMetrics();if(!J)return!1;let V=j.deltaY<0?-1:1;if(V!==A)Y=0,A=V;Y+=Z.mouseScrollSpeed;let $=Math.trunc(Y);if($===0)return!0;Y-=$;let X=j.target.getBoundingClientRect(),Q=Math.max(1,Math.floor((j.clientX-X.left)/J.width)+1),B=Math.max(1,Math.floor((j.clientY-X.top)/J.height)+1),E=`\x1B[<${V<0?64:65};${Q};${B}M`;if(N&&N.readyState===WebSocket.OPEN)for(let H=0;H<$;H++)N.send(E);return!0});_.addEventListener("keydown",(j)=>{let J=j.key.toLowerCase(),V=new Set([...j.shiftKey?["shift"]:[],...j.ctrlKey?["ctrl"]:[],...j.altKey?["alt"]:[],...j.metaKey?["meta"]:[]]),$=Z.keyboardBindings.find((X)=>{if(X.key.toLowerCase()!==J)return!1;let Q=new Set((Array.isArray(X.mods)?X.mods:[]).map((B)=>B.toLowerCase()));if(Q.size!==V.size)return!1;for(let B of Q)if(!V.has(B))return!1;return!0});if(!$)return;if(j.preventDefault(),j.stopPropagation(),$.chars&&N.readyState===WebSocket.OPEN)N.send($.chars)},{capture:!0});var W=Math.min(32,Math.max(6,Z.fontSize));window.addEventListener("keydown",(j)=>{if(!j.ctrlKey&&!j.metaKey)return;let J=j.code==="Equal"||j.code==="NumpadAdd",V=j.code==="Minus"&&!j.shiftKey||j.code==="NumpadSubtract",$=j.code==="Digit0"&&!j.shiftKey||j.code==="Numpad0";if(!J&&!V&&!$)return;if(j.preventDefault(),j.stopPropagation(),J)W=Math.min(32,W+1);else if(V)W=Math.max(6,W-1);else W=Math.min(32,Math.max(6,Z.fontSize));K.options.fontSize=W,I()},{capture:!0});var O=!1,G="";_.addEventListener("mousemove",(j)=>{if(O=K.hasMouseTracking()&&j.buttons===0,O)G=""},{capture:!0});K.onData((j)=>{let J=y(j,O);if(J!==j){if(N&&N.readyState===WebSocket.OPEN)N.send(J);return}if(M(j,G))return;if(G=j.startsWith("\x1B[<32;")?j:"",N&&N.readyState===WebSocket.OPEN)N.send(j)});K.onResize(({cols:j,rows:J})=>{if(N&&N.readyState===WebSocket.OPEN)N.send(JSON.stringify({type:"resize",cols:j,rows:J}))});if(Z.copyOnSelect)K.onSelectionChange(()=>{let j=K.getSelection();if(!j)return;navigator.clipboard.writeText(j).catch(()=>{})});if(Z.rightClickBehavior==="copyPaste")_.addEventListener("contextmenu",(j)=>{let J=K.getSelection();if(!J)return;j.preventDefault(),navigator.clipboard.writeText(J).catch(()=>{}),K.clearSelection()});_.addEventListener("paste",(j)=>{let J=j.clipboardData;if(!J)return;if(J.getData("text/plain"))return;if(j.preventDefault(),j.stopImmediatePropagation(),N&&N.readyState===WebSocket.OPEN)N.send("\x16")},{capture:!0});
@@ -69,7 +69,8 @@ __export(exports_node, {
69
69
  import { homedir as homedir2 } from "node:os";
70
70
  import nodePty from "@lydell/node-pty";
71
71
  function spawn2(shell, cols, rows, term, colorTerm) {
72
- const ptyProc = nodePty.spawn(shell, [], {
72
+ const shellArgs = process.platform === "win32" && /cmd\.exe$/i.test(shell) ? ["/d"] : [];
73
+ const ptyProc = nodePty.spawn(shell, shellArgs, {
73
74
  name: term,
74
75
  cols,
75
76
  rows,
@@ -108,7 +109,8 @@ import fs from "node:fs";
108
109
  import os from "node:os";
109
110
  import path from "node:path";
110
111
  function configDir() {
111
- return path.join(process.env.HOME ?? os.homedir(), ".config", "webtty");
112
+ const home = process.env.HOME && path.isAbsolute(process.env.HOME) ? process.env.HOME : os.homedir();
113
+ return path.join(home, ".config", "webtty");
112
114
  }
113
115
  function getConfigPath() {
114
116
  return path.join(configDir(), "config.json");
@@ -270,6 +272,7 @@ function createSession(id) {
270
272
  createdAt: Date.now(),
271
273
  pty: null,
272
274
  clients: new Set,
275
+ subscribers: new Set,
273
276
  scrollback: ""
274
277
  };
275
278
  sessionRegistry.set(id, session);
@@ -345,7 +348,7 @@ function serveFile(filePath, res) {
345
348
  import { WebSocketServer } from "ws";
346
349
 
347
350
  // src/pty/index.ts
348
- var isBun = !!process.versions.bun;
351
+ var isBun = !!process.versions.bun && process.platform !== "win32";
349
352
  console.log(`pty: ${isBun ? "Bun.Terminal" : "node-pty"}`);
350
353
  var { spawn: _spawn } = await (isBun ? Promise.resolve().then(() => (init_bun(), exports_bun)) : Promise.resolve().then(() => (init_node(), exports_node)));
351
354
  function spawnForSession(cols, rows, shell, term, colorTerm) {
@@ -356,12 +359,16 @@ function spawnForSession(cols, rows, shell, term, colorTerm) {
356
359
  var WS_CLOSE = {
357
360
  SERVER_STOPPED: 1001,
358
361
  BAD_REQUEST: 1008,
359
- SESSION_GONE: 4001
362
+ SESSION_GONE: 4001,
363
+ PTY_NOT_RUNNING: 4002
360
364
  };
361
365
  function closeClients(session, code, reason) {
362
366
  session.pty?.kill();
363
367
  for (const client of session.clients)
364
368
  client.close(code, reason);
369
+ for (const sub of session.subscribers)
370
+ sub.close(code, reason);
371
+ session.subscribers.clear();
365
372
  }
366
373
  function closeSession(session) {
367
374
  closeClients(session, WS_CLOSE.SESSION_GONE, "session deleted");
@@ -371,6 +378,12 @@ function closeAllSessions() {
371
378
  closeClients(session, WS_CLOSE.SERVER_STOPPED, "server stopped");
372
379
  }
373
380
  }
381
+ function broadcastToSubscribers(session, line) {
382
+ for (const sub of session.subscribers) {
383
+ if (sub.readyState === sub.OPEN)
384
+ sub.send(line);
385
+ }
386
+ }
374
387
  var onLastSessionClosed = null;
375
388
  function setLastSessionClosedHandler(handler) {
376
389
  onLastSessionClosed = handler;
@@ -420,11 +433,27 @@ function sessionBanner() {
420
433
  `
421
434
  ].join("");
422
435
  }
436
+ function handleSubscribe(ws, id) {
437
+ const session = sessionRegistry.get(id);
438
+ if (!session) {
439
+ ws.close(WS_CLOSE.SESSION_GONE, "session deleted");
440
+ return;
441
+ }
442
+ if (session.pty === null) {
443
+ ws.close(WS_CLOSE.PTY_NOT_RUNNING, "PTY not running");
444
+ return;
445
+ }
446
+ session.subscribers.add(ws);
447
+ ws.on("close", () => {
448
+ session.subscribers.delete(ws);
449
+ });
450
+ ws.on("error", () => {});
451
+ }
423
452
  function createWebSocketServer(httpServer) {
424
453
  const wss = new WebSocketServer({ noServer: true });
425
454
  httpServer.on("upgrade", (req, socket, head) => {
426
455
  const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "127.0.0.1"}`);
427
- if (url.pathname.match(/^\/ws\/([^/]+)$/)) {
456
+ if (url.pathname.match(/^\/ws\/([^/]+)\/pty$/) || url.pathname.match(/^\/ws\/([^/]+)\/events$/)) {
428
457
  wss.handleUpgrade(req, socket, head, (ws) => wss.emit("connection", ws, req));
429
458
  } else {
430
459
  socket.destroy();
@@ -432,18 +461,24 @@ function createWebSocketServer(httpServer) {
432
461
  });
433
462
  wss.on("connection", (ws, req) => {
434
463
  const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "127.0.0.1"}`);
435
- const wsMatch = url.pathname.match(/^\/ws\/([^/]+)$/);
436
- if (!wsMatch) {
464
+ const ptyMatch = url.pathname.match(/^\/ws\/([^/]+)\/pty$/);
465
+ const eventsMatch = url.pathname.match(/^\/ws\/([^/]+)\/events$/);
466
+ const match = ptyMatch ?? eventsMatch;
467
+ if (!match) {
437
468
  ws.close();
438
469
  return;
439
470
  }
440
471
  let id;
441
472
  try {
442
- id = decodeURIComponent(wsMatch[1]);
473
+ id = decodeURIComponent(match[1]);
443
474
  } catch {
444
475
  ws.close(WS_CLOSE.BAD_REQUEST, "Bad Request");
445
476
  return;
446
477
  }
478
+ if (eventsMatch) {
479
+ handleSubscribe(ws, id);
480
+ return;
481
+ }
447
482
  const cols = Math.max(1, Math.min(1000, Number.parseInt(url.searchParams.get("cols") ?? "80", 10) || 80));
448
483
  const rows = Math.max(1, Math.min(500, Number.parseInt(url.searchParams.get("rows") ?? "24", 10) || 24));
449
484
  if (!sessionRegistry.has(id)) {
@@ -475,6 +510,12 @@ function createWebSocketServer(httpServer) {
475
510
  client.close(WS_CLOSE.SESSION_GONE, "shell exited");
476
511
  }
477
512
  }
513
+ for (const sub of session.subscribers) {
514
+ if (sub.readyState === sub.OPEN) {
515
+ sub.close(WS_CLOSE.SESSION_GONE, "shell exited");
516
+ }
517
+ }
518
+ session.subscribers.clear();
478
519
  session.pty = null;
479
520
  if (sessionRegistry.size === 0)
480
521
  onLastSessionClosed?.();
@@ -704,6 +745,65 @@ async function handleRequest(req, res, distPath, wasmPath, clientDistPath, onSto
704
745
  serveFile(clientHtml, res);
705
746
  return;
706
747
  }
748
+ const publishMatch = pathname.match(/^\/s\/([^/]+)\/publish$/);
749
+ if (req.method === "POST" && publishMatch) {
750
+ const id = decodeId(publishMatch[1]);
751
+ if (!id) {
752
+ res.writeHead(400);
753
+ res.end("Bad Request");
754
+ return;
755
+ }
756
+ if (!(req.headers["content-type"] ?? "").startsWith("application/json")) {
757
+ res.writeHead(400);
758
+ res.end("Bad Request");
759
+ return;
760
+ }
761
+ const session = sessionRegistry.get(id);
762
+ if (!session) {
763
+ res.writeHead(404);
764
+ res.end("Not Found");
765
+ return;
766
+ }
767
+ if (session.pty === null) {
768
+ res.writeHead(409);
769
+ res.end("PTY not running");
770
+ return;
771
+ }
772
+ let buf = "";
773
+ req.on("data", (chunk) => {
774
+ buf += chunk.toString("utf8");
775
+ const lines = buf.split(`
776
+ `);
777
+ buf = lines.pop() ?? "";
778
+ if (buf.length > MAX_BODY)
779
+ buf = "";
780
+ for (const line of lines) {
781
+ const trimmed = line.replace(/\r$/, "");
782
+ if (!trimmed)
783
+ continue;
784
+ try {
785
+ JSON.parse(trimmed);
786
+ broadcastToSubscribers(session, trimmed);
787
+ } catch {}
788
+ }
789
+ });
790
+ req.on("end", () => {
791
+ const trimmed = buf.replace(/\r$/, "");
792
+ if (trimmed) {
793
+ try {
794
+ JSON.parse(trimmed);
795
+ broadcastToSubscribers(session, trimmed);
796
+ } catch {}
797
+ }
798
+ res.writeHead(204);
799
+ res.end();
800
+ });
801
+ req.on("error", () => {
802
+ res.writeHead(500);
803
+ res.end();
804
+ });
805
+ return;
806
+ }
707
807
  const pidMatch = pathname.match(/^\/p\/(\d+)$/);
708
808
  if (req.method === "GET" && pidMatch) {
709
809
  const pid = parseInt(pidMatch[1], 10);
@@ -786,4 +886,4 @@ httpServer.listen(HTTP_PORT, HTTP_HOST, () => {
786
886
  console.log(`listening on http://${HTTP_HOST_DISPLAY}:${HTTP_PORT}`);
787
887
  });
788
888
 
789
- //# debugId=65C3425C7B57D40F64756E2164756E21
889
+ //# debugId=493EB9B10F4BA8D664756E2164756E21
@@ -3,16 +3,16 @@
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
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 const ptyProc = nodePty.spawn(shell, [], {\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",
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
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 return path.join(process.env.HOME ?? os.homedir(), '.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 { 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 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 /** 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 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",
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",
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} 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}\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\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\n/**\n * Attaches a WebSocket server to `httpServer`, handling PTY I/O, session management,\n * scrollback replay, and terminal resize for all `/ws/:id` 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 (url.pathname.match(/^\\/ws\\/([^/]+)$/)) {\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 wsMatch = url.pathname.match(/^\\/ws\\/([^/]+)$/);\n if (!wsMatch) {\n ws.close();\n return;\n }\n\n let id: string;\n try {\n id = decodeURIComponent(wsMatch[1]);\n } catch {\n ws.close(WS_CLOSE.BAD_REQUEST, 'Bad Request');\n return;\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 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\nconst isBun = !!process.versions.bun;\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(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"
14
14
  ],
15
- "mappings": "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",
16
- "debugId": "65C3425C7B57D40F64756E2164756E21",
15
+ "mappings": "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",
16
+ "debugId": "493EB9B10F4BA8D664756E2164756E21",
17
17
  "names": []
18
18
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webtty",
3
- "version": "1.8.2",
3
+ "version": "1.9.0",
4
4
  "description": "Web TTY for running CLI/TUI applications in a browser tab, across platforms",
5
5
  "license": "MIT",
6
6
  "bin": {