remobi 0.2.1 → 0.2.3

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/CHANGELOG.md CHANGED
@@ -1,49 +1,35 @@
1
- ## [0.2.1](https://github.com/connorads/remobi/compare/v0.2.0...v0.2.1) (2026-03-15)
1
+ ## [0.2.3](https://github.com/connorads/remobi/compare/v0.2.2...v0.2.3) (2026-03-16)
2
2
 
3
3
 
4
4
  ### Bug Fixes
5
5
 
6
- * resolve symlink in entry guard so npx execution works ([4eab06d](https://github.com/connorads/remobi/commit/4eab06dcb2144788a65d2cd5e503b5e3e27b350c))
7
-
8
- # [0.2.0](https://github.com/connorads/remobi/compare/v0.1.0...v0.2.0) (2026-03-15)
6
+ * **serve:** default remobi serve to localhost ([36eb0ff](https://github.com/connorads/remobi/commit/36eb0ff54b780b1a4bf336051af6d80f00f9f1cf))
9
7
 
8
+ ## [0.2.2](https://github.com/connorads/remobi/compare/v0.2.1...v0.2.2) (2026-03-16)
10
9
 
11
10
  ### Bug Fixes
12
11
 
13
- * **ci:** add npm to mise.toml for OIDC trusted publishing ([44bb745](https://github.com/connorads/remobi/commit/44bb7452d0d9e3496bacc4aaf5a63f2236a8f80f))
14
- * exclude package.json from Biome formatter ([044183f](https://github.com/connorads/remobi/commit/044183fd636fe56eb17f1cd650338c85be46ce5e))
15
- * remove leading ./ from bin path for npm 11 compatibility ([33ba38d](https://github.com/connorads/remobi/commit/33ba38daab05224fc6eeb28402bb9860c36873d2))
16
- * remove redundant checks from prepublishOnly ([efdb742](https://github.com/connorads/remobi/commit/efdb742087842ba131ed704e3e29676aa672ab87))
17
-
18
-
19
- ### Features
20
-
21
- * add pixel R> logo and integrate across project ([44b238a](https://github.com/connorads/remobi/commit/44b238aa7909cec2c408acb5bde32f9f2c645e26))
22
-
23
- ## [0.2.2](https://github.com/connorads/remobi/compare/v0.2.1...v0.2.2) (2026-03-15)
12
+ * guard process.argv[1] for strict index access ([32eabce](https://github.com/connorads/remobi/commit/32eabce2a86651aa101c4756f5b59935c49e8154))
24
13
 
14
+ ## [0.2.1](https://github.com/connorads/remobi/compare/v0.2.0...v0.2.1) (2026-03-15)
25
15
 
26
16
  ### Bug Fixes
27
17
 
28
- * **ci:** add npm to mise.toml for OIDC trusted publishing ([44bb745](https://github.com/connorads/remobi/commit/44bb7452d0d9e3496bacc4aaf5a63f2236a8f80f))
29
-
30
- ## [0.2.1](https://github.com/connorads/remobi/compare/v0.2.0...v0.2.1) (2026-03-15)
18
+ * resolve symlink in entry guard so npx execution works ([4eab06d](https://github.com/connorads/remobi/commit/4eab06dcb2144788a65d2cd5e503b5e3e27b350c))
31
19
 
20
+ ## [0.2.0](https://github.com/connorads/remobi/compare/v0.1.0...v0.2.0) (2026-03-15)
32
21
 
33
22
  ### Bug Fixes
34
23
 
24
+ * **ci:** add npm to mise.toml for OIDC trusted publishing ([44bb745](https://github.com/connorads/remobi/commit/44bb7452d0d9e3496bacc4aaf5a63f2236a8f80f))
35
25
  * exclude package.json from Biome formatter ([044183f](https://github.com/connorads/remobi/commit/044183fd636fe56eb17f1cd650338c85be46ce5e))
26
+ * remove leading ./ from bin path for npm 11 compatibility ([33ba38d](https://github.com/connorads/remobi/commit/33ba38daab05224fc6eeb28402bb9860c36873d2))
36
27
  * remove redundant checks from prepublishOnly ([efdb742](https://github.com/connorads/remobi/commit/efdb742087842ba131ed704e3e29676aa672ab87))
37
28
 
38
- # [0.2.0](https://github.com/connorads/remobi/compare/v0.1.0...v0.2.0) (2026-03-15)
39
-
40
-
41
29
  ### Features
42
30
 
43
31
  * add pixel R> logo and integrate across project ([44b238a](https://github.com/connorads/remobi/commit/44b238aa7909cec2c408acb5bde32f9f2c645e26))
44
32
 
45
- # Changelog
46
-
47
33
  ## 0.1.0 — 2026-03-15
48
34
 
49
35
  - Changed: overlay is now pre-built as an IIFE during `build:dist` — faster `remobi serve` startup (no runtime esbuild), smaller install footprint (esbuild moved to devDependencies). Dev mode falls back to esbuild-from-source when `dist/overlay.iife.js` is absent.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 Connor
3
+ Copyright (c) 2026 Connor
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -32,7 +32,7 @@ Running coding agents in tmux? remobi lets you monitor and control them from you
32
32
  - **Pinch to zoom** — resize text like every other app on your phone
33
33
  - **Install to your home screen** — standalone PWA, looks and feels native
34
34
  - **Config-driven** — your buttons, your gestures, your layout. Or let an AI agent configure it for you
35
- - **Self-hosted** — your data never leaves your network. Bring your own tunnel (Tailscale, Cloudflare, ngrok)
35
+ - **Self-hosted** — local-first by default. Bring your own access layer (Tailscale, Cloudflare, ngrok)
36
36
 
37
37
  ## Requirements
38
38
 
@@ -48,20 +48,31 @@ remobi uses standard ttyd flags (`--writable`, `-t`, `-i`) and should work with
48
48
  # 1. Install
49
49
  npm install -g remobi
50
50
 
51
- # 2. Start (builds overlay, manages ttyd, serves with PWA support)
51
+ # 2. Start (builds overlay, manages ttyd, serves with PWA support on 127.0.0.1:7681)
52
52
  remobi serve
53
53
  ```
54
54
 
55
55
  For local development, use `pnpm link --global` from the repo root instead of `npm install -g remobi`.
56
56
 
57
- Open `http://localhost:7681` on your phone. Add to Home Screen for an app-like experience.
57
+ Open `http://localhost:7681` on the same machine to verify it works. For phone access, put a trusted proxy/tunnel in front of it, for example [Tailscale Serve](docs/guides/tailscale-serve.md).
58
+
59
+ ## Security model
60
+
61
+ `remobi` is a remote-control surface for your terminal. Anyone who can reach it can drive the tmux session with your user privileges.
62
+
63
+ - `remobi serve` binds to `127.0.0.1` by default.
64
+ - The inner `ttyd` process also binds to `127.0.0.1`.
65
+ - There is no built-in login, password, or ACL in remobi itself.
66
+ - Safe default: keep it on localhost and publish it through a trusted layer like Tailscale Serve.
67
+ - If you use `remobi serve --host 0.0.0.0`, you are exposing terminal control to your LAN/whatever can route to that port. Do that only if you intentionally want direct network exposure and have separate network controls in place.
58
68
 
59
69
  ## CLI reference
60
70
 
61
- ```
62
- remobi serve [--config <path>] [--port <n>] [-- <command...>]
71
+ ```text
72
+ remobi serve [--config <path>] [--port <n>] [--host <addr>] [-- <command...>]
63
73
  Build overlay in memory, manage ttyd, serve with PWA support.
64
- Default port: 7681. Default command: tmux new-session -A -s main
74
+ Default host: 127.0.0.1. Default port: 7681. Default command: tmux new-session -A -s main
75
+ Example: remobi serve --host 0.0.0.0 --port 8080
65
76
  Example: remobi serve --port 8080 -- tmux new -As dev
66
77
 
67
78
  remobi build [--config <path>] [--output <path>] [--dry-run]
package/dist/cli.mjs CHANGED
@@ -49,6 +49,7 @@ function parseCliArgs(args) {
49
49
  let outputPath;
50
50
  let dryRun = false;
51
51
  let port;
52
+ let host;
52
53
  let noSleep = false;
53
54
  let trailingCommand = [];
54
55
  for (let index = 1; index < args.length; index++) {
@@ -127,6 +128,19 @@ function parseCliArgs(args) {
127
128
  index++;
128
129
  continue;
129
130
  }
131
+ if (arg === "--host") {
132
+ if (commandToken !== "serve") return {
133
+ ok: false,
134
+ error: `${arg} is only valid for 'serve'`
135
+ };
136
+ if (isMissingOptionValue(nextArg)) return {
137
+ ok: false,
138
+ error: "Missing value for --host"
139
+ };
140
+ host = nextArg;
141
+ index++;
142
+ continue;
143
+ }
130
144
  if (arg === "--no-sleep") {
131
145
  if (commandToken !== "serve") return {
132
146
  ok: false,
@@ -152,6 +166,7 @@ function parseCliArgs(args) {
152
166
  outputPath,
153
167
  dryRun,
154
168
  port,
169
+ host,
155
170
  noSleep,
156
171
  command_: trailingCommand
157
172
  }
@@ -482,6 +497,7 @@ function manifestToJson(name, pwa) {
482
497
  //#endregion
483
498
  //#region src/serve.ts
484
499
  const DEFAULT_PORT = 7681;
500
+ const DEFAULT_HOST = "127.0.0.1";
485
501
  const DEFAULT_COMMAND = [
486
502
  "tmux",
487
503
  "new-session",
@@ -499,6 +515,56 @@ function findIconsDir() {
499
515
  return resolve(import.meta.dirname, "pwa/icons");
500
516
  }
501
517
  const ICONS_DIR = findIconsDir();
518
+ const RESPONSE_SECURITY_HEADERS = {
519
+ "content-security-policy": "frame-ancestors 'none'; base-uri 'none'; form-action 'self'; object-src 'none'",
520
+ "x-frame-options": "DENY",
521
+ "x-content-type-options": "nosniff",
522
+ "referrer-policy": "no-referrer",
523
+ "cross-origin-resource-policy": "same-origin",
524
+ "permissions-policy": "camera=(), microphone=(), geolocation=()"
525
+ };
526
+ const STRIPPED_PROXY_REQUEST_HEADERS = new Set([
527
+ "connection",
528
+ "content-length",
529
+ "host",
530
+ "keep-alive",
531
+ "origin",
532
+ "proxy-authenticate",
533
+ "proxy-authorization",
534
+ "te",
535
+ "trailer",
536
+ "transfer-encoding",
537
+ "upgrade"
538
+ ]);
539
+ function isLoopbackHost(host) {
540
+ return host === "127.0.0.1" || host === "::1" || host === "localhost";
541
+ }
542
+ function isAllowedWebSocketOrigin(originHeader, hostHeader) {
543
+ if (originHeader === void 0) return true;
544
+ if (hostHeader === void 0) return false;
545
+ try {
546
+ return new URL(originHeader).host === hostHeader;
547
+ } catch {
548
+ return false;
549
+ }
550
+ }
551
+ function buildProxyRequestHeaders(source) {
552
+ const headers = new Headers();
553
+ for (const [name, value] of source.entries()) {
554
+ if (STRIPPED_PROXY_REQUEST_HEADERS.has(name.toLowerCase())) continue;
555
+ headers.append(name, value);
556
+ }
557
+ return headers;
558
+ }
559
+ function withSecurityHeaders(response) {
560
+ const headers = new Headers(response.headers);
561
+ for (const [name, value] of Object.entries(RESPONSE_SECURITY_HEADERS)) headers.set(name, value);
562
+ return new Response(response.body, {
563
+ status: response.status,
564
+ statusText: response.statusText,
565
+ headers
566
+ });
567
+ }
502
568
  /** Poll until ttyd is accepting connections on the given port */
503
569
  async function waitForTtyd(port, retries = 40, intervalMs = 200) {
504
570
  for (let i = 0; i < retries; i++) {
@@ -564,7 +630,7 @@ function spawnCaffeinate(pid) {
564
630
  }
565
631
  }
566
632
  /** Start remobi serve: builds overlay in memory, manages ttyd, serves HTTP + WS */
567
- async function serve$1(config, port = DEFAULT_PORT, command = DEFAULT_COMMAND, noSleep = false) {
633
+ async function serve$1(config, port = DEFAULT_PORT, command = DEFAULT_COMMAND, noSleep = false, host = DEFAULT_HOST) {
568
634
  console.log("remobi: building overlay...");
569
635
  const { js, css } = await bundleOverlay(config);
570
636
  const internalPort = randomInternalPort();
@@ -585,6 +651,10 @@ async function serve$1(config, port = DEFAULT_PORT, command = DEFAULT_COMMAND, n
585
651
  const connections = /* @__PURE__ */ new WeakMap();
586
652
  const app = new Hono();
587
653
  const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app });
654
+ app.use("/ws", async (c, next) => {
655
+ if (!isAllowedWebSocketOrigin(c.req.header("origin"), c.req.header("host"))) return withSecurityHeaders(c.text("Forbidden", 403));
656
+ await next();
657
+ });
588
658
  app.get("/ws", upgradeWebSocket(() => ({
589
659
  onOpen(_event, ws) {
590
660
  const raw = ws.raw;
@@ -631,38 +701,40 @@ async function serve$1(config, port = DEFAULT_PORT, command = DEFAULT_COMMAND, n
631
701
  connections.delete(raw);
632
702
  }
633
703
  })));
634
- app.get("/", (c) => c.html(html));
704
+ app.get("/", (c) => withSecurityHeaders(c.html(html)));
635
705
  if (manifestJson !== null) app.get("/manifest.json", (c) => {
636
- return c.json(JSON.parse(manifestJson));
706
+ return withSecurityHeaders(c.json(JSON.parse(manifestJson)));
637
707
  });
638
708
  if (icon180) app.get("/apple-touch-icon.png", () => {
639
- return new Response(Uint8Array.from(icon180), { headers: { "content-type": "image/png" } });
709
+ return withSecurityHeaders(new Response(Uint8Array.from(icon180), { headers: { "content-type": "image/png" } }));
640
710
  });
641
711
  if (icon192) app.get("/icon-192.png", () => {
642
- return new Response(Uint8Array.from(icon192), { headers: { "content-type": "image/png" } });
712
+ return withSecurityHeaders(new Response(Uint8Array.from(icon192), { headers: { "content-type": "image/png" } }));
643
713
  });
644
714
  if (icon512) app.get("/icon-512.png", () => {
645
- return new Response(Uint8Array.from(icon512), { headers: { "content-type": "image/png" } });
715
+ return withSecurityHeaders(new Response(Uint8Array.from(icon512), { headers: { "content-type": "image/png" } }));
646
716
  });
647
717
  app.all("/*", async (c) => {
648
718
  const url = new URL(c.req.url);
649
719
  const backendUrl = `http://127.0.0.1:${internalPort}${url.pathname}${url.search}`;
650
720
  const resp = await fetch(backendUrl, {
651
721
  method: c.req.method,
652
- headers: c.req.raw.headers,
722
+ headers: buildProxyRequestHeaders(c.req.raw.headers),
653
723
  body: c.req.raw.body
654
724
  });
655
- return new Response(resp.body, {
725
+ return withSecurityHeaders(new Response(resp.body, {
656
726
  status: resp.status,
657
727
  headers: resp.headers
658
- });
728
+ }));
659
729
  });
660
730
  const server = serve({
661
731
  fetch: app.fetch,
662
- port
732
+ port,
733
+ hostname: host
663
734
  });
664
735
  injectWebSocket(server);
665
- console.log(`remobi: serving on http://localhost:${port}`);
736
+ console.log(`remobi: serving on http://${isLoopbackHost(host) ? "localhost" : host}:${port}`);
737
+ if (!isLoopbackHost(host)) console.warn(`remobi: warning: --host ${host} exposes terminal control beyond localhost`);
666
738
  const cleanup = () => {
667
739
  console.log("\nremobi: shutting down...");
668
740
  server.close();
@@ -693,9 +765,9 @@ function usage() {
693
765
  console.log(`remobi v${VERSION} — mobile-friendly terminal overlay for ttyd + tmux
694
766
 
695
767
  Usage:
696
- remobi serve [--config <path>] [--port <n>] [--no-sleep] [-- <command...>]
768
+ remobi serve [--config <path>] [--port <n>] [--host <addr>] [--no-sleep] [-- <command...>]
697
769
  Build overlay in memory, manage ttyd, serve with PWA support.
698
- Default port: 7681. Default command: tmux new-session -A -s main
770
+ Default host: 127.0.0.1. Default port: 7681. Default command: tmux new-session -A -s main
699
771
 
700
772
  remobi build [--config <path>] [--output <path>] [--dry-run]
701
773
  Build patched index.html for ttyd --index flag.
@@ -717,12 +789,14 @@ Flags:
717
789
  -c, --config <path> Use a specific config file (build/inject/serve)
718
790
  -o, --output <path> Build output path (build only)
719
791
  -p, --port <n> Port to serve on (serve only, default 7681)
792
+ --host <addr> Host/interface to bind (serve only, default 127.0.0.1)
720
793
  -n, --dry-run Validate + print plan only (build/inject)
721
794
  --no-sleep Prevent macOS sleep while serving (caffeinate -s, serve only)
722
795
 
723
796
  Examples:
724
797
  remobi serve
725
798
  remobi serve --no-sleep
799
+ remobi serve --host 0.0.0.0 --port 8080
726
800
  remobi serve --port 8080 -- tmux new -As dev
727
801
  remobi build -c ./remobi.config.ts -o ./dist/index.html
728
802
  remobi build --dry-run
@@ -817,10 +891,10 @@ async function main() {
817
891
  usage();
818
892
  process.exit(1);
819
893
  }
820
- const { command, configPath, outputPath, dryRun, port, noSleep, command_ } = parsed.value;
894
+ const { command, configPath, outputPath, dryRun, port, host, noSleep, command_ } = parsed.value;
821
895
  switch (command) {
822
896
  case "serve":
823
- await serve$1((await loadConfig(configPath)).config, port, command_.length > 0 ? command_ : void 0, noSleep);
897
+ await serve$1((await loadConfig(configPath)).config, port, command_.length > 0 ? command_ : void 0, noSleep, host);
824
898
  break;
825
899
  case "build": {
826
900
  const loaded = await loadConfig(configPath);
@@ -935,7 +1009,8 @@ export default defineConfig({
935
1009
  process.exit(1);
936
1010
  }
937
1011
  }
938
- if (import.meta.filename === realpathSync(process.argv[1])) main().catch((err) => {
1012
+ const entryScript = process.argv[1];
1013
+ if (entryScript && import.meta.filename === realpathSync(entryScript)) main().catch((err) => {
939
1014
  console.error(err);
940
1015
  process.exit(1);
941
1016
  });
package/dist/cli.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.mjs","names":["serve","honoServe","serve"],"sources":["../src/cli/args.ts","../src/config-schema.ts","../src/config-validate.ts","../src/pwa/manifest.ts","../src/serve.ts","../cli.ts"],"sourcesContent":["type CliCommand = 'build' | 'inject' | 'init' | 'serve' | 'help' | 'version'\n\ninterface ParsedCliArgs {\n\treadonly command: CliCommand\n\treadonly configPath?: string\n\treadonly outputPath?: string\n\treadonly dryRun: boolean\n\treadonly port?: number\n\treadonly noSleep: boolean\n\treadonly command_: readonly string[]\n}\n\ninterface ParseSuccess {\n\treadonly ok: true\n\treadonly value: ParsedCliArgs\n}\n\ninterface ParseFailure {\n\treadonly ok: false\n\treadonly error: string\n}\n\ntype ParseCliResult = ParseSuccess | ParseFailure\n\nfunction isHelpCommand(value: string): boolean {\n\treturn value === '--help' || value === '-h' || value === 'help'\n}\n\nfunction isVersionCommand(value: string): boolean {\n\treturn value === '--version' || value === '-v' || value === 'version'\n}\n\nfunction isMissingOptionValue(value: string | undefined): boolean {\n\treturn value === undefined || value.startsWith('-')\n}\n\nexport function parseCliArgs(args: readonly string[]): ParseCliResult {\n\tconst commandToken = args[0]\n\tif (!commandToken || isHelpCommand(commandToken)) {\n\t\treturn { ok: true, value: { command: 'help', dryRun: false, noSleep: false, command_: [] } }\n\t}\n\n\tif (isVersionCommand(commandToken)) {\n\t\treturn { ok: true, value: { command: 'version', dryRun: false, noSleep: false, command_: [] } }\n\t}\n\n\tif (\n\t\tcommandToken !== 'build' &&\n\t\tcommandToken !== 'inject' &&\n\t\tcommandToken !== 'init' &&\n\t\tcommandToken !== 'serve'\n\t) {\n\t\treturn { ok: false, error: `Unknown command: ${commandToken}` }\n\t}\n\n\tlet configPath: string | undefined\n\tlet outputPath: string | undefined\n\tlet dryRun = false\n\tlet port: number | undefined\n\tlet noSleep = false\n\tlet trailingCommand: readonly string[] = []\n\n\tfor (let index = 1; index < args.length; index++) {\n\t\tconst arg = args[index]\n\t\tconst nextArg = args[index + 1]\n\t\tif (!arg) {\n\t\t\treturn { ok: false, error: 'Invalid argument list' }\n\t\t}\n\n\t\t// -- separator: everything after is the trailing command\n\t\tif (arg === '--') {\n\t\t\ttrailingCommand = args.slice(index + 1)\n\t\t\tbreak\n\t\t}\n\n\t\tif (arg === '--help' || arg === '-h') {\n\t\t\treturn { ok: true, value: { command: 'help', dryRun: false, noSleep: false, command_: [] } }\n\t\t}\n\n\t\tif (!arg.startsWith('-')) {\n\t\t\treturn { ok: false, error: `Unexpected positional argument: ${arg}` }\n\t\t}\n\n\t\tif (arg === '--config' || arg === '-c') {\n\t\t\tif (commandToken !== 'build' && commandToken !== 'inject' && commandToken !== 'serve') {\n\t\t\t\treturn { ok: false, error: `${arg} is only valid for 'build', 'inject', or 'serve'` }\n\t\t\t}\n\t\t\tif (isMissingOptionValue(nextArg)) {\n\t\t\t\treturn { ok: false, error: 'Missing value for --config' }\n\t\t\t}\n\t\t\tconfigPath = nextArg\n\t\t\tindex++\n\t\t\tcontinue\n\t\t}\n\n\t\tif (arg === '--output' || arg === '-o') {\n\t\t\tif (commandToken !== 'build') {\n\t\t\t\treturn { ok: false, error: `${arg} is only valid for 'build'` }\n\t\t\t}\n\t\t\tif (isMissingOptionValue(nextArg)) {\n\t\t\t\treturn { ok: false, error: 'Missing value for --output' }\n\t\t\t}\n\t\t\toutputPath = nextArg\n\t\t\tindex++\n\t\t\tcontinue\n\t\t}\n\n\t\tif (arg === '--dry-run' || arg === '-n') {\n\t\t\tif (commandToken !== 'build' && commandToken !== 'inject') {\n\t\t\t\treturn { ok: false, error: `${arg} is only valid for 'build' or 'inject'` }\n\t\t\t}\n\t\t\tdryRun = true\n\t\t\tcontinue\n\t\t}\n\n\t\tif (arg === '--port' || arg === '-p') {\n\t\t\tif (commandToken !== 'serve') {\n\t\t\t\treturn { ok: false, error: `${arg} is only valid for 'serve'` }\n\t\t\t}\n\t\t\tif (isMissingOptionValue(nextArg)) {\n\t\t\t\treturn { ok: false, error: 'Missing value for --port' }\n\t\t\t}\n\t\t\tconst portNum = Number(nextArg)\n\t\t\tif (!Number.isInteger(portNum) || portNum < 1 || portNum > 65535) {\n\t\t\t\treturn { ok: false, error: `Invalid port: ${nextArg}` }\n\t\t\t}\n\t\t\tport = portNum\n\t\t\tindex++\n\t\t\tcontinue\n\t\t}\n\n\t\tif (arg === '--no-sleep') {\n\t\t\tif (commandToken !== 'serve') {\n\t\t\t\treturn { ok: false, error: `${arg} is only valid for 'serve'` }\n\t\t\t}\n\t\t\tnoSleep = true\n\t\t\tcontinue\n\t\t}\n\n\t\tif (isVersionCommand(arg)) {\n\t\t\treturn { ok: false, error: `${arg} is only valid as a top-level command` }\n\t\t}\n\n\t\treturn { ok: false, error: `Unknown flag: ${arg}` }\n\t}\n\n\treturn {\n\t\tok: true,\n\t\tvalue: {\n\t\t\tcommand: commandToken,\n\t\t\tconfigPath,\n\t\t\toutputPath,\n\t\t\tdryRun,\n\t\t\tport,\n\t\t\tnoSleep,\n\t\t\tcommand_: trailingCommand,\n\t\t},\n\t}\n}\n","/**\n * Valibot schemas for remobi config validation.\n * Only used at CLI time (build/inject/serve) — never in the browser bundle.\n */\nimport * as v from 'valibot'\n\n// --- Primitives ---\n\nconst finiteNumber = v.pipe(v.number(), v.finite())\n\n// --- Button action (discriminated union) ---\n\nconst sendActionSchema = v.strictObject({\n\ttype: v.literal('send'),\n\tdata: v.string(),\n\tkeyLabel: v.optional(v.string()),\n})\n\nconst ctrlModifierActionSchema = v.strictObject({ type: v.literal('ctrl-modifier') })\nconst pasteActionSchema = v.strictObject({ type: v.literal('paste') })\nconst comboPickerActionSchema = v.strictObject({ type: v.literal('combo-picker') })\nconst drawerToggleActionSchema = v.strictObject({ type: v.literal('drawer-toggle') })\n\nconst buttonActionSchema = v.variant('type', [\n\tsendActionSchema,\n\tctrlModifierActionSchema,\n\tpasteActionSchema,\n\tcomboPickerActionSchema,\n\tdrawerToggleActionSchema,\n])\n\n// --- Control button ---\n\nconst controlButtonSchema = v.strictObject({\n\tid: v.string(),\n\tlabel: v.string(),\n\tdescription: v.string(),\n\taction: buttonActionSchema,\n})\n\n// --- Button array input (array | function) ---\n// Uses v.custom for type check + v.rawCheck for deep array validation,\n// avoiding v.union which loses path context for nested issues.\n\nconst buttonArrayInputSchema = v.pipe(\n\tv.custom<readonly Record<string, unknown>[] | ((...args: readonly unknown[]) => unknown)>(\n\t\t(input) => Array.isArray(input) || typeof input === 'function',\n\t\t'array or function',\n\t),\n\tv.rawCheck(({ dataset, addIssue }) => {\n\t\tif (!dataset.typed || !Array.isArray(dataset.value)) return\n\t\tconst arr = dataset.value\n\t\tfor (let i = 0; i < arr.length; i++) {\n\t\t\tconst result = v.safeParse(controlButtonSchema, arr[i])\n\t\t\tif (!result.success) {\n\t\t\t\tfor (const issue of result.issues) {\n\t\t\t\t\taddIssue({\n\t\t\t\t\t\tmessage: issue.message,\n\t\t\t\t\t\texpected: issue.expected,\n\t\t\t\t\t\treceived: issue.received,\n\t\t\t\t\t\tpath: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttype: 'array',\n\t\t\t\t\t\t\t\torigin: 'value',\n\t\t\t\t\t\t\t\tinput: arr,\n\t\t\t\t\t\t\t\tkey: i,\n\t\t\t\t\t\t\t\t// oxlint-disable-next-line typescript/consistent-type-assertions -- Valibot path segment requires typed value\n\t\t\t\t\t\t\t\tvalue: arr[i] as Record<string, unknown>,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t...(issue.path ?? []),\n\t\t\t\t\t\t],\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}),\n)\n\n// --- Theme ---\n\nconst themeColourSchema = v.optional(v.string())\n\nconst termThemeOverridesSchema = v.strictObject({\n\tbackground: themeColourSchema,\n\tforeground: themeColourSchema,\n\tcursor: themeColourSchema,\n\tcursorAccent: themeColourSchema,\n\tselectionBackground: themeColourSchema,\n\tblack: themeColourSchema,\n\tred: themeColourSchema,\n\tgreen: themeColourSchema,\n\tyellow: themeColourSchema,\n\tblue: themeColourSchema,\n\tmagenta: themeColourSchema,\n\tcyan: themeColourSchema,\n\twhite: themeColourSchema,\n\tbrightBlack: themeColourSchema,\n\tbrightRed: themeColourSchema,\n\tbrightGreen: themeColourSchema,\n\tbrightYellow: themeColourSchema,\n\tbrightBlue: themeColourSchema,\n\tbrightMagenta: themeColourSchema,\n\tbrightCyan: themeColourSchema,\n\tbrightWhite: themeColourSchema,\n})\n\nconst termThemeResolvedSchema = v.strictObject({\n\tbackground: v.string(),\n\tforeground: v.string(),\n\tcursor: v.string(),\n\tcursorAccent: v.string(),\n\tselectionBackground: v.string(),\n\tblack: v.string(),\n\tred: v.string(),\n\tgreen: v.string(),\n\tyellow: v.string(),\n\tblue: v.string(),\n\tmagenta: v.string(),\n\tcyan: v.string(),\n\twhite: v.string(),\n\tbrightBlack: v.string(),\n\tbrightRed: v.string(),\n\tbrightGreen: v.string(),\n\tbrightYellow: v.string(),\n\tbrightBlue: v.string(),\n\tbrightMagenta: v.string(),\n\tbrightCyan: v.string(),\n\tbrightWhite: v.string(),\n})\n\n// --- Font ---\n\nconst fontOverridesSchema = v.strictObject({\n\tfamily: v.optional(v.string()),\n\tcdnUrl: v.optional(v.string()),\n\tmobileSizeDefault: v.optional(finiteNumber),\n\tsizeRange: v.optional(v.pipe(v.tuple([finiteNumber, finiteNumber]))),\n})\n\nconst fontResolvedSchema = v.strictObject({\n\tfamily: v.string(),\n\tcdnUrl: v.string(),\n\tmobileSizeDefault: finiteNumber,\n\tsizeRange: v.pipe(v.tuple([finiteNumber, finiteNumber])),\n})\n\n// --- Gestures ---\n\nconst swipeOverridesSchema = v.strictObject({\n\tenabled: v.optional(v.boolean()),\n\tthreshold: v.optional(finiteNumber),\n\tmaxDuration: v.optional(finiteNumber),\n\tleft: v.optional(v.string()),\n\tright: v.optional(v.string()),\n\tleftLabel: v.optional(v.string()),\n\trightLabel: v.optional(v.string()),\n})\n\nconst swipeResolvedSchema = v.strictObject({\n\tenabled: v.boolean(),\n\tthreshold: finiteNumber,\n\tmaxDuration: finiteNumber,\n\tleft: v.string(),\n\tright: v.string(),\n\tleftLabel: v.string(),\n\trightLabel: v.string(),\n})\n\nconst pinchOverridesSchema = v.strictObject({\n\tenabled: v.optional(v.boolean()),\n})\n\nconst pinchResolvedSchema = v.strictObject({\n\tenabled: v.boolean(),\n})\n\nconst scrollStrategySchema = v.picklist(['keys', 'wheel'])\n\nconst scrollOverridesSchema = v.strictObject({\n\tenabled: v.optional(v.boolean()),\n\tsensitivity: v.optional(finiteNumber),\n\tstrategy: v.optional(scrollStrategySchema),\n\twheelIntervalMs: v.optional(finiteNumber),\n})\n\nconst scrollResolvedSchema = v.strictObject({\n\tenabled: v.boolean(),\n\tsensitivity: finiteNumber,\n\tstrategy: scrollStrategySchema,\n\twheelIntervalMs: finiteNumber,\n})\n\nconst gestureOverridesSchema = v.strictObject({\n\tswipe: v.optional(swipeOverridesSchema),\n\tpinch: v.optional(pinchOverridesSchema),\n\tscroll: v.optional(scrollOverridesSchema),\n})\n\nconst gestureResolvedSchema = v.strictObject({\n\tswipe: swipeResolvedSchema,\n\tpinch: pinchResolvedSchema,\n\tscroll: scrollResolvedSchema,\n})\n\n// --- Mobile ---\n\nconst mobileOverridesSchema = v.strictObject({\n\tinitData: v.optional(v.nullable(v.string())),\n\twidthThreshold: v.optional(finiteNumber),\n})\n\nconst mobileResolvedSchema = v.strictObject({\n\tinitData: v.nullable(v.string()),\n\twidthThreshold: finiteNumber,\n})\n\n// --- Floating buttons ---\n\nconst floatingPositionSchema = v.picklist([\n\t'top-left',\n\t'top-right',\n\t'top-centre',\n\t'bottom-left',\n\t'bottom-right',\n\t'bottom-centre',\n\t'centre-left',\n\t'centre-right',\n])\n\nconst floatingDirectionSchema = v.picklist(['row', 'column'])\n\nconst floatingButtonGroupSchema = v.strictObject({\n\tposition: floatingPositionSchema,\n\tdirection: v.optional(floatingDirectionSchema),\n\tbuttons: v.array(controlButtonSchema),\n})\n\n// --- PWA ---\n\nconst pwaOverridesSchema = v.strictObject({\n\tenabled: v.optional(v.boolean()),\n\tshortName: v.optional(v.string()),\n\tthemeColor: v.optional(v.string()),\n})\n\nconst pwaResolvedSchema = v.strictObject({\n\tenabled: v.boolean(),\n\tshortName: v.optional(v.string()),\n\tthemeColor: v.string(),\n})\n\n// --- Reconnect ---\n\nconst reconnectOverridesSchema = v.strictObject({\n\tenabled: v.optional(v.boolean()),\n})\n\nconst reconnectResolvedSchema = v.strictObject({\n\tenabled: v.boolean(),\n})\n\n// --- Top-level schemas ---\n\n/** Schema for config overrides (all fields optional, button arrays accept array | function) */\nexport const remobiConfigOverridesSchema = v.strictObject({\n\tname: v.optional(v.string()),\n\ttheme: v.optional(termThemeOverridesSchema),\n\tfont: v.optional(fontOverridesSchema),\n\ttoolbar: v.optional(\n\t\tv.strictObject({\n\t\t\trow1: v.optional(buttonArrayInputSchema),\n\t\t\trow2: v.optional(buttonArrayInputSchema),\n\t\t}),\n\t),\n\tdrawer: v.optional(\n\t\tv.strictObject({\n\t\t\tbuttons: v.optional(buttonArrayInputSchema),\n\t\t}),\n\t),\n\tgestures: v.optional(gestureOverridesSchema),\n\tmobile: v.optional(mobileOverridesSchema),\n\tfloatingButtons: v.optional(v.array(floatingButtonGroupSchema)),\n\tpwa: v.optional(pwaOverridesSchema),\n\treconnect: v.optional(reconnectOverridesSchema),\n})\n\n/** Schema for fully resolved config (all required fields, plain button arrays) */\nexport const remobiConfigResolvedSchema = v.strictObject({\n\tname: v.string(),\n\ttheme: termThemeResolvedSchema,\n\tfont: fontResolvedSchema,\n\ttoolbar: v.strictObject({\n\t\trow1: v.array(controlButtonSchema),\n\t\trow2: v.array(controlButtonSchema),\n\t}),\n\tdrawer: v.strictObject({\n\t\tbuttons: v.array(controlButtonSchema),\n\t}),\n\tgestures: gestureResolvedSchema,\n\tmobile: mobileResolvedSchema,\n\tfloatingButtons: v.array(floatingButtonGroupSchema),\n\tpwa: pwaResolvedSchema,\n\treconnect: reconnectResolvedSchema,\n})\n","import * as v from 'valibot'\nimport { remobiConfigOverridesSchema, remobiConfigResolvedSchema } from './config-schema'\nimport type { RemobiConfig, RemobiConfigOverrides } from './types'\n\ninterface ValidationIssue {\n\treadonly path: string\n\treadonly expected: string\n\treadonly received: string\n}\n\nexport class ConfigValidationError extends Error {\n\treadonly issues: readonly ValidationIssue[]\n\n\tconstructor(issues: readonly ValidationIssue[]) {\n\t\tsuper(formatIssues(issues))\n\t\tthis.name = 'ConfigValidationError'\n\t\tthis.issues = issues\n\t}\n}\n\nfunction formatIssues(issues: readonly ValidationIssue[]): string {\n\tconst lines = ['Invalid remobi config:']\n\tfor (const issue of issues) {\n\t\tlines.push(`- ${issue.path}: expected ${issue.expected}, received ${issue.received}`)\n\t}\n\treturn lines.join('\\n')\n}\n\nfunction truncate(value: string, maxLength: number): string {\n\tif (value.length <= maxLength) {\n\t\treturn value\n\t}\n\treturn `${value.slice(0, maxLength - 3)}...`\n}\n\nfunction describeReceived(value: unknown): string {\n\tif (value === null) return 'null'\n\tif (Array.isArray(value)) return `array(len=${value.length})`\n\tif (typeof value === 'string') return `string(${JSON.stringify(truncate(value, 80))})`\n\tif (typeof value === 'number') return `number(${String(value)})`\n\tif (typeof value === 'boolean') return `boolean(${String(value)})`\n\tif (typeof value === 'bigint') return `bigint(${String(value)})`\n\tif (typeof value === 'undefined') return 'undefined'\n\tif (typeof value === 'function')\n\t\treturn value.name.length > 0 ? `function(${value.name})` : 'function'\n\tif (typeof value === 'object') {\n\t\tconst keys = Object.keys(value)\n\t\tif (keys.length === 0) return 'object(empty)'\n\t\tconst shown = keys.slice(0, 3).join(', ')\n\t\tconst suffix = keys.length > 3 ? ', ...' : ''\n\t\treturn `object(keys: ${shown}${suffix})`\n\t}\n\treturn typeof value\n}\n\n/** Convert Valibot issue path to dotted string */\nfunction issuePath(issue: v.BaseIssue<unknown>): string {\n\tif (!issue.path || issue.path.length === 0) return 'config'\n\tconst segments: string[] = ['config']\n\tfor (const segment of issue.path) {\n\t\tif (typeof segment.key === 'number') {\n\t\t\tsegments.push(`[${String(segment.key)}]`)\n\t\t} else {\n\t\t\tsegments.push(`.${String(segment.key)}`)\n\t\t}\n\t}\n\treturn segments.join('').replaceAll('.[', '[')\n}\n\n/** Extract human-readable expected string from a Valibot issue */\nfunction issueExpected(issue: v.BaseIssue<unknown>): string {\n\t// v.custom() sets expected to \"unknown\" — use the message instead\n\tif (issue.expected === 'unknown' || !issue.expected) {\n\t\treturn issue.message\n\t}\n\treturn issue.expected\n}\n\n/** Map Valibot flat issues to our ValidationIssue format */\nfunction toValidationIssues(issues: readonly v.BaseIssue<unknown>[]): ValidationIssue[] {\n\tconst result: ValidationIssue[] = []\n\tfor (const issue of issues) {\n\t\t// Leaf issues only — skip container issues that have nested issues\n\t\tif (issue.issues && issue.issues.length > 0) {\n\t\t\tresult.push(...toValidationIssues(issue.issues))\n\t\t\tcontinue\n\t\t}\n\t\tconst received = issue.input !== undefined ? issue.input : undefined\n\t\tresult.push({\n\t\t\tpath: issuePath(issue),\n\t\t\texpected: issueExpected(issue),\n\t\t\treceived: describeReceived(received),\n\t\t})\n\t}\n\treturn result\n}\n\nexport function assertValidConfigOverrides(value: unknown): asserts value is RemobiConfigOverrides {\n\tconst result = v.safeParse(remobiConfigOverridesSchema, value)\n\tif (!result.success) {\n\t\tthrow new ConfigValidationError(toValidationIssues(result.issues))\n\t}\n}\n\nexport function assertValidResolvedConfig(value: unknown): asserts value is RemobiConfig {\n\tconst result = v.safeParse(remobiConfigResolvedSchema, value)\n\tif (!result.success) {\n\t\tthrow new ConfigValidationError(toValidationIssues(result.issues))\n\t}\n}\n","import type { PwaConfig } from '../types'\n\ninterface WebAppManifest {\n\treadonly name: string\n\treadonly short_name: string\n\treadonly start_url: string\n\treadonly display: string\n\treadonly background_color: string\n\treadonly theme_color: string\n\treadonly icons: readonly {\n\t\treadonly src: string\n\t\treadonly sizes: string\n\t\treadonly type: string\n\t\treadonly purpose?: string\n\t}[]\n}\n\n/** Generate a web app manifest object from pwa config */\nexport function generateManifest(name: string, pwa: PwaConfig): WebAppManifest {\n\treturn {\n\t\tname,\n\t\tshort_name: pwa.shortName ?? name,\n\t\tstart_url: '/',\n\t\tdisplay: 'standalone',\n\t\tbackground_color: pwa.themeColor,\n\t\ttheme_color: pwa.themeColor,\n\t\ticons: [\n\t\t\t{ src: '/icon-192.png', sizes: '192x192', type: 'image/png', purpose: 'any maskable' },\n\t\t\t{ src: '/icon-512.png', sizes: '512x512', type: 'image/png' },\n\t\t],\n\t}\n}\n\n/** Serialise manifest to JSON string */\nexport function manifestToJson(name: string, pwa: PwaConfig): string {\n\treturn JSON.stringify(generateManifest(name, pwa), null, 2)\n}\n","import { existsSync, readFileSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport { serve as honoServe } from '@hono/node-server'\nimport { createNodeWebSocket } from '@hono/node-ws'\nimport { Hono } from 'hono'\nimport type { WSContext } from 'hono/ws'\nimport WebSocket from 'ws'\nimport { bundleOverlay, injectOverlay } from '../build'\nimport { serialiseThemeForTtyd } from './config'\nimport { manifestToJson } from './pwa/manifest'\nimport type { RemobiConfig } from './types'\nimport { sleep, spawnProcess } from './util/node-compat'\nimport type { SpawnedProcess } from './util/node-compat'\n\nconst DEFAULT_PORT = 7681\nconst DEFAULT_COMMAND = ['tmux', 'new-session', '-A', '-s', 'main']\n// Walk up from module location to find package root, then resolve icons\nfunction findIconsDir(): string {\n\tlet dir = import.meta.dirname\n\tfor (let i = 0; i < 5; i++) {\n\t\tconst candidate = resolve(dir, 'src/pwa/icons')\n\t\tif (existsSync(candidate)) return candidate\n\t\tdir = dirname(dir)\n\t}\n\t// Fallback for source layout (running from src/)\n\treturn resolve(import.meta.dirname, 'pwa/icons')\n}\n\nconst ICONS_DIR = findIconsDir()\n\ninterface WsData {\n\tbackend: WebSocket | null\n\tbuffer: (string | Uint8Array)[]\n}\n\n/** Poll until ttyd is accepting connections on the given port */\nasync function waitForTtyd(port: number, retries = 40, intervalMs = 200): Promise<void> {\n\tfor (let i = 0; i < retries; i++) {\n\t\ttry {\n\t\t\tconst resp = await fetch(`http://127.0.0.1:${port}/`)\n\t\t\tif (resp.ok) return\n\t\t} catch {\n\t\t\t// not ready yet\n\t\t}\n\t\tawait sleep(intervalMs)\n\t}\n\tthrow new Error(\n\t\t`ttyd did not start on port ${port} — is ttyd installed and on PATH?\\nInstall ttyd: macOS \\`brew install ttyd\\`; Linux use your distro package manager or build from source: https://github.com/tsl0922/ttyd#installation`,\n\t)\n}\n\n/** Pick a random internal port */\nexport function randomInternalPort(): number {\n\treturn 19000 + Math.floor(Math.random() * 1000)\n}\n\n/** Build ttyd args from remobi config */\nexport function buildTtydArgs(\n\tconfig: RemobiConfig,\n\tinternalPort: number,\n\tcommand: readonly string[],\n): string[] {\n\treturn [\n\t\t'--writable',\n\t\t'-i',\n\t\t'127.0.0.1',\n\t\t'--port',\n\t\tString(internalPort),\n\t\t'-t',\n\t\t`theme=${serialiseThemeForTtyd(config)}`,\n\t\t'-t',\n\t\t`fontFamily=\"${config.font.family}\"`,\n\t\t'-t',\n\t\t'scrollSensitivity=3',\n\t\t'-t',\n\t\t'disableLeaveAlert=true',\n\t\t...command,\n\t]\n}\n\n/** Read a PNG icon, returns undefined if not found */\nfunction readIcon(filename: string): Uint8Array | undefined {\n\ttry {\n\t\treturn readFileSync(resolve(ICONS_DIR, filename))\n\t} catch {\n\t\treturn undefined\n\t}\n}\n\n/** Spawn caffeinate to prevent system sleep while ttyd is running (macOS only).\n * Uses -s (system sleep on AC) and -w <pid> so the assertion drops when ttyd exits. */\nfunction spawnCaffeinate(pid: number): SpawnedProcess | null {\n\ttry {\n\t\tconst proc = spawnProcess(['caffeinate', '-s', '-w', String(pid)], {\n\t\t\tstdout: 'ignore',\n\t\t\tstderr: 'ignore',\n\t\t})\n\t\t// Catch async spawn errors (e.g. caffeinate not found on Linux)\n\t\tproc.exited.catch(() => {\n\t\t\tconsole.warn('remobi: --no-sleep requires caffeinate (macOS only), ignoring')\n\t\t})\n\t\tconsole.log(`remobi: sleep prevention active (caffeinate -s -w ${pid})`)\n\t\treturn proc\n\t} catch {\n\t\tconsole.warn('remobi: --no-sleep requires caffeinate (macOS only), ignoring')\n\t\treturn null\n\t}\n}\n\n/** Start remobi serve: builds overlay in memory, manages ttyd, serves HTTP + WS */\nexport async function serve(\n\tconfig: RemobiConfig,\n\tport: number = DEFAULT_PORT,\n\tcommand: readonly string[] = DEFAULT_COMMAND,\n\tnoSleep = false,\n): Promise<void> {\n\tconsole.log('remobi: building overlay...')\n\tconst { js, css } = await bundleOverlay(config)\n\n\tconst internalPort = randomInternalPort()\n\tconst ttydArgs = buildTtydArgs(config, internalPort, command)\n\n\tconsole.log(`remobi: starting ttyd on internal port ${internalPort}...`)\n\tconst ttydProc = spawnProcess(['ttyd', ...ttydArgs], {\n\t\tstdout: 'ignore',\n\t\tstderr: 'ignore',\n\t})\n\n\tconst caffeinateProc = noSleep && ttydProc.pid ? spawnCaffeinate(ttydProc.pid) : null\n\n\tawait waitForTtyd(internalPort)\n\n\tconst baseResp = await fetch(`http://127.0.0.1:${internalPort}/`)\n\tconst baseHtml = await baseResp.text()\n\tconst html = injectOverlay(baseHtml, js, css, config)\n\n\tconsole.log('remobi: overlay ready')\n\n\tconst manifestJson = config.pwa.enabled ? manifestToJson(config.name, config.pwa) : null\n\tconst icon180 = readIcon('icon-180.png')\n\tconst icon192 = readIcon('icon-192.png')\n\tconst icon512 = readIcon('icon-512.png')\n\n\t// Per-connection data via WeakMap (replaces Bun's ws.data)\n\tconst connections = new WeakMap<WebSocket, WsData>()\n\n\tconst app = new Hono()\n\tconst { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app })\n\n\tapp.get(\n\t\t'/ws',\n\t\tupgradeWebSocket(() => ({\n\t\t\tonOpen(_event: Event, ws: WSContext<WebSocket>) {\n\t\t\t\tconst raw = ws.raw\n\t\t\t\tif (!raw) return\n\n\t\t\t\tconst data: WsData = { backend: null, buffer: [] }\n\t\t\t\tconnections.set(raw, data)\n\n\t\t\t\tconst backend = new WebSocket(`ws://127.0.0.1:${internalPort}/ws`, ['tty'])\n\t\t\t\tbackend.binaryType = 'arraybuffer'\n\t\t\t\tdata.backend = backend\n\n\t\t\t\tbackend.on('open', () => {\n\t\t\t\t\tfor (const msg of data.buffer) {\n\t\t\t\t\t\tbackend.send(msg)\n\t\t\t\t\t}\n\t\t\t\t\tdata.buffer = []\n\t\t\t\t})\n\n\t\t\t\tbackend.on('message', (message: WebSocket.RawData, isBinary: boolean) => {\n\t\t\t\t\tif (isBinary && message instanceof ArrayBuffer) {\n\t\t\t\t\t\tws.send(new Uint8Array(message))\n\t\t\t\t\t} else {\n\t\t\t\t\t\tws.send(message.toString())\n\t\t\t\t\t}\n\t\t\t\t})\n\n\t\t\t\tbackend.on('error', () => {\n\t\t\t\t\tws.close()\n\t\t\t\t})\n\n\t\t\t\tbackend.on('close', () => {\n\t\t\t\t\tws.close()\n\t\t\t\t})\n\t\t\t},\n\t\t\tonMessage(event: MessageEvent, ws: WSContext<WebSocket>) {\n\t\t\t\tconst raw = ws.raw\n\t\t\t\tif (!raw) return\n\t\t\t\tconst data = connections.get(raw)\n\t\t\t\tif (!data) return\n\n\t\t\t\tconst { backend, buffer } = data\n\t\t\t\tif (backend !== null && backend.readyState === WebSocket.OPEN) {\n\t\t\t\t\t// oxlint-disable-next-line typescript/consistent-type-assertions -- WSMessageReceive union needs narrowing for ws.send()\n\t\t\t\t\tbackend.send(event.data as string | ArrayBuffer)\n\t\t\t\t} else {\n\t\t\t\t\tconst msg = event.data\n\t\t\t\t\t// oxlint-disable-next-line typescript/consistent-type-assertions -- WSMessageReceive union needs narrowing for Uint8Array ctor\n\t\t\t\t\tbuffer.push(typeof msg === 'string' ? msg : new Uint8Array(msg as ArrayBuffer))\n\t\t\t\t}\n\t\t\t},\n\t\t\tonClose(_event: CloseEvent, ws: WSContext<WebSocket>) {\n\t\t\t\tconst raw = ws.raw\n\t\t\t\tif (!raw) return\n\t\t\t\tconnections.get(raw)?.backend?.close()\n\t\t\t\tconnections.delete(raw)\n\t\t\t},\n\t\t})),\n\t)\n\n\tapp.get('/', (c) => c.html(html))\n\n\tif (manifestJson !== null) {\n\t\tapp.get('/manifest.json', (c) => {\n\t\t\t// oxlint-disable-next-line typescript/consistent-type-assertions -- JSON.parse returns unknown, safe for manifest\n\t\t\treturn c.json(JSON.parse(manifestJson) as Record<string, unknown>)\n\t\t})\n\t}\n\n\tif (icon180) {\n\t\tapp.get('/apple-touch-icon.png', () => {\n\t\t\treturn new Response(Uint8Array.from(icon180), {\n\t\t\t\theaders: { 'content-type': 'image/png' },\n\t\t\t})\n\t\t})\n\t}\n\n\tif (icon192) {\n\t\tapp.get('/icon-192.png', () => {\n\t\t\treturn new Response(Uint8Array.from(icon192), {\n\t\t\t\theaders: { 'content-type': 'image/png' },\n\t\t\t})\n\t\t})\n\t}\n\n\tif (icon512) {\n\t\tapp.get('/icon-512.png', () => {\n\t\t\treturn new Response(Uint8Array.from(icon512), {\n\t\t\t\theaders: { 'content-type': 'image/png' },\n\t\t\t})\n\t\t})\n\t}\n\n\t// Proxy remaining requests to ttyd (e.g. /token)\n\tapp.all('/*', async (c) => {\n\t\tconst url = new URL(c.req.url)\n\t\tconst backendUrl = `http://127.0.0.1:${internalPort}${url.pathname}${url.search}`\n\t\tconst resp = await fetch(backendUrl, {\n\t\t\tmethod: c.req.method,\n\t\t\theaders: c.req.raw.headers,\n\t\t\tbody: c.req.raw.body,\n\t\t})\n\t\treturn new Response(resp.body, {\n\t\t\tstatus: resp.status,\n\t\t\theaders: resp.headers,\n\t\t})\n\t})\n\n\tconst server = honoServe({ fetch: app.fetch, port })\n\tinjectWebSocket(server)\n\n\tconsole.log(`remobi: serving on http://localhost:${port}`)\n\n\t// Clean shutdown on SIGINT / SIGTERM\n\tconst cleanup = () => {\n\t\tconsole.log('\\nremobi: shutting down...')\n\t\tserver.close()\n\t\tttydProc.kill()\n\t\tcaffeinateProc?.kill()\n\t\tprocess.exit(0)\n\t}\n\n\tprocess.on('SIGINT', cleanup)\n\tprocess.on('SIGTERM', cleanup)\n\n\t// Keep process alive until ttyd exits\n\tawait ttydProc.exited\n\tserver.close()\n}\n","#!/usr/bin/env node\nimport { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { dirname, join, resolve } from 'node:path'\nimport { build, injectFromStdin } from './build'\nimport { parseCliArgs } from './src/cli/args'\nimport { defaultConfig, defineConfig, mergeConfig } from './src/config'\nimport {\n\tConfigValidationError,\n\tassertValidConfigOverrides,\n\tassertValidResolvedConfig,\n} from './src/config-validate'\nimport { serve } from './src/serve'\nimport type { RemobiConfig, RemobiConfigOverrides } from './src/types'\nimport { readStdin } from './src/util/node-compat'\n\n// Walk up from module location to find package.json — works from both source and dist/\nfunction loadPackageVersion(): string {\n\tlet dir = import.meta.dirname\n\tfor (let i = 0; i < 5; i++) {\n\t\ttry {\n\t\t\tconst content = readFileSync(resolve(dir, 'package.json'), 'utf-8')\n\t\t\t// oxlint-disable-next-line typescript/consistent-type-assertions -- JSON.parse returns unknown\n\t\t\treturn (JSON.parse(content) as { version: string }).version\n\t\t} catch {\n\t\t\tdir = dirname(dir)\n\t\t}\n\t}\n\treturn '0.0.0'\n}\n\nconst VERSION: string = loadPackageVersion()\n\nfunction usage(): void {\n\tconsole.log(`remobi v${VERSION} — mobile-friendly terminal overlay for ttyd + tmux\n\nUsage:\n remobi serve [--config <path>] [--port <n>] [--no-sleep] [-- <command...>]\n Build overlay in memory, manage ttyd, serve with PWA support.\n Default port: 7681. Default command: tmux new-session -A -s main\n\n remobi build [--config <path>] [--output <path>] [--dry-run]\n Build patched index.html for ttyd --index flag.\n Starts temp ttyd, fetches base HTML, injects overlay.\n\n remobi inject [--config <path>] [--dry-run]\n Pipe mode: reads ttyd HTML from stdin, outputs patched HTML to stdout.\n\n remobi init\n Scaffold a remobi.config.ts with defaults.\n\n remobi --version\n Print version.\n\n remobi --help\n Show this help.\n\nFlags:\n -c, --config <path> Use a specific config file (build/inject/serve)\n -o, --output <path> Build output path (build only)\n -p, --port <n> Port to serve on (serve only, default 7681)\n -n, --dry-run Validate + print plan only (build/inject)\n --no-sleep Prevent macOS sleep while serving (caffeinate -s, serve only)\n\nExamples:\n remobi serve\n remobi serve --no-sleep\n remobi serve --port 8080 -- tmux new -As dev\n remobi build -c ./remobi.config.ts -o ./dist/index.html\n remobi build --dry-run\n curl -s http://127.0.0.1:7681/ | remobi inject --dry-run`)\n}\n\ninterface LoadedConfig {\n\treadonly config: RemobiConfig\n\treadonly source: string\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction extractDefaultExport(value: unknown): unknown | undefined {\n\tif (!isRecord(value)) return undefined\n\tif (!('default' in value)) return undefined\n\treturn value.default\n}\n\nfunction ensureInjectInputMode(context: string): void {\n\tif (process.stdin.isTTY) {\n\t\tthrow new Error(`${context} expects piped ttyd HTML on stdin`)\n\t}\n}\n\nfunction throwConfigValidationError(source: string, error: ConfigValidationError): never {\n\tthrow new Error(`Config validation failed for ${source}\\n${error.message}`)\n}\n\n/** Convert a config path to its .local sibling, e.g. remobi.config.ts → remobi.config.local.ts */\nfunction toLocalPath(configPath: string): string {\n\tconst dotIdx = configPath.lastIndexOf('.')\n\tif (dotIdx === -1) {\n\t\treturn `${configPath}.local`\n\t}\n\treturn `${configPath.slice(0, dotIdx)}.local${configPath.slice(dotIdx)}`\n}\n\n/** Try to load a .local config override file. Returns undefined if the file does not exist. */\nasync function loadLocalOverrides(localPath: string): Promise<RemobiConfigOverrides | undefined> {\n\tif (!existsSync(localPath)) {\n\t\treturn undefined\n\t}\n\n\tconst mod = await import(localPath)\n\tconst defaultExport = extractDefaultExport(mod)\n\tif (defaultExport === undefined) {\n\t\tthrow new Error(`Local config file has no default export: ${localPath}`)\n\t}\n\n\tassertValidOverridesOrThrow(defaultExport, localPath)\n\treturn defaultExport\n}\n\nfunction assertValidOverridesOrThrow(\n\tvalue: unknown,\n\tsource: string,\n): asserts value is RemobiConfigOverrides {\n\ttry {\n\t\tassertValidConfigOverrides(value)\n\t} catch (error) {\n\t\tif (error instanceof ConfigValidationError) {\n\t\t\tthrowConfigValidationError(source, error)\n\t\t}\n\t\tthrow error\n\t}\n}\n\nfunction assertValidResolvedOrThrow(value: unknown, source: string): asserts value is RemobiConfig {\n\ttry {\n\t\tassertValidResolvedConfig(value)\n\t} catch (error) {\n\t\tif (error instanceof ConfigValidationError) {\n\t\t\tthrowConfigValidationError(source, error)\n\t\t}\n\t\tthrow error\n\t}\n}\n\nasync function loadConfig(configPath: string | undefined): Promise<LoadedConfig> {\n\tlet resolved = configPath\n\tif (!resolved) {\n\t\t// Search order: cwd → XDG config dir (~/.config/remobi/)\n\t\tconst names = ['remobi.config.ts', 'remobi.config.js']\n\t\tconst searchDirs = [\n\t\t\tprocess.cwd(),\n\t\t\tjoin(process.env.XDG_CONFIG_HOME ?? join(homedir(), '.config'), 'remobi'),\n\t\t]\n\t\tfor (const dir of searchDirs) {\n\t\t\tfor (const name of names) {\n\t\t\t\tconst full = join(dir, name)\n\t\t\t\tif (existsSync(full)) {\n\t\t\t\t\tresolved = full\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (resolved) break\n\t\t}\n\t}\n\n\tif (resolved) {\n\t\tconst abs = resolve(process.cwd(), resolved)\n\t\tconst mod = await import(abs)\n\t\tconst defaultExport = extractDefaultExport(mod)\n\t\tif (defaultExport === undefined) {\n\t\t\tthrow new Error(`Config file has no default export: ${abs}`)\n\t\t}\n\n\t\tassertValidOverridesOrThrow(defaultExport, abs)\n\t\tconst sharedConfig = defineConfig(defaultExport)\n\n\t\t// Apply .local overrides on top of the shared config\n\t\tconst localPath = toLocalPath(abs)\n\t\tconst localOverrides = await loadLocalOverrides(localPath)\n\t\tconst config =\n\t\t\tlocalOverrides !== undefined ? mergeConfig(sharedConfig, localOverrides) : sharedConfig\n\n\t\tconst sourceLabel = localOverrides !== undefined ? `${abs} + ${localPath}` : abs\n\t\tassertValidResolvedOrThrow(config, sourceLabel)\n\t\treturn { config, source: sourceLabel }\n\t}\n\n\tassertValidResolvedOrThrow(defaultConfig, 'built-in defaults')\n\n\treturn {\n\t\tconfig: defaultConfig,\n\t\tsource: 'built-in defaults',\n\t}\n}\n\nasync function main(): Promise<void> {\n\tconst parsed = parseCliArgs(process.argv.slice(2))\n\tif (!parsed.ok) {\n\t\tconsole.error(parsed.error)\n\t\tusage()\n\t\tprocess.exit(1)\n\t}\n\n\tconst { command, configPath, outputPath, dryRun, port, noSleep, command_ } = parsed.value\n\n\tswitch (command) {\n\t\tcase 'serve': {\n\t\t\tconst loaded = await loadConfig(configPath)\n\t\t\tawait serve(loaded.config, port, command_.length > 0 ? command_ : undefined, noSleep)\n\t\t\tbreak\n\t\t}\n\n\t\tcase 'build': {\n\t\t\tconst loaded = await loadConfig(configPath)\n\t\t\tconst targetPath = outputPath\n\t\t\t\t? resolve(process.cwd(), outputPath)\n\t\t\t\t: resolve(process.cwd(), 'dist/index.html')\n\n\t\t\tif (dryRun) {\n\t\t\t\tconsole.log('Dry run: build')\n\t\t\t\tconsole.log(`- config: ${loaded.source}`)\n\t\t\t\tconsole.log(`- output: ${targetPath}`)\n\t\t\t\tconsole.log('- action: would bundle overlay, fetch ttyd base HTML, inject, and write file')\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\t// Ensure output directory exists\n\t\t\tmkdirSync(dirname(targetPath), { recursive: true })\n\n\t\t\tawait build(loaded.config, targetPath)\n\t\t\tconsole.log(`Built: ${targetPath}`)\n\t\t\tbreak\n\t\t}\n\n\t\tcase 'inject': {\n\t\t\tconst loaded = await loadConfig(configPath)\n\t\t\tif (dryRun) {\n\t\t\t\tensureInjectInputMode('remobi inject --dry-run')\n\t\t\t\tconst dryRunStdin = await readStdin()\n\t\t\t\tif (dryRunStdin.trim().length === 0) {\n\t\t\t\t\tthrow new Error('remobi inject --dry-run expects piped ttyd HTML on stdin')\n\t\t\t\t}\n\t\t\t\tconsole.log('Dry run: inject')\n\t\t\t\tconsole.log(`- config: ${loaded.source}`)\n\t\t\t\tconsole.log('- stdin: piped input detected')\n\t\t\t\tconsole.log('- action: would read stdin HTML, inject overlay, and write to stdout')\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tensureInjectInputMode('remobi inject')\n\t\t\tconst result = await injectFromStdin(loaded.config)\n\t\t\tprocess.stdout.write(result)\n\t\t\tbreak\n\t\t}\n\n\t\tcase 'init': {\n\t\t\tconst targetPath = resolve(process.cwd(), 'remobi.config.ts')\n\t\t\tif (existsSync(targetPath)) {\n\t\t\t\tconsole.error('remobi.config.ts already exists')\n\t\t\t\tprocess.exit(1)\n\t\t\t}\n\t\t\tconst template = `import { defineConfig } from 'remobi'\n\nexport default defineConfig({\n // name: 'remobi', // app name (tab title, PWA home screen label)\n // theme: 'catppuccin-mocha',\n // font: {\n // family: 'JetBrainsMono NFM, monospace',\n // mobileSizeDefault: 16,\n // sizeRange: [8, 32],\n // },\n //\n // Toolbar/drawer accept a plain array (replace) or a function (transform):\n //\n // toolbar: { row1: [{ id, label, description, action }], row2: [...] },\n //\n // drawer: {\n // buttons: [\n // { id: 'sessions', label: 'Sessions', description: 'Choose tmux session', action: { type: 'send', data: '\\\\x02s' } },\n // ],\n // },\n //\n // toolbar: {\n // row2: (defaults) => defaults.filter((b) => b.id !== 'q'),\n // },\n //\n // drawer: {\n // buttons: (defaults) => [\n // ...defaults,\n // { id: 'my-btn', label: 'X', description: 'Send x', action: { type: 'send', data: 'x' } },\n // ],\n // },\n //\n // gestures: {\n // swipe: {\n // enabled: true,\n // left: '\\\\x02n', // data sent on swipe left (default: next tmux window)\n // right: '\\\\x02p', // data sent on swipe right (default: prev tmux window)\n // leftLabel: 'Next tmux window',\n // rightLabel: 'Previous tmux window',\n // },\n // pinch: { enabled: true },\n // scroll: { strategy: 'wheel', sensitivity: 40 },\n // },\n // mobile: {\n // initData: '\\\\x02z', // send on load when viewport < widthThreshold (auto-zoom pane)\n // widthThreshold: 768, // px — default matches phone/tablet breakpoint\n // },\n // floatingButtons: [\n // // Always-visible top-left buttons (touch devices only)\n // { position: 'top-left', buttons: [{ id: 'zoom', label: 'Zoom', description: 'Toggle pane zoom', action: { type: 'send', data: '\\\\x02z' } }] },\n // ],\n // pwa: {\n // enabled: true, // enable PWA manifest + meta tags (used by remobi serve)\n // shortName: 'remobi', // short name for home screen icon (defaults to name)\n // themeColor: '#1e1e2e', // theme-color meta tag + manifest\n // },\n // reconnect: {\n // enabled: true, // show overlay + auto-reload on connection loss (default true)\n // },\n})\n`\n\t\t\twriteFileSync(targetPath, template)\n\t\t\tconsole.log(`Created: ${targetPath}`)\n\t\t\tbreak\n\t\t}\n\n\t\tcase 'version':\n\t\t\tconsole.log(VERSION)\n\t\t\tbreak\n\n\t\tcase 'help':\n\t\t\tusage()\n\t\t\tbreak\n\n\t\tdefault:\n\t\t\tconsole.error(`Unknown command: ${command}`)\n\t\t\tusage()\n\t\t\tprocess.exit(1)\n\t}\n}\n\nif (import.meta.filename === realpathSync(process.argv[1])) {\n\tmain().catch((err: unknown) => {\n\t\tconsole.error(err)\n\t\tprocess.exit(1)\n\t})\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.mjs","names":["serve","honoServe","serve"],"sources":["../src/cli/args.ts","../src/config-schema.ts","../src/config-validate.ts","../src/pwa/manifest.ts","../src/serve.ts","../cli.ts"],"sourcesContent":["type CliCommand = 'build' | 'inject' | 'init' | 'serve' | 'help' | 'version'\n\ninterface ParsedCliArgs {\n\treadonly command: CliCommand\n\treadonly configPath?: string\n\treadonly outputPath?: string\n\treadonly dryRun: boolean\n\treadonly port?: number\n\treadonly host?: string\n\treadonly noSleep: boolean\n\treadonly command_: readonly string[]\n}\n\ninterface ParseSuccess {\n\treadonly ok: true\n\treadonly value: ParsedCliArgs\n}\n\ninterface ParseFailure {\n\treadonly ok: false\n\treadonly error: string\n}\n\ntype ParseCliResult = ParseSuccess | ParseFailure\n\nfunction isHelpCommand(value: string): boolean {\n\treturn value === '--help' || value === '-h' || value === 'help'\n}\n\nfunction isVersionCommand(value: string): boolean {\n\treturn value === '--version' || value === '-v' || value === 'version'\n}\n\nfunction isMissingOptionValue(value: string | undefined): boolean {\n\treturn value === undefined || value.startsWith('-')\n}\n\nexport function parseCliArgs(args: readonly string[]): ParseCliResult {\n\tconst commandToken = args[0]\n\tif (!commandToken || isHelpCommand(commandToken)) {\n\t\treturn { ok: true, value: { command: 'help', dryRun: false, noSleep: false, command_: [] } }\n\t}\n\n\tif (isVersionCommand(commandToken)) {\n\t\treturn { ok: true, value: { command: 'version', dryRun: false, noSleep: false, command_: [] } }\n\t}\n\n\tif (\n\t\tcommandToken !== 'build' &&\n\t\tcommandToken !== 'inject' &&\n\t\tcommandToken !== 'init' &&\n\t\tcommandToken !== 'serve'\n\t) {\n\t\treturn { ok: false, error: `Unknown command: ${commandToken}` }\n\t}\n\n\tlet configPath: string | undefined\n\tlet outputPath: string | undefined\n\tlet dryRun = false\n\tlet port: number | undefined\n\tlet host: string | undefined\n\tlet noSleep = false\n\tlet trailingCommand: readonly string[] = []\n\n\tfor (let index = 1; index < args.length; index++) {\n\t\tconst arg = args[index]\n\t\tconst nextArg = args[index + 1]\n\t\tif (!arg) {\n\t\t\treturn { ok: false, error: 'Invalid argument list' }\n\t\t}\n\n\t\t// -- separator: everything after is the trailing command\n\t\tif (arg === '--') {\n\t\t\ttrailingCommand = args.slice(index + 1)\n\t\t\tbreak\n\t\t}\n\n\t\tif (arg === '--help' || arg === '-h') {\n\t\t\treturn { ok: true, value: { command: 'help', dryRun: false, noSleep: false, command_: [] } }\n\t\t}\n\n\t\tif (!arg.startsWith('-')) {\n\t\t\treturn { ok: false, error: `Unexpected positional argument: ${arg}` }\n\t\t}\n\n\t\tif (arg === '--config' || arg === '-c') {\n\t\t\tif (commandToken !== 'build' && commandToken !== 'inject' && commandToken !== 'serve') {\n\t\t\t\treturn { ok: false, error: `${arg} is only valid for 'build', 'inject', or 'serve'` }\n\t\t\t}\n\t\t\tif (isMissingOptionValue(nextArg)) {\n\t\t\t\treturn { ok: false, error: 'Missing value for --config' }\n\t\t\t}\n\t\t\tconfigPath = nextArg\n\t\t\tindex++\n\t\t\tcontinue\n\t\t}\n\n\t\tif (arg === '--output' || arg === '-o') {\n\t\t\tif (commandToken !== 'build') {\n\t\t\t\treturn { ok: false, error: `${arg} is only valid for 'build'` }\n\t\t\t}\n\t\t\tif (isMissingOptionValue(nextArg)) {\n\t\t\t\treturn { ok: false, error: 'Missing value for --output' }\n\t\t\t}\n\t\t\toutputPath = nextArg\n\t\t\tindex++\n\t\t\tcontinue\n\t\t}\n\n\t\tif (arg === '--dry-run' || arg === '-n') {\n\t\t\tif (commandToken !== 'build' && commandToken !== 'inject') {\n\t\t\t\treturn { ok: false, error: `${arg} is only valid for 'build' or 'inject'` }\n\t\t\t}\n\t\t\tdryRun = true\n\t\t\tcontinue\n\t\t}\n\n\t\tif (arg === '--port' || arg === '-p') {\n\t\t\tif (commandToken !== 'serve') {\n\t\t\t\treturn { ok: false, error: `${arg} is only valid for 'serve'` }\n\t\t\t}\n\t\t\tif (isMissingOptionValue(nextArg)) {\n\t\t\t\treturn { ok: false, error: 'Missing value for --port' }\n\t\t\t}\n\t\t\tconst portNum = Number(nextArg)\n\t\t\tif (!Number.isInteger(portNum) || portNum < 1 || portNum > 65535) {\n\t\t\t\treturn { ok: false, error: `Invalid port: ${nextArg}` }\n\t\t\t}\n\t\t\tport = portNum\n\t\t\tindex++\n\t\t\tcontinue\n\t\t}\n\n\t\tif (arg === '--host') {\n\t\t\tif (commandToken !== 'serve') {\n\t\t\t\treturn { ok: false, error: `${arg} is only valid for 'serve'` }\n\t\t\t}\n\t\t\tif (isMissingOptionValue(nextArg)) {\n\t\t\t\treturn { ok: false, error: 'Missing value for --host' }\n\t\t\t}\n\t\t\thost = nextArg\n\t\t\tindex++\n\t\t\tcontinue\n\t\t}\n\n\t\tif (arg === '--no-sleep') {\n\t\t\tif (commandToken !== 'serve') {\n\t\t\t\treturn { ok: false, error: `${arg} is only valid for 'serve'` }\n\t\t\t}\n\t\t\tnoSleep = true\n\t\t\tcontinue\n\t\t}\n\n\t\tif (isVersionCommand(arg)) {\n\t\t\treturn { ok: false, error: `${arg} is only valid as a top-level command` }\n\t\t}\n\n\t\treturn { ok: false, error: `Unknown flag: ${arg}` }\n\t}\n\n\treturn {\n\t\tok: true,\n\t\tvalue: {\n\t\t\tcommand: commandToken,\n\t\t\tconfigPath,\n\t\t\toutputPath,\n\t\t\tdryRun,\n\t\t\tport,\n\t\t\thost,\n\t\t\tnoSleep,\n\t\t\tcommand_: trailingCommand,\n\t\t},\n\t}\n}\n","/**\n * Valibot schemas for remobi config validation.\n * Only used at CLI time (build/inject/serve) — never in the browser bundle.\n */\nimport * as v from 'valibot'\n\n// --- Primitives ---\n\nconst finiteNumber = v.pipe(v.number(), v.finite())\n\n// --- Button action (discriminated union) ---\n\nconst sendActionSchema = v.strictObject({\n\ttype: v.literal('send'),\n\tdata: v.string(),\n\tkeyLabel: v.optional(v.string()),\n})\n\nconst ctrlModifierActionSchema = v.strictObject({ type: v.literal('ctrl-modifier') })\nconst pasteActionSchema = v.strictObject({ type: v.literal('paste') })\nconst comboPickerActionSchema = v.strictObject({ type: v.literal('combo-picker') })\nconst drawerToggleActionSchema = v.strictObject({ type: v.literal('drawer-toggle') })\n\nconst buttonActionSchema = v.variant('type', [\n\tsendActionSchema,\n\tctrlModifierActionSchema,\n\tpasteActionSchema,\n\tcomboPickerActionSchema,\n\tdrawerToggleActionSchema,\n])\n\n// --- Control button ---\n\nconst controlButtonSchema = v.strictObject({\n\tid: v.string(),\n\tlabel: v.string(),\n\tdescription: v.string(),\n\taction: buttonActionSchema,\n})\n\n// --- Button array input (array | function) ---\n// Uses v.custom for type check + v.rawCheck for deep array validation,\n// avoiding v.union which loses path context for nested issues.\n\nconst buttonArrayInputSchema = v.pipe(\n\tv.custom<readonly Record<string, unknown>[] | ((...args: readonly unknown[]) => unknown)>(\n\t\t(input) => Array.isArray(input) || typeof input === 'function',\n\t\t'array or function',\n\t),\n\tv.rawCheck(({ dataset, addIssue }) => {\n\t\tif (!dataset.typed || !Array.isArray(dataset.value)) return\n\t\tconst arr = dataset.value\n\t\tfor (let i = 0; i < arr.length; i++) {\n\t\t\tconst result = v.safeParse(controlButtonSchema, arr[i])\n\t\t\tif (!result.success) {\n\t\t\t\tfor (const issue of result.issues) {\n\t\t\t\t\taddIssue({\n\t\t\t\t\t\tmessage: issue.message,\n\t\t\t\t\t\texpected: issue.expected,\n\t\t\t\t\t\treceived: issue.received,\n\t\t\t\t\t\tpath: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttype: 'array',\n\t\t\t\t\t\t\t\torigin: 'value',\n\t\t\t\t\t\t\t\tinput: arr,\n\t\t\t\t\t\t\t\tkey: i,\n\t\t\t\t\t\t\t\t// oxlint-disable-next-line typescript/consistent-type-assertions -- Valibot path segment requires typed value\n\t\t\t\t\t\t\t\tvalue: arr[i] as Record<string, unknown>,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t...(issue.path ?? []),\n\t\t\t\t\t\t],\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}),\n)\n\n// --- Theme ---\n\nconst themeColourSchema = v.optional(v.string())\n\nconst termThemeOverridesSchema = v.strictObject({\n\tbackground: themeColourSchema,\n\tforeground: themeColourSchema,\n\tcursor: themeColourSchema,\n\tcursorAccent: themeColourSchema,\n\tselectionBackground: themeColourSchema,\n\tblack: themeColourSchema,\n\tred: themeColourSchema,\n\tgreen: themeColourSchema,\n\tyellow: themeColourSchema,\n\tblue: themeColourSchema,\n\tmagenta: themeColourSchema,\n\tcyan: themeColourSchema,\n\twhite: themeColourSchema,\n\tbrightBlack: themeColourSchema,\n\tbrightRed: themeColourSchema,\n\tbrightGreen: themeColourSchema,\n\tbrightYellow: themeColourSchema,\n\tbrightBlue: themeColourSchema,\n\tbrightMagenta: themeColourSchema,\n\tbrightCyan: themeColourSchema,\n\tbrightWhite: themeColourSchema,\n})\n\nconst termThemeResolvedSchema = v.strictObject({\n\tbackground: v.string(),\n\tforeground: v.string(),\n\tcursor: v.string(),\n\tcursorAccent: v.string(),\n\tselectionBackground: v.string(),\n\tblack: v.string(),\n\tred: v.string(),\n\tgreen: v.string(),\n\tyellow: v.string(),\n\tblue: v.string(),\n\tmagenta: v.string(),\n\tcyan: v.string(),\n\twhite: v.string(),\n\tbrightBlack: v.string(),\n\tbrightRed: v.string(),\n\tbrightGreen: v.string(),\n\tbrightYellow: v.string(),\n\tbrightBlue: v.string(),\n\tbrightMagenta: v.string(),\n\tbrightCyan: v.string(),\n\tbrightWhite: v.string(),\n})\n\n// --- Font ---\n\nconst fontOverridesSchema = v.strictObject({\n\tfamily: v.optional(v.string()),\n\tcdnUrl: v.optional(v.string()),\n\tmobileSizeDefault: v.optional(finiteNumber),\n\tsizeRange: v.optional(v.pipe(v.tuple([finiteNumber, finiteNumber]))),\n})\n\nconst fontResolvedSchema = v.strictObject({\n\tfamily: v.string(),\n\tcdnUrl: v.string(),\n\tmobileSizeDefault: finiteNumber,\n\tsizeRange: v.pipe(v.tuple([finiteNumber, finiteNumber])),\n})\n\n// --- Gestures ---\n\nconst swipeOverridesSchema = v.strictObject({\n\tenabled: v.optional(v.boolean()),\n\tthreshold: v.optional(finiteNumber),\n\tmaxDuration: v.optional(finiteNumber),\n\tleft: v.optional(v.string()),\n\tright: v.optional(v.string()),\n\tleftLabel: v.optional(v.string()),\n\trightLabel: v.optional(v.string()),\n})\n\nconst swipeResolvedSchema = v.strictObject({\n\tenabled: v.boolean(),\n\tthreshold: finiteNumber,\n\tmaxDuration: finiteNumber,\n\tleft: v.string(),\n\tright: v.string(),\n\tleftLabel: v.string(),\n\trightLabel: v.string(),\n})\n\nconst pinchOverridesSchema = v.strictObject({\n\tenabled: v.optional(v.boolean()),\n})\n\nconst pinchResolvedSchema = v.strictObject({\n\tenabled: v.boolean(),\n})\n\nconst scrollStrategySchema = v.picklist(['keys', 'wheel'])\n\nconst scrollOverridesSchema = v.strictObject({\n\tenabled: v.optional(v.boolean()),\n\tsensitivity: v.optional(finiteNumber),\n\tstrategy: v.optional(scrollStrategySchema),\n\twheelIntervalMs: v.optional(finiteNumber),\n})\n\nconst scrollResolvedSchema = v.strictObject({\n\tenabled: v.boolean(),\n\tsensitivity: finiteNumber,\n\tstrategy: scrollStrategySchema,\n\twheelIntervalMs: finiteNumber,\n})\n\nconst gestureOverridesSchema = v.strictObject({\n\tswipe: v.optional(swipeOverridesSchema),\n\tpinch: v.optional(pinchOverridesSchema),\n\tscroll: v.optional(scrollOverridesSchema),\n})\n\nconst gestureResolvedSchema = v.strictObject({\n\tswipe: swipeResolvedSchema,\n\tpinch: pinchResolvedSchema,\n\tscroll: scrollResolvedSchema,\n})\n\n// --- Mobile ---\n\nconst mobileOverridesSchema = v.strictObject({\n\tinitData: v.optional(v.nullable(v.string())),\n\twidthThreshold: v.optional(finiteNumber),\n})\n\nconst mobileResolvedSchema = v.strictObject({\n\tinitData: v.nullable(v.string()),\n\twidthThreshold: finiteNumber,\n})\n\n// --- Floating buttons ---\n\nconst floatingPositionSchema = v.picklist([\n\t'top-left',\n\t'top-right',\n\t'top-centre',\n\t'bottom-left',\n\t'bottom-right',\n\t'bottom-centre',\n\t'centre-left',\n\t'centre-right',\n])\n\nconst floatingDirectionSchema = v.picklist(['row', 'column'])\n\nconst floatingButtonGroupSchema = v.strictObject({\n\tposition: floatingPositionSchema,\n\tdirection: v.optional(floatingDirectionSchema),\n\tbuttons: v.array(controlButtonSchema),\n})\n\n// --- PWA ---\n\nconst pwaOverridesSchema = v.strictObject({\n\tenabled: v.optional(v.boolean()),\n\tshortName: v.optional(v.string()),\n\tthemeColor: v.optional(v.string()),\n})\n\nconst pwaResolvedSchema = v.strictObject({\n\tenabled: v.boolean(),\n\tshortName: v.optional(v.string()),\n\tthemeColor: v.string(),\n})\n\n// --- Reconnect ---\n\nconst reconnectOverridesSchema = v.strictObject({\n\tenabled: v.optional(v.boolean()),\n})\n\nconst reconnectResolvedSchema = v.strictObject({\n\tenabled: v.boolean(),\n})\n\n// --- Top-level schemas ---\n\n/** Schema for config overrides (all fields optional, button arrays accept array | function) */\nexport const remobiConfigOverridesSchema = v.strictObject({\n\tname: v.optional(v.string()),\n\ttheme: v.optional(termThemeOverridesSchema),\n\tfont: v.optional(fontOverridesSchema),\n\ttoolbar: v.optional(\n\t\tv.strictObject({\n\t\t\trow1: v.optional(buttonArrayInputSchema),\n\t\t\trow2: v.optional(buttonArrayInputSchema),\n\t\t}),\n\t),\n\tdrawer: v.optional(\n\t\tv.strictObject({\n\t\t\tbuttons: v.optional(buttonArrayInputSchema),\n\t\t}),\n\t),\n\tgestures: v.optional(gestureOverridesSchema),\n\tmobile: v.optional(mobileOverridesSchema),\n\tfloatingButtons: v.optional(v.array(floatingButtonGroupSchema)),\n\tpwa: v.optional(pwaOverridesSchema),\n\treconnect: v.optional(reconnectOverridesSchema),\n})\n\n/** Schema for fully resolved config (all required fields, plain button arrays) */\nexport const remobiConfigResolvedSchema = v.strictObject({\n\tname: v.string(),\n\ttheme: termThemeResolvedSchema,\n\tfont: fontResolvedSchema,\n\ttoolbar: v.strictObject({\n\t\trow1: v.array(controlButtonSchema),\n\t\trow2: v.array(controlButtonSchema),\n\t}),\n\tdrawer: v.strictObject({\n\t\tbuttons: v.array(controlButtonSchema),\n\t}),\n\tgestures: gestureResolvedSchema,\n\tmobile: mobileResolvedSchema,\n\tfloatingButtons: v.array(floatingButtonGroupSchema),\n\tpwa: pwaResolvedSchema,\n\treconnect: reconnectResolvedSchema,\n})\n","import * as v from 'valibot'\nimport { remobiConfigOverridesSchema, remobiConfigResolvedSchema } from './config-schema'\nimport type { RemobiConfig, RemobiConfigOverrides } from './types'\n\ninterface ValidationIssue {\n\treadonly path: string\n\treadonly expected: string\n\treadonly received: string\n}\n\nexport class ConfigValidationError extends Error {\n\treadonly issues: readonly ValidationIssue[]\n\n\tconstructor(issues: readonly ValidationIssue[]) {\n\t\tsuper(formatIssues(issues))\n\t\tthis.name = 'ConfigValidationError'\n\t\tthis.issues = issues\n\t}\n}\n\nfunction formatIssues(issues: readonly ValidationIssue[]): string {\n\tconst lines = ['Invalid remobi config:']\n\tfor (const issue of issues) {\n\t\tlines.push(`- ${issue.path}: expected ${issue.expected}, received ${issue.received}`)\n\t}\n\treturn lines.join('\\n')\n}\n\nfunction truncate(value: string, maxLength: number): string {\n\tif (value.length <= maxLength) {\n\t\treturn value\n\t}\n\treturn `${value.slice(0, maxLength - 3)}...`\n}\n\nfunction describeReceived(value: unknown): string {\n\tif (value === null) return 'null'\n\tif (Array.isArray(value)) return `array(len=${value.length})`\n\tif (typeof value === 'string') return `string(${JSON.stringify(truncate(value, 80))})`\n\tif (typeof value === 'number') return `number(${String(value)})`\n\tif (typeof value === 'boolean') return `boolean(${String(value)})`\n\tif (typeof value === 'bigint') return `bigint(${String(value)})`\n\tif (typeof value === 'undefined') return 'undefined'\n\tif (typeof value === 'function')\n\t\treturn value.name.length > 0 ? `function(${value.name})` : 'function'\n\tif (typeof value === 'object') {\n\t\tconst keys = Object.keys(value)\n\t\tif (keys.length === 0) return 'object(empty)'\n\t\tconst shown = keys.slice(0, 3).join(', ')\n\t\tconst suffix = keys.length > 3 ? ', ...' : ''\n\t\treturn `object(keys: ${shown}${suffix})`\n\t}\n\treturn typeof value\n}\n\n/** Convert Valibot issue path to dotted string */\nfunction issuePath(issue: v.BaseIssue<unknown>): string {\n\tif (!issue.path || issue.path.length === 0) return 'config'\n\tconst segments: string[] = ['config']\n\tfor (const segment of issue.path) {\n\t\tif (typeof segment.key === 'number') {\n\t\t\tsegments.push(`[${String(segment.key)}]`)\n\t\t} else {\n\t\t\tsegments.push(`.${String(segment.key)}`)\n\t\t}\n\t}\n\treturn segments.join('').replaceAll('.[', '[')\n}\n\n/** Extract human-readable expected string from a Valibot issue */\nfunction issueExpected(issue: v.BaseIssue<unknown>): string {\n\t// v.custom() sets expected to \"unknown\" — use the message instead\n\tif (issue.expected === 'unknown' || !issue.expected) {\n\t\treturn issue.message\n\t}\n\treturn issue.expected\n}\n\n/** Map Valibot flat issues to our ValidationIssue format */\nfunction toValidationIssues(issues: readonly v.BaseIssue<unknown>[]): ValidationIssue[] {\n\tconst result: ValidationIssue[] = []\n\tfor (const issue of issues) {\n\t\t// Leaf issues only — skip container issues that have nested issues\n\t\tif (issue.issues && issue.issues.length > 0) {\n\t\t\tresult.push(...toValidationIssues(issue.issues))\n\t\t\tcontinue\n\t\t}\n\t\tconst received = issue.input !== undefined ? issue.input : undefined\n\t\tresult.push({\n\t\t\tpath: issuePath(issue),\n\t\t\texpected: issueExpected(issue),\n\t\t\treceived: describeReceived(received),\n\t\t})\n\t}\n\treturn result\n}\n\nexport function assertValidConfigOverrides(value: unknown): asserts value is RemobiConfigOverrides {\n\tconst result = v.safeParse(remobiConfigOverridesSchema, value)\n\tif (!result.success) {\n\t\tthrow new ConfigValidationError(toValidationIssues(result.issues))\n\t}\n}\n\nexport function assertValidResolvedConfig(value: unknown): asserts value is RemobiConfig {\n\tconst result = v.safeParse(remobiConfigResolvedSchema, value)\n\tif (!result.success) {\n\t\tthrow new ConfigValidationError(toValidationIssues(result.issues))\n\t}\n}\n","import type { PwaConfig } from '../types'\n\ninterface WebAppManifest {\n\treadonly name: string\n\treadonly short_name: string\n\treadonly start_url: string\n\treadonly display: string\n\treadonly background_color: string\n\treadonly theme_color: string\n\treadonly icons: readonly {\n\t\treadonly src: string\n\t\treadonly sizes: string\n\t\treadonly type: string\n\t\treadonly purpose?: string\n\t}[]\n}\n\n/** Generate a web app manifest object from pwa config */\nexport function generateManifest(name: string, pwa: PwaConfig): WebAppManifest {\n\treturn {\n\t\tname,\n\t\tshort_name: pwa.shortName ?? name,\n\t\tstart_url: '/',\n\t\tdisplay: 'standalone',\n\t\tbackground_color: pwa.themeColor,\n\t\ttheme_color: pwa.themeColor,\n\t\ticons: [\n\t\t\t{ src: '/icon-192.png', sizes: '192x192', type: 'image/png', purpose: 'any maskable' },\n\t\t\t{ src: '/icon-512.png', sizes: '512x512', type: 'image/png' },\n\t\t],\n\t}\n}\n\n/** Serialise manifest to JSON string */\nexport function manifestToJson(name: string, pwa: PwaConfig): string {\n\treturn JSON.stringify(generateManifest(name, pwa), null, 2)\n}\n","import { existsSync, readFileSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport { serve as honoServe } from '@hono/node-server'\nimport { createNodeWebSocket } from '@hono/node-ws'\nimport { Hono } from 'hono'\nimport type { WSContext } from 'hono/ws'\nimport WebSocket from 'ws'\nimport { bundleOverlay, injectOverlay } from '../build'\nimport { serialiseThemeForTtyd } from './config'\nimport { manifestToJson } from './pwa/manifest'\nimport type { RemobiConfig } from './types'\nimport { sleep, spawnProcess } from './util/node-compat'\nimport type { SpawnedProcess } from './util/node-compat'\n\nconst DEFAULT_PORT = 7681\nconst DEFAULT_HOST = '127.0.0.1'\nconst DEFAULT_COMMAND = ['tmux', 'new-session', '-A', '-s', 'main']\n// Walk up from module location to find package root, then resolve icons\nfunction findIconsDir(): string {\n\tlet dir = import.meta.dirname\n\tfor (let i = 0; i < 5; i++) {\n\t\tconst candidate = resolve(dir, 'src/pwa/icons')\n\t\tif (existsSync(candidate)) return candidate\n\t\tdir = dirname(dir)\n\t}\n\t// Fallback for source layout (running from src/)\n\treturn resolve(import.meta.dirname, 'pwa/icons')\n}\n\nconst ICONS_DIR = findIconsDir()\n\ninterface WsData {\n\tbackend: WebSocket | null\n\tbuffer: (string | Uint8Array)[]\n}\n\nconst RESPONSE_SECURITY_HEADERS = {\n\t'content-security-policy':\n\t\t\"frame-ancestors 'none'; base-uri 'none'; form-action 'self'; object-src 'none'\",\n\t'x-frame-options': 'DENY',\n\t'x-content-type-options': 'nosniff',\n\t'referrer-policy': 'no-referrer',\n\t'cross-origin-resource-policy': 'same-origin',\n\t'permissions-policy': 'camera=(), microphone=(), geolocation=()',\n} as const\n\nconst STRIPPED_PROXY_REQUEST_HEADERS = new Set([\n\t'connection',\n\t'content-length',\n\t'host',\n\t'keep-alive',\n\t'origin',\n\t'proxy-authenticate',\n\t'proxy-authorization',\n\t'te',\n\t'trailer',\n\t'transfer-encoding',\n\t'upgrade',\n])\n\nexport function isLoopbackHost(host: string): boolean {\n\treturn host === '127.0.0.1' || host === '::1' || host === 'localhost'\n}\n\nexport function isAllowedWebSocketOrigin(\n\toriginHeader: string | undefined,\n\thostHeader: string | undefined,\n): boolean {\n\tif (originHeader === undefined) {\n\t\treturn true\n\t}\n\tif (hostHeader === undefined) {\n\t\treturn false\n\t}\n\n\ttry {\n\t\treturn new URL(originHeader).host === hostHeader\n\t} catch {\n\t\treturn false\n\t}\n}\n\nexport function buildProxyRequestHeaders(source: Headers): Headers {\n\tconst headers = new Headers()\n\n\tfor (const [name, value] of source.entries()) {\n\t\tif (STRIPPED_PROXY_REQUEST_HEADERS.has(name.toLowerCase())) {\n\t\t\tcontinue\n\t\t}\n\t\theaders.append(name, value)\n\t}\n\n\treturn headers\n}\n\nexport function withSecurityHeaders(response: Response): Response {\n\tconst headers = new Headers(response.headers)\n\n\tfor (const [name, value] of Object.entries(RESPONSE_SECURITY_HEADERS)) {\n\t\theaders.set(name, value)\n\t}\n\n\treturn new Response(response.body, {\n\t\tstatus: response.status,\n\t\tstatusText: response.statusText,\n\t\theaders,\n\t})\n}\n\n/** Poll until ttyd is accepting connections on the given port */\nasync function waitForTtyd(port: number, retries = 40, intervalMs = 200): Promise<void> {\n\tfor (let i = 0; i < retries; i++) {\n\t\ttry {\n\t\t\tconst resp = await fetch(`http://127.0.0.1:${port}/`)\n\t\t\tif (resp.ok) return\n\t\t} catch {\n\t\t\t// not ready yet\n\t\t}\n\t\tawait sleep(intervalMs)\n\t}\n\tthrow new Error(\n\t\t`ttyd did not start on port ${port} — is ttyd installed and on PATH?\\nInstall ttyd: macOS \\`brew install ttyd\\`; Linux use your distro package manager or build from source: https://github.com/tsl0922/ttyd#installation`,\n\t)\n}\n\n/** Pick a random internal port */\nexport function randomInternalPort(): number {\n\treturn 19000 + Math.floor(Math.random() * 1000)\n}\n\n/** Build ttyd args from remobi config */\nexport function buildTtydArgs(\n\tconfig: RemobiConfig,\n\tinternalPort: number,\n\tcommand: readonly string[],\n): string[] {\n\treturn [\n\t\t'--writable',\n\t\t'-i',\n\t\t'127.0.0.1',\n\t\t'--port',\n\t\tString(internalPort),\n\t\t'-t',\n\t\t`theme=${serialiseThemeForTtyd(config)}`,\n\t\t'-t',\n\t\t`fontFamily=\"${config.font.family}\"`,\n\t\t'-t',\n\t\t'scrollSensitivity=3',\n\t\t'-t',\n\t\t'disableLeaveAlert=true',\n\t\t...command,\n\t]\n}\n\n/** Read a PNG icon, returns undefined if not found */\nfunction readIcon(filename: string): Uint8Array | undefined {\n\ttry {\n\t\treturn readFileSync(resolve(ICONS_DIR, filename))\n\t} catch {\n\t\treturn undefined\n\t}\n}\n\n/** Spawn caffeinate to prevent system sleep while ttyd is running (macOS only).\n * Uses -s (system sleep on AC) and -w <pid> so the assertion drops when ttyd exits. */\nfunction spawnCaffeinate(pid: number): SpawnedProcess | null {\n\ttry {\n\t\tconst proc = spawnProcess(['caffeinate', '-s', '-w', String(pid)], {\n\t\t\tstdout: 'ignore',\n\t\t\tstderr: 'ignore',\n\t\t})\n\t\t// Catch async spawn errors (e.g. caffeinate not found on Linux)\n\t\tproc.exited.catch(() => {\n\t\t\tconsole.warn('remobi: --no-sleep requires caffeinate (macOS only), ignoring')\n\t\t})\n\t\tconsole.log(`remobi: sleep prevention active (caffeinate -s -w ${pid})`)\n\t\treturn proc\n\t} catch {\n\t\tconsole.warn('remobi: --no-sleep requires caffeinate (macOS only), ignoring')\n\t\treturn null\n\t}\n}\n\n/** Start remobi serve: builds overlay in memory, manages ttyd, serves HTTP + WS */\nexport async function serve(\n\tconfig: RemobiConfig,\n\tport: number = DEFAULT_PORT,\n\tcommand: readonly string[] = DEFAULT_COMMAND,\n\tnoSleep = false,\n\thost: string = DEFAULT_HOST,\n): Promise<void> {\n\tconsole.log('remobi: building overlay...')\n\tconst { js, css } = await bundleOverlay(config)\n\n\tconst internalPort = randomInternalPort()\n\tconst ttydArgs = buildTtydArgs(config, internalPort, command)\n\n\tconsole.log(`remobi: starting ttyd on internal port ${internalPort}...`)\n\tconst ttydProc = spawnProcess(['ttyd', ...ttydArgs], {\n\t\tstdout: 'ignore',\n\t\tstderr: 'ignore',\n\t})\n\n\tconst caffeinateProc = noSleep && ttydProc.pid ? spawnCaffeinate(ttydProc.pid) : null\n\n\tawait waitForTtyd(internalPort)\n\n\tconst baseResp = await fetch(`http://127.0.0.1:${internalPort}/`)\n\tconst baseHtml = await baseResp.text()\n\tconst html = injectOverlay(baseHtml, js, css, config)\n\n\tconsole.log('remobi: overlay ready')\n\n\tconst manifestJson = config.pwa.enabled ? manifestToJson(config.name, config.pwa) : null\n\tconst icon180 = readIcon('icon-180.png')\n\tconst icon192 = readIcon('icon-192.png')\n\tconst icon512 = readIcon('icon-512.png')\n\n\t// Per-connection data via WeakMap (replaces Bun's ws.data)\n\tconst connections = new WeakMap<WebSocket, WsData>()\n\n\tconst app = new Hono()\n\tconst { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app })\n\n\tapp.use('/ws', async (c, next) => {\n\t\tif (!isAllowedWebSocketOrigin(c.req.header('origin'), c.req.header('host'))) {\n\t\t\treturn withSecurityHeaders(c.text('Forbidden', 403))\n\t\t}\n\t\tawait next()\n\t})\n\n\tapp.get(\n\t\t'/ws',\n\t\tupgradeWebSocket(() => ({\n\t\t\tonOpen(_event: Event, ws: WSContext<WebSocket>) {\n\t\t\t\tconst raw = ws.raw\n\t\t\t\tif (!raw) return\n\n\t\t\t\tconst data: WsData = { backend: null, buffer: [] }\n\t\t\t\tconnections.set(raw, data)\n\n\t\t\t\tconst backend = new WebSocket(`ws://127.0.0.1:${internalPort}/ws`, ['tty'])\n\t\t\t\tbackend.binaryType = 'arraybuffer'\n\t\t\t\tdata.backend = backend\n\n\t\t\t\tbackend.on('open', () => {\n\t\t\t\t\tfor (const msg of data.buffer) {\n\t\t\t\t\t\tbackend.send(msg)\n\t\t\t\t\t}\n\t\t\t\t\tdata.buffer = []\n\t\t\t\t})\n\n\t\t\t\tbackend.on('message', (message: WebSocket.RawData, isBinary: boolean) => {\n\t\t\t\t\tif (isBinary && message instanceof ArrayBuffer) {\n\t\t\t\t\t\tws.send(new Uint8Array(message))\n\t\t\t\t\t} else {\n\t\t\t\t\t\tws.send(message.toString())\n\t\t\t\t\t}\n\t\t\t\t})\n\n\t\t\t\tbackend.on('error', () => {\n\t\t\t\t\tws.close()\n\t\t\t\t})\n\n\t\t\t\tbackend.on('close', () => {\n\t\t\t\t\tws.close()\n\t\t\t\t})\n\t\t\t},\n\t\t\tonMessage(event: MessageEvent, ws: WSContext<WebSocket>) {\n\t\t\t\tconst raw = ws.raw\n\t\t\t\tif (!raw) return\n\t\t\t\tconst data = connections.get(raw)\n\t\t\t\tif (!data) return\n\n\t\t\t\tconst { backend, buffer } = data\n\t\t\t\tif (backend !== null && backend.readyState === WebSocket.OPEN) {\n\t\t\t\t\t// oxlint-disable-next-line typescript/consistent-type-assertions -- WSMessageReceive union needs narrowing for ws.send()\n\t\t\t\t\tbackend.send(event.data as string | ArrayBuffer)\n\t\t\t\t} else {\n\t\t\t\t\tconst msg = event.data\n\t\t\t\t\t// oxlint-disable-next-line typescript/consistent-type-assertions -- WSMessageReceive union needs narrowing for Uint8Array ctor\n\t\t\t\t\tbuffer.push(typeof msg === 'string' ? msg : new Uint8Array(msg as ArrayBuffer))\n\t\t\t\t}\n\t\t\t},\n\t\t\tonClose(_event: CloseEvent, ws: WSContext<WebSocket>) {\n\t\t\t\tconst raw = ws.raw\n\t\t\t\tif (!raw) return\n\t\t\t\tconnections.get(raw)?.backend?.close()\n\t\t\t\tconnections.delete(raw)\n\t\t\t},\n\t\t})),\n\t)\n\n\tapp.get('/', (c) => withSecurityHeaders(c.html(html)))\n\n\tif (manifestJson !== null) {\n\t\tapp.get('/manifest.json', (c) => {\n\t\t\t// oxlint-disable-next-line typescript/consistent-type-assertions -- JSON.parse returns unknown, safe for manifest\n\t\t\treturn withSecurityHeaders(c.json(JSON.parse(manifestJson) as Record<string, unknown>))\n\t\t})\n\t}\n\n\tif (icon180) {\n\t\tapp.get('/apple-touch-icon.png', () => {\n\t\t\treturn withSecurityHeaders(\n\t\t\t\tnew Response(Uint8Array.from(icon180), {\n\t\t\t\t\theaders: { 'content-type': 'image/png' },\n\t\t\t\t}),\n\t\t\t)\n\t\t})\n\t}\n\n\tif (icon192) {\n\t\tapp.get('/icon-192.png', () => {\n\t\t\treturn withSecurityHeaders(\n\t\t\t\tnew Response(Uint8Array.from(icon192), {\n\t\t\t\t\theaders: { 'content-type': 'image/png' },\n\t\t\t\t}),\n\t\t\t)\n\t\t})\n\t}\n\n\tif (icon512) {\n\t\tapp.get('/icon-512.png', () => {\n\t\t\treturn withSecurityHeaders(\n\t\t\t\tnew Response(Uint8Array.from(icon512), {\n\t\t\t\t\theaders: { 'content-type': 'image/png' },\n\t\t\t\t}),\n\t\t\t)\n\t\t})\n\t}\n\n\t// Proxy remaining requests to ttyd (e.g. /token)\n\tapp.all('/*', async (c) => {\n\t\tconst url = new URL(c.req.url)\n\t\tconst backendUrl = `http://127.0.0.1:${internalPort}${url.pathname}${url.search}`\n\t\tconst resp = await fetch(backendUrl, {\n\t\t\tmethod: c.req.method,\n\t\t\theaders: buildProxyRequestHeaders(c.req.raw.headers),\n\t\t\tbody: c.req.raw.body,\n\t\t})\n\t\treturn withSecurityHeaders(\n\t\t\tnew Response(resp.body, {\n\t\t\t\tstatus: resp.status,\n\t\t\t\theaders: resp.headers,\n\t\t\t}),\n\t\t)\n\t})\n\n\tconst server = honoServe({ fetch: app.fetch, port, hostname: host })\n\tinjectWebSocket(server)\n\n\tconsole.log(`remobi: serving on http://${isLoopbackHost(host) ? 'localhost' : host}:${port}`)\n\tif (!isLoopbackHost(host)) {\n\t\tconsole.warn(`remobi: warning: --host ${host} exposes terminal control beyond localhost`)\n\t}\n\n\t// Clean shutdown on SIGINT / SIGTERM\n\tconst cleanup = () => {\n\t\tconsole.log('\\nremobi: shutting down...')\n\t\tserver.close()\n\t\tttydProc.kill()\n\t\tcaffeinateProc?.kill()\n\t\tprocess.exit(0)\n\t}\n\n\tprocess.on('SIGINT', cleanup)\n\tprocess.on('SIGTERM', cleanup)\n\n\t// Keep process alive until ttyd exits\n\tawait ttydProc.exited\n\tserver.close()\n}\n","#!/usr/bin/env node\nimport { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { dirname, join, resolve } from 'node:path'\nimport { build, injectFromStdin } from './build'\nimport { parseCliArgs } from './src/cli/args'\nimport { defaultConfig, defineConfig, mergeConfig } from './src/config'\nimport {\n\tConfigValidationError,\n\tassertValidConfigOverrides,\n\tassertValidResolvedConfig,\n} from './src/config-validate'\nimport { serve } from './src/serve'\nimport type { RemobiConfig, RemobiConfigOverrides } from './src/types'\nimport { readStdin } from './src/util/node-compat'\n\n// Walk up from module location to find package.json — works from both source and dist/\nfunction loadPackageVersion(): string {\n\tlet dir = import.meta.dirname\n\tfor (let i = 0; i < 5; i++) {\n\t\ttry {\n\t\t\tconst content = readFileSync(resolve(dir, 'package.json'), 'utf-8')\n\t\t\t// oxlint-disable-next-line typescript/consistent-type-assertions -- JSON.parse returns unknown\n\t\t\treturn (JSON.parse(content) as { version: string }).version\n\t\t} catch {\n\t\t\tdir = dirname(dir)\n\t\t}\n\t}\n\treturn '0.0.0'\n}\n\nconst VERSION: string = loadPackageVersion()\n\nfunction usage(): void {\n\tconsole.log(`remobi v${VERSION} — mobile-friendly terminal overlay for ttyd + tmux\n\nUsage:\n remobi serve [--config <path>] [--port <n>] [--host <addr>] [--no-sleep] [-- <command...>]\n Build overlay in memory, manage ttyd, serve with PWA support.\n Default host: 127.0.0.1. Default port: 7681. Default command: tmux new-session -A -s main\n\n remobi build [--config <path>] [--output <path>] [--dry-run]\n Build patched index.html for ttyd --index flag.\n Starts temp ttyd, fetches base HTML, injects overlay.\n\n remobi inject [--config <path>] [--dry-run]\n Pipe mode: reads ttyd HTML from stdin, outputs patched HTML to stdout.\n\n remobi init\n Scaffold a remobi.config.ts with defaults.\n\n remobi --version\n Print version.\n\n remobi --help\n Show this help.\n\nFlags:\n -c, --config <path> Use a specific config file (build/inject/serve)\n -o, --output <path> Build output path (build only)\n -p, --port <n> Port to serve on (serve only, default 7681)\n --host <addr> Host/interface to bind (serve only, default 127.0.0.1)\n -n, --dry-run Validate + print plan only (build/inject)\n --no-sleep Prevent macOS sleep while serving (caffeinate -s, serve only)\n\nExamples:\n remobi serve\n remobi serve --no-sleep\n remobi serve --host 0.0.0.0 --port 8080\n remobi serve --port 8080 -- tmux new -As dev\n remobi build -c ./remobi.config.ts -o ./dist/index.html\n remobi build --dry-run\n curl -s http://127.0.0.1:7681/ | remobi inject --dry-run`)\n}\n\ninterface LoadedConfig {\n\treadonly config: RemobiConfig\n\treadonly source: string\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction extractDefaultExport(value: unknown): unknown | undefined {\n\tif (!isRecord(value)) return undefined\n\tif (!('default' in value)) return undefined\n\treturn value.default\n}\n\nfunction ensureInjectInputMode(context: string): void {\n\tif (process.stdin.isTTY) {\n\t\tthrow new Error(`${context} expects piped ttyd HTML on stdin`)\n\t}\n}\n\nfunction throwConfigValidationError(source: string, error: ConfigValidationError): never {\n\tthrow new Error(`Config validation failed for ${source}\\n${error.message}`)\n}\n\n/** Convert a config path to its .local sibling, e.g. remobi.config.ts → remobi.config.local.ts */\nfunction toLocalPath(configPath: string): string {\n\tconst dotIdx = configPath.lastIndexOf('.')\n\tif (dotIdx === -1) {\n\t\treturn `${configPath}.local`\n\t}\n\treturn `${configPath.slice(0, dotIdx)}.local${configPath.slice(dotIdx)}`\n}\n\n/** Try to load a .local config override file. Returns undefined if the file does not exist. */\nasync function loadLocalOverrides(localPath: string): Promise<RemobiConfigOverrides | undefined> {\n\tif (!existsSync(localPath)) {\n\t\treturn undefined\n\t}\n\n\tconst mod = await import(localPath)\n\tconst defaultExport = extractDefaultExport(mod)\n\tif (defaultExport === undefined) {\n\t\tthrow new Error(`Local config file has no default export: ${localPath}`)\n\t}\n\n\tassertValidOverridesOrThrow(defaultExport, localPath)\n\treturn defaultExport\n}\n\nfunction assertValidOverridesOrThrow(\n\tvalue: unknown,\n\tsource: string,\n): asserts value is RemobiConfigOverrides {\n\ttry {\n\t\tassertValidConfigOverrides(value)\n\t} catch (error) {\n\t\tif (error instanceof ConfigValidationError) {\n\t\t\tthrowConfigValidationError(source, error)\n\t\t}\n\t\tthrow error\n\t}\n}\n\nfunction assertValidResolvedOrThrow(value: unknown, source: string): asserts value is RemobiConfig {\n\ttry {\n\t\tassertValidResolvedConfig(value)\n\t} catch (error) {\n\t\tif (error instanceof ConfigValidationError) {\n\t\t\tthrowConfigValidationError(source, error)\n\t\t}\n\t\tthrow error\n\t}\n}\n\nasync function loadConfig(configPath: string | undefined): Promise<LoadedConfig> {\n\tlet resolved = configPath\n\tif (!resolved) {\n\t\t// Search order: cwd → XDG config dir (~/.config/remobi/)\n\t\tconst names = ['remobi.config.ts', 'remobi.config.js']\n\t\tconst searchDirs = [\n\t\t\tprocess.cwd(),\n\t\t\tjoin(process.env.XDG_CONFIG_HOME ?? join(homedir(), '.config'), 'remobi'),\n\t\t]\n\t\tfor (const dir of searchDirs) {\n\t\t\tfor (const name of names) {\n\t\t\t\tconst full = join(dir, name)\n\t\t\t\tif (existsSync(full)) {\n\t\t\t\t\tresolved = full\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (resolved) break\n\t\t}\n\t}\n\n\tif (resolved) {\n\t\tconst abs = resolve(process.cwd(), resolved)\n\t\tconst mod = await import(abs)\n\t\tconst defaultExport = extractDefaultExport(mod)\n\t\tif (defaultExport === undefined) {\n\t\t\tthrow new Error(`Config file has no default export: ${abs}`)\n\t\t}\n\n\t\tassertValidOverridesOrThrow(defaultExport, abs)\n\t\tconst sharedConfig = defineConfig(defaultExport)\n\n\t\t// Apply .local overrides on top of the shared config\n\t\tconst localPath = toLocalPath(abs)\n\t\tconst localOverrides = await loadLocalOverrides(localPath)\n\t\tconst config =\n\t\t\tlocalOverrides !== undefined ? mergeConfig(sharedConfig, localOverrides) : sharedConfig\n\n\t\tconst sourceLabel = localOverrides !== undefined ? `${abs} + ${localPath}` : abs\n\t\tassertValidResolvedOrThrow(config, sourceLabel)\n\t\treturn { config, source: sourceLabel }\n\t}\n\n\tassertValidResolvedOrThrow(defaultConfig, 'built-in defaults')\n\n\treturn {\n\t\tconfig: defaultConfig,\n\t\tsource: 'built-in defaults',\n\t}\n}\n\nasync function main(): Promise<void> {\n\tconst parsed = parseCliArgs(process.argv.slice(2))\n\tif (!parsed.ok) {\n\t\tconsole.error(parsed.error)\n\t\tusage()\n\t\tprocess.exit(1)\n\t}\n\n\tconst { command, configPath, outputPath, dryRun, port, host, noSleep, command_ } = parsed.value\n\n\tswitch (command) {\n\t\tcase 'serve': {\n\t\t\tconst loaded = await loadConfig(configPath)\n\t\t\tawait serve(loaded.config, port, command_.length > 0 ? command_ : undefined, noSleep, host)\n\t\t\tbreak\n\t\t}\n\n\t\tcase 'build': {\n\t\t\tconst loaded = await loadConfig(configPath)\n\t\t\tconst targetPath = outputPath\n\t\t\t\t? resolve(process.cwd(), outputPath)\n\t\t\t\t: resolve(process.cwd(), 'dist/index.html')\n\n\t\t\tif (dryRun) {\n\t\t\t\tconsole.log('Dry run: build')\n\t\t\t\tconsole.log(`- config: ${loaded.source}`)\n\t\t\t\tconsole.log(`- output: ${targetPath}`)\n\t\t\t\tconsole.log('- action: would bundle overlay, fetch ttyd base HTML, inject, and write file')\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\t// Ensure output directory exists\n\t\t\tmkdirSync(dirname(targetPath), { recursive: true })\n\n\t\t\tawait build(loaded.config, targetPath)\n\t\t\tconsole.log(`Built: ${targetPath}`)\n\t\t\tbreak\n\t\t}\n\n\t\tcase 'inject': {\n\t\t\tconst loaded = await loadConfig(configPath)\n\t\t\tif (dryRun) {\n\t\t\t\tensureInjectInputMode('remobi inject --dry-run')\n\t\t\t\tconst dryRunStdin = await readStdin()\n\t\t\t\tif (dryRunStdin.trim().length === 0) {\n\t\t\t\t\tthrow new Error('remobi inject --dry-run expects piped ttyd HTML on stdin')\n\t\t\t\t}\n\t\t\t\tconsole.log('Dry run: inject')\n\t\t\t\tconsole.log(`- config: ${loaded.source}`)\n\t\t\t\tconsole.log('- stdin: piped input detected')\n\t\t\t\tconsole.log('- action: would read stdin HTML, inject overlay, and write to stdout')\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tensureInjectInputMode('remobi inject')\n\t\t\tconst result = await injectFromStdin(loaded.config)\n\t\t\tprocess.stdout.write(result)\n\t\t\tbreak\n\t\t}\n\n\t\tcase 'init': {\n\t\t\tconst targetPath = resolve(process.cwd(), 'remobi.config.ts')\n\t\t\tif (existsSync(targetPath)) {\n\t\t\t\tconsole.error('remobi.config.ts already exists')\n\t\t\t\tprocess.exit(1)\n\t\t\t}\n\t\t\tconst template = `import { defineConfig } from 'remobi'\n\nexport default defineConfig({\n // name: 'remobi', // app name (tab title, PWA home screen label)\n // theme: 'catppuccin-mocha',\n // font: {\n // family: 'JetBrainsMono NFM, monospace',\n // mobileSizeDefault: 16,\n // sizeRange: [8, 32],\n // },\n //\n // Toolbar/drawer accept a plain array (replace) or a function (transform):\n //\n // toolbar: { row1: [{ id, label, description, action }], row2: [...] },\n //\n // drawer: {\n // buttons: [\n // { id: 'sessions', label: 'Sessions', description: 'Choose tmux session', action: { type: 'send', data: '\\\\x02s' } },\n // ],\n // },\n //\n // toolbar: {\n // row2: (defaults) => defaults.filter((b) => b.id !== 'q'),\n // },\n //\n // drawer: {\n // buttons: (defaults) => [\n // ...defaults,\n // { id: 'my-btn', label: 'X', description: 'Send x', action: { type: 'send', data: 'x' } },\n // ],\n // },\n //\n // gestures: {\n // swipe: {\n // enabled: true,\n // left: '\\\\x02n', // data sent on swipe left (default: next tmux window)\n // right: '\\\\x02p', // data sent on swipe right (default: prev tmux window)\n // leftLabel: 'Next tmux window',\n // rightLabel: 'Previous tmux window',\n // },\n // pinch: { enabled: true },\n // scroll: { strategy: 'wheel', sensitivity: 40 },\n // },\n // mobile: {\n // initData: '\\\\x02z', // send on load when viewport < widthThreshold (auto-zoom pane)\n // widthThreshold: 768, // px — default matches phone/tablet breakpoint\n // },\n // floatingButtons: [\n // // Always-visible top-left buttons (touch devices only)\n // { position: 'top-left', buttons: [{ id: 'zoom', label: 'Zoom', description: 'Toggle pane zoom', action: { type: 'send', data: '\\\\x02z' } }] },\n // ],\n // pwa: {\n // enabled: true, // enable PWA manifest + meta tags (used by remobi serve)\n // shortName: 'remobi', // short name for home screen icon (defaults to name)\n // themeColor: '#1e1e2e', // theme-color meta tag + manifest\n // },\n // reconnect: {\n // enabled: true, // show overlay + auto-reload on connection loss (default true)\n // },\n})\n`\n\t\t\twriteFileSync(targetPath, template)\n\t\t\tconsole.log(`Created: ${targetPath}`)\n\t\t\tbreak\n\t\t}\n\n\t\tcase 'version':\n\t\t\tconsole.log(VERSION)\n\t\t\tbreak\n\n\t\tcase 'help':\n\t\t\tusage()\n\t\t\tbreak\n\n\t\tdefault:\n\t\t\tconsole.error(`Unknown command: ${command}`)\n\t\t\tusage()\n\t\t\tprocess.exit(1)\n\t}\n}\n\nconst entryScript = process.argv[1]\nif (entryScript && import.meta.filename === realpathSync(entryScript)) {\n\tmain().catch((err: unknown) => {\n\t\tconsole.error(err)\n\t\tprocess.exit(1)\n\t})\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remobi",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Monitor and control your coding agents from your phone. Touch controls for tmux over the web.",
5
5
  "type": "module",
6
6
  "bin": {