@supanadit/morse-web 0.2.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.
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/cli.ts"],
4
+ "sourcesContent": ["/**\n * `morse` \u2014 lifecycle CLI for the standalone browser host.\n *\n * This file owns only the *process* concerns: daemonize, pidfile, log file,\n * health probe and the `stop`/`restart`/`status`/`logs` commands. Everything\n * protocol- and agent-shaped lives in `dist/server.mjs`, which is the same\n * NestJS composition root used in development \u2014 the CLI never reimplements it.\n *\n * morse start # daemonize, print the URL, exit \u2014 the terminal may close\n * morse start -f # stay in the foreground (systemd Type=simple)\n * morse status|stop|restart|logs\n */\nimport { spawn } from 'node:child_process';\nimport { createServer } from 'node:net';\nimport {\n existsSync,\n mkdirSync,\n openSync,\n readFileSync,\n rmSync,\n writeFileSync,\n} from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst CLI_DIR = dirname(fileURLToPath(import.meta.url));\nconst PACKAGE_ROOT = join(CLI_DIR, '..');\nconst SERVER_ENTRY = join(CLI_DIR, 'server.mjs');\nconst UI_DIR = join(CLI_DIR, 'ui');\nconst PACKAGE_JSON = join(PACKAGE_ROOT, 'package.json');\n\n/** Data directory: state, logs and (by default) nothing else. */\nconst MORSE_HOME = process.env.MORSE_HOME?.trim() || join(homedir(), '.morse');\nconst STATE_FILE = join(MORSE_HOME, 'server.json');\nconst LOG_DIR = join(MORSE_HOME, 'logs');\nconst LOG_FILE = join(LOG_DIR, 'server.log');\n\nconst DEFAULT_PORT = 4399;\nconst DEFAULT_HOST = '127.0.0.1';\nconst HEALTH_PATH = '/api/health';\n\ninterface ServerState {\n pid: number;\n port: number;\n host: string;\n url: string;\n workspace: string;\n startedAt: string;\n logFile: string;\n}\n\ninterface Options {\n command: string;\n port?: number;\n host?: string;\n workspace?: string;\n projects?: string;\n lan: boolean;\n foreground: boolean;\n open: boolean;\n follow: boolean;\n force: boolean;\n json: boolean;\n help: boolean;\n version: boolean;\n}\n\n// --- tiny terminal helpers (no dependencies) ---------------------------------\n\nconst colorEnabled = process.stdout.isTTY === true && process.env.NO_COLOR === undefined;\nconst paint = (code: number) => (text: string) =>\n colorEnabled ? `\\u001b[${code}m${text}\\u001b[0m` : text;\nconst dim = paint(2);\nconst bold = paint(1);\nconst cyan = paint(36);\nconst green = paint(32);\nconst yellow = paint(33);\nconst red = paint(31);\n\n// --- argument parsing --------------------------------------------------------\n\nconst COMMANDS = new Set([\n 'start',\n 'serve',\n 'stop',\n 'restart',\n 'status',\n 'logs',\n 'help',\n 'version',\n]);\n\nfunction parseArgs(argv: string[]): Options {\n const options: Options = {\n command: 'start',\n lan: false,\n foreground: false,\n open: false,\n follow: false,\n force: false,\n json: false,\n help: false,\n version: false,\n };\n let commandSeen = false;\n\n for (let index = 0; index < argv.length; index += 1) {\n const arg = argv[index];\n const [flag, inline] = splitFlag(arg);\n const takeValue = (): string => {\n if (inline !== undefined) return inline;\n const next = argv[index + 1];\n if (next === undefined || next.startsWith('-')) {\n throw new Error(`${flag} needs a value`);\n }\n index += 1;\n return next;\n };\n\n switch (flag) {\n case 'start':\n case 'serve':\n case 'stop':\n case 'restart':\n case 'status':\n case 'logs':\n case 'help':\n case 'version':\n options.command = flag;\n commandSeen = true;\n break;\n case '-p':\n case '--port':\n options.port = parsePort(takeValue(), flag);\n break;\n case '--host':\n case '--hostname':\n options.host = takeValue();\n break;\n case '--workspace':\n case '--cwd':\n options.workspace = takeValue();\n break;\n case '--projects':\n options.projects = takeValue();\n break;\n case '--lan':\n options.lan = true;\n break;\n case '-f':\n case '--foreground':\n case '--no-daemon':\n options.foreground = true;\n break;\n case '--open':\n options.open = true;\n break;\n case '--follow':\n options.follow = true;\n break;\n case '--force':\n options.force = true;\n break;\n case '--json':\n options.json = true;\n break;\n case '-h':\n case '--help':\n options.help = true;\n break;\n case '-v':\n case '--version':\n options.version = true;\n break;\n default:\n if (!arg.startsWith('-') && !commandSeen) {\n options.command = arg;\n commandSeen = true;\n break;\n }\n throw new Error(`Unknown option \"${arg}\". Run \\`morse help\\`.`);\n }\n }\n\n if (!COMMANDS.has(options.command)) {\n throw new Error(`Unknown command \"${options.command}\". Run \\`morse help\\`.`);\n }\n return options;\n}\n\nfunction splitFlag(arg: string): [string, string | undefined] {\n if (!arg.startsWith('--')) return [arg, undefined];\n const equals = arg.indexOf('=');\n return equals === -1 ? [arg, undefined] : [arg.slice(0, equals), arg.slice(equals + 1)];\n}\n\nfunction parsePort(value: string, flag: string): number {\n const port = Number.parseInt(value, 10);\n if (!Number.isInteger(port) || port < 1 || port > 65535) {\n throw new Error(`${flag} expects a port between 1 and 65535`);\n }\n return port;\n}\n\n// --- state file --------------------------------------------------------------\n\nfunction readState(): ServerState | undefined {\n try {\n const parsed = JSON.parse(readFileSync(STATE_FILE, 'utf8')) as Partial<ServerState>;\n if (typeof parsed.pid !== 'number' || typeof parsed.port !== 'number') return undefined;\n return {\n pid: parsed.pid,\n port: parsed.port,\n host: parsed.host ?? DEFAULT_HOST,\n url: parsed.url ?? `http://${DEFAULT_HOST}:${parsed.port}/`,\n workspace: parsed.workspace ?? process.cwd(),\n startedAt: parsed.startedAt ?? new Date().toISOString(),\n logFile: parsed.logFile ?? LOG_FILE,\n };\n } catch {\n return undefined;\n }\n}\n\nfunction writeState(state: ServerState): void {\n mkdirSync(MORSE_HOME, { recursive: true });\n writeFileSync(STATE_FILE, `${JSON.stringify(state, null, 2)}\\n`, 'utf8');\n}\n\nfunction clearState(): void {\n rmSync(STATE_FILE, { force: true });\n}\n\nfunction isAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n // EPERM means the process exists but belongs to another user.\n return (error as NodeJS.ErrnoException).code === 'EPERM';\n }\n}\n\n// --- networking --------------------------------------------------------------\n\nfunction displayHost(host: string): string {\n return host === '0.0.0.0' || host === '::' ? DEFAULT_HOST : host;\n}\n\nfunction healthUrl(state: Pick<ServerState, 'host' | 'port'>): string {\n return `http://${displayHost(state.host)}:${state.port}${HEALTH_PATH}`;\n}\n\nasync function probe(url: string): Promise<boolean> {\n try {\n const response = await fetch(url, { signal: AbortSignal.timeout(1500) });\n return response.ok;\n } catch {\n return false;\n }\n}\n\nasync function waitForHealth(url: string, timeoutMs: number): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n if (await probe(url)) return true;\n if (Date.now() >= deadline) return false;\n await sleep(250);\n }\n}\n\nasync function isPortFree(port: number): Promise<boolean> {\n return new Promise((resolvePort) => {\n const server = createServer();\n server.once('error', () => resolvePort(false));\n server.once('listening', () => server.close(() => resolvePort(true)));\n server.listen(port, DEFAULT_HOST);\n });\n}\n\nasync function findFreePort(start: number): Promise<number> {\n for (let port = start; port < start + 100; port += 1) {\n if (await isPortFree(port)) return port;\n }\n throw new Error(`No free port found in ${start}..${start + 99}`);\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolveSleep) => setTimeout(resolveSleep, ms));\n}\n\n// --- commands ----------------------------------------------------------------\n\nasync function start(options: Options): Promise<number> {\n const existing = readState();\n if (existing && isAlive(existing.pid)) {\n if (!options.force) {\n console.log(`${yellow('Morse is already running')} (PID ${existing.pid})`);\n console.log(` visit: ${cyan(existing.url)}`);\n console.log(` stop: ${dim(`morse stop`)}`);\n return 0;\n }\n await stop({ quiet: true });\n }\n clearState();\n\n const workspace = options.workspace ? resolve(options.workspace) : process.cwd();\n if (!existsSync(workspace)) {\n throw new Error(`Workspace does not exist: ${workspace}`);\n }\n\n const envPort = process.env.MORSE_PORT ? Number.parseInt(process.env.MORSE_PORT, 10) : NaN;\n let port = options.port ?? (Number.isInteger(envPort) ? envPort : undefined) ?? DEFAULT_PORT;\n if (options.port === undefined && !Number.isInteger(envPort)) {\n // Nobody asked for a specific port: avoid clobbering whatever already\n // listens on the default one (another Morse, a dev server, ...).\n port = await findFreePort(DEFAULT_PORT);\n } else if (!(await isPortFree(port))) {\n throw new Error(`Port ${port} is already in use. Pass --port to pick another one.`);\n }\n\n const host = options.lan ? '0.0.0.0' : (options.host ?? process.env.MORSE_HOST ?? DEFAULT_HOST);\n const env = buildEnv(options, port, host, workspace);\n\n if (options.foreground) {\n return startForeground(env, port, host, workspace);\n }\n return startDaemon(env, port, host, workspace, options);\n}\n\nfunction buildEnv(options: Options, port: number, host: string, workspace: string): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = {\n ...process.env,\n MORSE_HOST: host,\n MORSE_PORT: String(port),\n MORSE_UI_DIR: UI_DIR,\n MORSE_WORKSPACE: workspace,\n };\n if (options.projects) env.MORSE_PROJECTS = options.projects;\n return env;\n}\n\nasync function startDaemon(\n env: NodeJS.ProcessEnv,\n port: number,\n host: string,\n workspace: string,\n options: Options,\n): Promise<number> {\n mkdirSync(LOG_DIR, { recursive: true });\n writeFileSync(\n LOG_FILE,\n `\\n=== morse start ${new Date().toISOString()} (port ${port}) ===\\n`,\n { flag: 'a' },\n );\n const logFd = openSync(LOG_FILE, 'a');\n\n const child = spawn(process.execPath, [SERVER_ENTRY], {\n cwd: workspace,\n env,\n detached: true,\n // The daemon owns no terminal: closing the shell must not send it SIGHUP.\n stdio: ['ignore', logFd, logFd],\n });\n child.unref();\n\n const state: ServerState = {\n pid: child.pid ?? -1,\n port,\n host,\n url: `http://${displayHost(host)}:${port}/`,\n workspace,\n startedAt: new Date().toISOString(),\n logFile: LOG_FILE,\n };\n writeState(state);\n\n const healthy = await waitForHealth(healthUrl(state), 25_000);\n if (!healthy) {\n console.error(red('Morse failed to start.'));\n if (!isAlive(state.pid)) {\n clearState();\n console.error(dim(`Last lines of ${LOG_FILE}:`));\n console.error(tailLog(40));\n return 1;\n }\n console.error(dim('The process is alive but did not answer the health probe.'));\n console.error(dim(`Logs: morse logs`));\n return 1;\n }\n\n console.log();\n console.log(` ${green('\u25B2')} ${bold('Morse started')}`);\n console.log(` \u2502`);\n console.log(` \u25C6 port ${port} (PID: ${state.pid})`);\n console.log(` \u2502`);\n console.log(` \u25CF visit: ${cyan(state.url)}`);\n console.log(` \u25CF logs: ${dim('morse logs')}`);\n console.log(` \u2502`);\n console.log(` \u2514 daemon running \u2014 the terminal can be closed`);\n console.log();\n\n if (options.open) openBrowser(state.url);\n return 0;\n}\n\nasync function startForeground(\n env: NodeJS.ProcessEnv,\n port: number,\n host: string,\n workspace: string,\n): Promise<number> {\n const child = spawn(process.execPath, [SERVER_ENTRY], { cwd: workspace, env, stdio: 'inherit' });\n writeState({\n pid: child.pid ?? -1,\n port,\n host,\n url: `http://${displayHost(host)}:${port}/`,\n workspace,\n startedAt: new Date().toISOString(),\n logFile: LOG_FILE,\n });\n\n const forward = (signal: NodeJS.Signals): void => {\n if (child.pid !== undefined) child.kill(signal);\n };\n process.on('SIGINT', () => forward('SIGINT'));\n process.on('SIGTERM', () => forward('SIGTERM'));\n\n const code = await new Promise<number>((resolveExit) => {\n child.on('exit', (exitCode) => resolveExit(exitCode ?? 0));\n });\n clearState();\n return code;\n}\n\nasync function stop(options: { quiet?: boolean } = {}): Promise<number> {\n const state = readState();\n if (!state || !isAlive(state.pid)) {\n clearState();\n if (!options.quiet) console.log(dim('Morse is not running.'));\n return 0;\n }\n\n const { pid } = state;\n process.kill(pid, 'SIGTERM');\n if (!(await waitForExit(pid, 5000))) {\n process.kill(pid, 'SIGKILL');\n await waitForExit(pid, 2000);\n }\n clearState();\n if (!options.quiet) {\n console.log(`${green('\u25CF')} Morse stopped (PID ${pid}).`);\n }\n return 0;\n}\n\nasync function waitForExit(pid: number, timeoutMs: number): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n if (!isAlive(pid)) return true;\n await sleep(100);\n }\n return !isAlive(pid);\n}\n\nfunction status(options: Options): number {\n const state = readState();\n if (!state || !isAlive(state.pid)) {\n clearState();\n if (options.json) {\n console.log(JSON.stringify({ running: false }));\n } else {\n console.log(dim('Morse is not running.'));\n }\n return 1;\n }\n\n const uptimeMs = Date.now() - new Date(state.startedAt).getTime();\n if (options.json) {\n console.log(JSON.stringify({ running: true, ...state, uptimeMs }));\n return 0;\n }\n console.log(`${green('\u25CF')} Morse is running (PID ${state.pid})`);\n console.log(` visit: ${cyan(state.url)}`);\n console.log(` workspace: ${state.workspace}`);\n console.log(` uptime: ${formatUptime(uptimeMs)}`);\n console.log(` logs: ${dim(state.logFile)}`);\n return 0;\n}\n\nfunction formatUptime(uptimeMs: number): string {\n const seconds = Math.max(0, Math.floor(uptimeMs / 1000));\n const hours = Math.floor(seconds / 3600);\n const minutes = Math.floor((seconds % 3600) / 60);\n if (hours > 0) return `${hours}h ${minutes}m`;\n if (minutes > 0) return `${minutes}m ${seconds % 60}s`;\n return `${seconds}s`;\n}\n\nfunction tailLog(linesToShow: number): string {\n if (!existsSync(LOG_FILE)) return '(no log yet)';\n const lines = readFileSync(LOG_FILE, 'utf8').split('\\n');\n return lines.slice(-linesToShow).join('\\n');\n}\n\nasync function logs(options: Options): Promise<number> {\n if (!existsSync(LOG_FILE)) {\n console.log(dim(`No log file yet (${LOG_FILE}).`));\n return 0;\n }\n process.stdout.write(tailLog(200));\n if (!options.follow) return 0;\n\n // Simple, dependency-free `tail -f` so it works the same on every platform.\n let seen = readFileSync(LOG_FILE, 'utf8').length;\n await new Promise<void>((resolveFollow) => {\n const timer = setInterval(() => {\n try {\n const current = readFileSync(LOG_FILE, 'utf8');\n if (current.length > seen) {\n process.stdout.write(current.slice(seen));\n seen = current.length;\n } else if (current.length < seen) {\n seen = current.length;\n }\n } catch {\n /* the log was rotated away; keep waiting */\n }\n }, 400);\n const stopFollowing = (): void => {\n clearInterval(timer);\n resolveFollow();\n };\n process.on('SIGINT', stopFollowing);\n process.on('SIGTERM', stopFollowing);\n });\n return 0;\n}\n\nfunction openBrowser(url: string): void {\n const command =\n process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'cmd' : 'xdg-open';\n const args = process.platform === 'win32' ? ['/c', 'start', '', url] : [url];\n try {\n const child = spawn(command, args, { stdio: 'ignore', detached: true });\n child.unref();\n } catch {\n /* opening a browser is best-effort */\n }\n}\n\nfunction readVersion(): string {\n try {\n return (JSON.parse(readFileSync(PACKAGE_JSON, 'utf8')) as { version?: string }).version ?? '0.0.0';\n } catch {\n return '0.0.0';\n }\n}\n\nfunction printHelp(): void {\n const name = `@supanadit/morse-web`;\n console.log(`\n ${bold('Morse')} \u2014 the Comfortable Pi Interface, in your browser (${name})\n\n ${bold('USAGE')}\n morse [COMMAND] [OPTIONS]\n\n ${bold('COMMANDS')}\n start Start the web server in the background (default)\n stop Stop the running server\n restart Stop, then start the server\n status Show whether the server is running\n logs Print the server log (--follow to tail)\n help Show this help\n version Print the version\n\n ${bold('OPTIONS')}\n -p, --port <n> Web server port (default: ${DEFAULT_PORT}, next free if taken)\n --host <addr> Bind address (default: ${DEFAULT_HOST})\n --lan Bind 0.0.0.0 so the LAN can reach it\n -f, --foreground Run in the foreground (systemd Type=simple)\n --workspace <dir> Directory the agent works in (default: the current one)\n --projects <list> Comma/colon separated list of roots the agent may open\n --open Open the browser once the server is healthy\n --force Replace a server this CLI already started\n --json Machine-readable output (status)\n --follow Follow the log file (logs)\n -h, --help Show this help\n -v, --version Print the version\n\n ${bold('ENVIRONMENT')}\n MORSE_HOME Data directory (default: ~/.morse)\n MORSE_PORT Default port\n MORSE_HOST Default bind address\n MORSE_WORKSPACE Workspace the agent runs in\n MORSE_PROJECTS Roots the agent may open\n MORSE_HOT_SESSIONS How many pi processes stay alive (default: 4)\n MORSE_PI_PATH Path to the pi executable (default: pi on PATH)\n MORSE_PI_ENTRY Run a pi RPC entry with node instead of the binary\n\n ${bold('EXAMPLES')}\n morse # start the daemon on ${DEFAULT_PORT}\n morse start --port 8080 # a specific port\n morse --lan --port 3002 # reachable on the LAN\n morse start --open # start and open the browser\n morse logs --follow # tail the server log\n morse stop # stop the daemon\n`);\n}\n\n// --- entry point -------------------------------------------------------------\n\nexport async function run(argv: string[]): Promise<number> {\n const options = parseArgs(argv);\n\n if (options.version || options.command === 'version') {\n console.log(readVersion());\n return 0;\n }\n if (options.help || options.command === 'help') {\n printHelp();\n return 0;\n }\n\n switch (options.command) {\n case 'start':\n case 'serve':\n return start(options);\n case 'stop':\n return stop();\n case 'restart':\n await stop({ quiet: true });\n return start(options);\n case 'status':\n return status(options);\n case 'logs':\n return logs(options);\n default:\n printHelp();\n return 0;\n }\n}\n\nrun(process.argv.slice(2))\n .then((code) => {\n process.exitCode = code;\n })\n .catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`${red('morse:')} ${message}`);\n process.exitCode = 1;\n });\n"],
5
+ "mappings": "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",
6
+ "names": ["start"]
7
+ }