{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n/**\n * dreb-dashboard — launch the dreb web dashboard server.\n *\n * Modes (exactly two, no LAN mode):\n *   default        loopback bind (127.0.0.1), no auth, no Tailscale needed\n *   --remote       requires Tailscale; binds all interfaces but every request\n *                  passes identity allowlist + pairing code + device cookies\n *\n * Usage:\n *   dreb-dashboard [--port 5343] [--remote --allow me@example.com [--allow ...]]\n */\n\nimport { existsSync, readFileSync, watch } from \"node:fs\";\nimport { createServer as createHttpServer, type Server as HttpServer } from \"node:http\";\nimport { createServer as createHttpsServer, type Server as HttpsServer } from \"node:https\";\nimport { homedir } from \"node:os\";\nimport { basename, dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { DashboardAuth } from \"./server/auth.js\";\nimport { DashboardImageService } from \"./server/dashboard-images.js\";\nimport { ImagePreviewWorker } from \"./server/image-preview.js\";\nimport { FilePairingStorage, loadOrCreateDashboardSecret } from \"./server/pairing-storage.js\";\nimport { RuntimePool } from \"./server/runtime-pool.js\";\nimport { createDashboardServer } from \"./server/server.js\";\n\nexport { DashboardAuth, TailscaleStatusResolver, TailscaleWhoisResolver } from \"./server/auth.js\";\nexport { DashboardImageService } from \"./server/dashboard-images.js\";\nexport { EventHub } from \"./server/event-hub.js\";\nexport { canonicalizePath, FileApi, resolveExistingDirectory } from \"./server/files.js\";\nexport { ImagePreviewWorker } from \"./server/image-preview.js\";\nexport { FilePairingStorage, loadOrCreateDashboardSecret } from \"./server/pairing-storage.js\";\nexport { RuntimePool, resolveDrebCliPath } from \"./server/runtime-pool.js\";\nexport { createDashboardServer, parseDeviceCookie } from \"./server/server.js\";\nexport type * from \"./shared/protocol.js\";\n\nconst DEFAULT_PORT = 5343;\n\ninterface CliArgs {\n\tport: number;\n\tremote: boolean;\n\tallow: string[];\n\thelp: boolean;\n\thttps: boolean;\n\tcert?: string;\n\tkey?: string;\n}\n\nexport function parseArgs(argv: string[]): CliArgs {\n\tconst args: CliArgs = { port: DEFAULT_PORT, remote: false, allow: [], help: false, https: false };\n\tfor (let i = 0; i < argv.length; i++) {\n\t\tconst arg = argv[i];\n\t\tif (arg === \"--port\") {\n\t\t\tconst value = argv[++i];\n\t\t\tconst port = Number.parseInt(value ?? \"\", 10);\n\t\t\tif (!Number.isInteger(port) || port < 1 || port > 65535) {\n\t\t\t\tthrow new Error(`Invalid --port value: ${value}`);\n\t\t\t}\n\t\t\targs.port = port;\n\t\t} else if (arg === \"--remote\") {\n\t\t\targs.remote = true;\n\t\t} else if (arg === \"--allow\") {\n\t\t\tconst value = argv[++i];\n\t\t\tif (!value) throw new Error(\"--allow requires an identity (Tailscale login name)\");\n\t\t\targs.allow.push(value);\n\t\t} else if (arg === \"--https\") {\n\t\t\targs.https = true;\n\t\t} else if (arg === \"--cert\") {\n\t\t\tconst value = argv[++i];\n\t\t\tif (!value) throw new Error(\"--cert requires a path to a PEM certificate file\");\n\t\t\targs.cert = value;\n\t\t} else if (arg === \"--key\") {\n\t\t\tconst value = argv[++i];\n\t\t\tif (!value) throw new Error(\"--key requires a path to a PEM private key file\");\n\t\t\targs.key = value;\n\t\t} else if (arg === \"--help\" || arg === \"-h\") {\n\t\t\targs.help = true;\n\t\t} else {\n\t\t\tthrow new Error(`Unknown argument: ${arg}`);\n\t\t}\n\t}\n\tif (args.remote && args.allow.length === 0) {\n\t\tthrow new Error(\"--remote requires at least one --allow <tailscale-login> (empty allowlist denies everyone)\");\n\t}\n\t// Native TLS. `https` is opt-in; it requires cert AND key (no silent\n\t// fallback to plain HTTP). The dashboard terminates TLS itself — no reverse\n\t// proxy, no auth-model change. `req.socket.remoteAddress` stays the real\n\t// tailnet IP, so identity resolution + allowlist + pairing keep working.\n\tif (args.https) {\n\t\tif (!args.cert) throw new Error(\"--https requires --cert <path>\");\n\t\tif (!args.key) throw new Error(\"--https requires --key <path>\");\n\t}\n\treturn args;\n}\n\n/**\n * Warning shown at startup when `--remote` binds the tailnet over plain HTTP\n * (no `--https`). Browsers treat plain HTTP over a non-loopback host as an\n * INSECURE context, so service workers and the Notification API are\n * unavailable — PWA install and mobile notifications (iOS exposes no\n * Notification API at all there) silently never work. Loopback (127.0.0.1) is\n * exempt (it counts as secure), so pure-local mode returns no warning.\n *\n * Returns the multi-line warning string, or `undefined` when TLS is enabled or\n * the server is loopback-only. Kept pure (no console) so it is unit-testable.\n */\nexport function insecureRemoteWarning(args: Pick<CliArgs, \"remote\" | \"https\">): string | undefined {\n\tif (!args.remote || args.https) return undefined;\n\treturn (\n\t\t\"⚠ warning: --remote without --https serves plain HTTP over the tailnet, which browsers treat as an\\n\" +\n\t\t\"  INSECURE context — service workers, PWA install, and mobile notifications (especially iOS) will NOT work.\\n\" +\n\t\t\"  Enable TLS for the tailnet hostname:\\n\" +\n\t\t\"    tailscale cert <host>.<tailnet>.ts.net\\n\" +\n\t\t\"    …then relaunch with --https --cert <host>.<tailnet>.ts.net.crt --key <host>.<tailnet>.ts.net.key\\n\" +\n\t\t\"  and open the dashboard via https://<host>.<tailnet>.ts.net (the hostname, not the tailnet IP).\"\n\t);\n}\n\ntype ShutdownWatcher = Pick<ReturnType<typeof watch>, \"close\">;\n\ninterface ShutdownDeps {\n\tlog: (message: string) => void;\n\tclearReloadTimer: () => void;\n\twatchers: Iterable<ShutdownWatcher>;\n\tcloseServer: () => void;\n\tstopAll: () => Promise<unknown>;\n\texit: (code: number) => void;\n}\n\nexport function createShutdown(deps: ShutdownDeps): (message: string, exitCode: number) => void {\n\tlet shuttingDown = false;\n\treturn (message: string, exitCode: number) => {\n\t\tif (shuttingDown) return;\n\t\tshuttingDown = true;\n\t\tdeps.log(message);\n\t\tdeps.clearReloadTimer();\n\t\tfor (const watcher of deps.watchers) watcher.close();\n\t\tdeps.closeServer();\n\t\tvoid deps\n\t\t\t.stopAll()\n\t\t\t.catch((err) => {\n\t\t\t\tdeps.log(`shutdown teardown failed: ${err instanceof Error ? err.message : String(err)}`);\n\t\t\t})\n\t\t\t.finally(() => deps.exit(exitCode));\n\t};\n}\n\ntype TlsOptions = { cert: string; key: string };\ntype TlsWatchFilename = string | Buffer | null;\ntype TlsWatchListener = (event: string, filename: TlsWatchFilename) => void;\ntype TlsFileWatcher = ShutdownWatcher & {\n\ton(event: \"error\", listener: (err: unknown) => void): unknown;\n};\n\ninterface TlsReloaderDeps {\n\treadTlsOptions: () => TlsOptions;\n\tsetSecureContext: (options: TlsOptions) => void;\n\tlog: (message: string) => void;\n\twarn: (message: string) => void;\n}\n\nexport function createTlsReloader(deps: TlsReloaderDeps): () => void {\n\treturn () => {\n\t\ttry {\n\t\t\tdeps.setSecureContext(deps.readTlsOptions());\n\t\t\tdeps.log(\"tls certificate reloaded\");\n\t\t} catch (err) {\n\t\t\tdeps.warn(`tls reload failed (keeping old cert): ${err instanceof Error ? err.message : String(err)}`);\n\t\t}\n\t};\n}\n\nexport function shouldReloadForFile(filename: TlsWatchFilename, certBase: string, keyBase: string): boolean {\n\tif (filename == null) return true;\n\tif (typeof filename !== \"string\") return false;\n\treturn filename === certBase || filename === keyBase;\n}\n\ninterface TlsWatchDeps {\n\tcertPath: string;\n\tkeyPath: string;\n\twatchDirectory: (directory: string, listener: TlsWatchListener) => TlsFileWatcher;\n\treloadTls: () => void;\n\twarn: (message: string) => void;\n\tdebounceMs?: number;\n}\n\ninterface TlsWatchController {\n\twatchers: TlsFileWatcher[];\n\tclearReloadTimer: () => void;\n}\n\nexport function createTlsWatchers(deps: TlsWatchDeps): TlsWatchController {\n\tlet reloadTimer: ReturnType<typeof setTimeout> | undefined;\n\tconst watchers: TlsFileWatcher[] = [];\n\tconst debounceMs = deps.debounceMs ?? 500;\n\tconst certBase = basename(deps.certPath);\n\tconst keyBase = basename(deps.keyPath);\n\tconst clearReloadTimer = () => {\n\t\tif (!reloadTimer) return;\n\t\tclearTimeout(reloadTimer);\n\t\treloadTimer = undefined;\n\t};\n\tconst scheduleReload = () => {\n\t\tclearReloadTimer();\n\t\treloadTimer = setTimeout(deps.reloadTls, debounceMs);\n\t};\n\n\tfor (const dir of new Set([dirname(deps.certPath), dirname(deps.keyPath)])) {\n\t\ttry {\n\t\t\tconst watcher = deps.watchDirectory(dir, (_event, filename) => {\n\t\t\t\t// `fs.watch` directory events on macOS (FSEvents) frequently fire\n\t\t\t\t// with `filename === null`; fall through and reload anyway.\n\t\t\t\t// On Linux (inotify) the basename is carried reliably and filters\n\t\t\t\t// out unrelated files in the parent directory.\n\t\t\t\tif (!shouldReloadForFile(filename, certBase, keyBase)) return;\n\t\t\t\tscheduleReload();\n\t\t\t});\n\t\t\twatcher.on(\"error\", (err) => {\n\t\t\t\tdeps.warn(`tls cert watch error: ${err instanceof Error ? err.message : String(err)}`);\n\t\t\t});\n\t\t\twatchers.push(watcher);\n\t\t} catch (err) {\n\t\t\tdeps.warn(`tls cert watch setup failed for ${dir}: ${err instanceof Error ? err.message : String(err)}`);\n\t\t}\n\t}\n\n\treturn { watchers, clearReloadTimer };\n}\n\nconst HELP = `dreb-dashboard — dreb web dashboard server\n\nUsage: dreb-dashboard [options]\n\nOptions:\n  --port <n>          Port to listen on (default ${DEFAULT_PORT})\n  --remote            Enable remote mode (requires Tailscale). Without this\n                      flag the server binds 127.0.0.1 only — no LAN access.\n                      Plain HTTP over the tailnet is an INSECURE context: add\n                      --https (below) or mobile PWA install + notifications\n                      (especially iOS) will not work.\n  --allow <identity>  Tailscale login name allowed to pair (repeatable;\n                      required with --remote)\n  --https             Terminate TLS on the dashboard itself (native TLS). No\n                      reverse proxy, no auth-model change. Requires --cert\n                      and --key. Mainly for --remote (loopback is already a\n                      secure context): use 'tailscale cert' files for a\n                      tailnet hostname so mobile PWAs + notifications work.\n                      NOTE: with --https the server speaks TLS only, so the\n                      host's plain-http local tab (http://127.0.0.1) stops\n                      working — use the tailnet hostname (https://...) there.\n  --cert <path>       PEM certificate file (required with --https)\n  --key <path>        PEM private key file (required with --https)\n  --help              Show this help\n`;\n\nasync function main(): Promise<void> {\n\tlet args: CliArgs;\n\ttry {\n\t\targs = parseArgs(process.argv.slice(2));\n\t} catch (err) {\n\t\tconsole.error(`error: ${err instanceof Error ? err.message : String(err)}`);\n\t\tconsole.error(HELP);\n\t\tprocess.exit(1);\n\t}\n\tif (args.help) {\n\t\tconsole.log(HELP);\n\t\treturn;\n\t}\n\n\tconst agentDir = join(homedir(), \".dreb\", \"agent\");\n\tconst auth = new DashboardAuth({\n\t\tremoteEnabled: args.remote,\n\t\tallowedIdentities: args.allow,\n\t\tstorage: new FilePairingStorage(join(agentDir, \"dashboard-pairings.json\")),\n\t\tsecret: loadOrCreateDashboardSecret(join(agentDir, \"dashboard-auth-secret\")),\n\t\tlogger: (line) => console.warn(`[dashboard-auth] ${line}`),\n\t});\n\tconst pool = new RuntimePool();\n\tconst imageService = new DashboardImageService(new ImagePreviewWorker());\n\n\t// Static client assets live next to the compiled server (dist/static).\n\tconst staticDir = join(dirname(fileURLToPath(import.meta.url)), \"static\");\n\n\t// The server's own build version (dist/index.js → ../package.json) — surfaced\n\t// in the settings footer so a stale long-running service is spottable.\n\tlet serverVersion: string | undefined;\n\ttry {\n\t\tconst pkgPath = join(dirname(fileURLToPath(import.meta.url)), \"..\", \"package.json\");\n\t\tserverVersion = JSON.parse(readFileSync(pkgPath, \"utf8\")).version;\n\t} catch {\n\t\tserverVersion = undefined;\n\t}\n\n\t// Restart hook: exit non-zero so a supervisor (systemd Restart=on-failure,\n\t// etc.) respawns the process with the freshly-built dist. Assigned the http\n\t// server below via a mutable holder so the closure can close it first.\n\t// The TLS hot-reload debounce timer and directory watchers are lifted here\n\t// too, so restart and signal shutdown can release them (otherwise they leak\n\t// across restart/exit).\n\tlet httpServer: HttpServer | HttpsServer | undefined;\n\tlet clearTlsReloadTimer = () => {};\n\tlet closeDashboard = () => imageService.close();\n\tconst tlsWatchers: TlsFileWatcher[] = [];\n\tconst beginShutdown = createShutdown({\n\t\tlog: (message) => console.log(message),\n\t\tclearReloadTimer: () => clearTlsReloadTimer(),\n\t\twatchers: tlsWatchers,\n\t\tcloseServer: () => httpServer?.close(),\n\t\tstopAll: () => Promise.all([pool.stopAll(), closeDashboard()]),\n\t\texit: (code) => process.exit(code),\n\t});\n\tconst onRestart = () => beginShutdown(\"restart requested — exiting for supervisor to respawn\", 1);\n\n\tconst { SessionManager } = await import(\"@dreb/coding-agent\");\n\tconst app = createDashboardServer({\n\t\tauth,\n\t\tpool,\n\t\timageService,\n\t\tstaticDir: existsSync(staticDir) ? staticDir : undefined,\n\t\tserverVersion,\n\t\tonRestart,\n\t\tlistAllSessions: () => SessionManager.listAll(),\n\t\tdeleteSession: async (path: string) => {\n\t\t\tconst result = await SessionManager.deleteSession(path, {});\n\t\t\tif (!result.ok) throw new Error(result.error ?? \"Unknown deletion error\");\n\t\t\treturn { method: result.method };\n\t\t},\n\t});\n\tcloseDashboard = () => app.closeDashboard();\n\n\tconst host = args.remote ? \"0.0.0.0\" : \"127.0.0.1\";\n\tconst scheme = args.https ? \"https\" : \"http\";\n\n\t// Native TLS: the dashboard terminates TLS itself (no reverse proxy). The\n\t// auth model is unchanged — `req.socket.remoteAddress` is still the real\n\t// tailnet IP, so Tailscale identity resolution + allowlist + pairing keep\n\t// working. Local mode never sets --https (loopback is already a secure\n\t// context), but it's allowed if the operator wants it.\n\tlet server: HttpServer | HttpsServer;\n\tif (args.https && args.cert && args.key) {\n\t\tconst readTlsOptions = () => ({\n\t\t\tcert: readFileSync(args.cert!, \"utf-8\"),\n\t\t\tkey: readFileSync(args.key!, \"utf-8\"),\n\t\t});\n\t\tlet tlsOptions: TlsOptions;\n\t\ttry {\n\t\t\ttlsOptions = readTlsOptions();\n\t\t} catch (err) {\n\t\t\tconsole.error(`error: failed to read TLS cert/key: ${err instanceof Error ? err.message : String(err)}`);\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tconst httpsServer = createHttpsServer(tlsOptions, app);\n\t\t// Hot-reload the cert on file change (zero-downtime renewal — e.g. a\n\t\t// systemd timer rewrites the `tailscale cert` files daily). New\n\t\t// connections pick up the new cert; existing ones finish on the old.\n\t\tconst reloadTls = createTlsReloader({\n\t\t\treadTlsOptions,\n\t\t\tsetSecureContext: (options) => httpsServer.setSecureContext(options),\n\t\t\tlog: (message) => console.log(message),\n\t\t\twarn: (message) => console.warn(message),\n\t\t});\n\t\t// Debounce: cert + key are often rewritten in quick succession. Watch\n\t\t// the PARENT DIRECTORY (not each file) and filter by filename: `fs.watch`\n\t\t// follows the inode, so an atomic renewal (write temp + rename(2))\n\t\t// replaces the inode and a file-level watcher goes silent on the new\n\t\t// file after the first reload. A directory watcher survives renames and\n\t\t// reports the changed basename via the `filename` callback argument.\n\t\tconst tlsWatchController = createTlsWatchers({\n\t\t\tcertPath: args.cert,\n\t\t\tkeyPath: args.key,\n\t\t\twatchDirectory: (dir, listener) => watch(dir, listener),\n\t\t\treloadTls,\n\t\t\twarn: (message) => console.warn(message),\n\t\t});\n\t\tclearTlsReloadTimer = tlsWatchController.clearReloadTimer;\n\t\ttlsWatchers.push(...tlsWatchController.watchers);\n\t\tserver = httpsServer;\n\t} else {\n\t\tserver = createHttpServer(app);\n\t}\n\tserver.listen(args.port, host, () => {\n\t\tconsole.log(\n\t\t\t`dreb dashboard listening on ${scheme}://${host === \"0.0.0.0\" ? \"<tailscale-ip>\" : host}:${args.port}`,\n\t\t);\n\t\tif (args.remote) {\n\t\t\tconsole.log(`remote mode: allowed identities = ${args.allow.join(\", \")}`);\n\t\t\tconst { code, expiresInMs } = auth.currentPairingCode();\n\t\t\tconsole.log(\n\t\t\t\t`pairing code: ${code} (rotates every 30s; current code rolls in ${Math.ceil(expiresInMs / 1000)}s)`,\n\t\t\t);\n\t\t\tconsole.log(\"new devices enter this code; see it live in dashboard Settings on the host machine\");\n\t\t} else {\n\t\t\tconsole.log(\"local mode: loopback only — use --remote for Tailscale access\");\n\t\t}\n\t\tif (args.https) {\n\t\t\tconsole.log(\"tls: native HTTPS enabled (cert + key hot-reload on file change)\");\n\t\t} else {\n\t\t\tconst insecureWarning = insecureRemoteWarning(args);\n\t\t\tif (insecureWarning) console.warn(insecureWarning);\n\t\t}\n\t});\n\thttpServer = server;\n\n\tconst shutdown = () => beginShutdown(\"shutting down…\", 0);\n\tprocess.on(\"SIGINT\", shutdown);\n\tprocess.on(\"SIGTERM\", shutdown);\n}\n\n// Only run when executed directly (not when imported for the library exports).\nconst entryPath = process.argv[1];\nif (entryPath && import.meta.url === new URL(`file://${entryPath}`).href) {\n\tmain().catch((err) => {\n\t\tconsole.error(`fatal: ${err instanceof Error ? err.message : String(err)}`);\n\t\tprocess.exit(1);\n\t});\n}\n"]}