remobi 0.1.0 → 0.2.1
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 +44 -0
- package/README.md +4 -0
- package/dist/build.mjs +1 -1
- package/dist/cli.mjs +3 -3
- package/dist/cli.mjs.map +1 -1
- package/dist/{node-compat-BzXgbTV9.mjs → node-compat-CqwRIRV_.mjs} +32 -7
- package/dist/node-compat-CqwRIRV_.mjs.map +1 -0
- package/package.json +101 -87
- package/src/pwa/icons/icon-180.png +0 -0
- package/src/pwa/icons/icon-192.png +0 -0
- package/src/pwa/icons/icon-512.png +0 -0
- package/dist/node-compat-BzXgbTV9.mjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,47 @@
|
|
|
1
|
+
## [0.2.1](https://github.com/connorads/remobi/compare/v0.2.0...v0.2.1) (2026-03-15)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
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)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
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)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Bug Fixes
|
|
27
|
+
|
|
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)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
### Bug Fixes
|
|
34
|
+
|
|
35
|
+
* exclude package.json from Biome formatter ([044183f](https://github.com/connorads/remobi/commit/044183fd636fe56eb17f1cd650338c85be46ce5e))
|
|
36
|
+
* remove redundant checks from prepublishOnly ([efdb742](https://github.com/connorads/remobi/commit/efdb742087842ba131ed704e3e29676aa672ab87))
|
|
37
|
+
|
|
38
|
+
# [0.2.0](https://github.com/connorads/remobi/compare/v0.1.0...v0.2.0) (2026-03-15)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
### Features
|
|
42
|
+
|
|
43
|
+
* add pixel R> logo and integrate across project ([44b238a](https://github.com/connorads/remobi/commit/44b238aa7909cec2c408acb5bde32f9f2c645e26))
|
|
44
|
+
|
|
1
45
|
# Changelog
|
|
2
46
|
|
|
3
47
|
## 0.1.0 — 2026-03-15
|
package/README.md
CHANGED
package/dist/build.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as generatePwaHtml, n as sleep, r as spawnProcess, t as readStdin } from "./node-compat-
|
|
1
|
+
import { i as generatePwaHtml, n as sleep, r as spawnProcess, t as readStdin } from "./node-compat-CqwRIRV_.mjs";
|
|
2
2
|
import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { dirname, resolve } from "node:path";
|
|
4
4
|
|
package/dist/cli.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { n as sleep, r as spawnProcess, t as readStdin } from "./node-compat-
|
|
2
|
+
import { n as sleep, r as spawnProcess, t as readStdin } from "./node-compat-CqwRIRV_.mjs";
|
|
3
3
|
import { build, bundleOverlay, injectFromStdin, injectOverlay } from "./build.mjs";
|
|
4
4
|
import { defaultConfig, defineConfig, mergeConfig, serialiseThemeForTtyd } from "./src/config.mjs";
|
|
5
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
import { dirname, join, resolve } from "node:path";
|
|
8
8
|
import * as v from "valibot";
|
|
@@ -935,7 +935,7 @@ export default defineConfig({
|
|
|
935
935
|
process.exit(1);
|
|
936
936
|
}
|
|
937
937
|
}
|
|
938
|
-
if (import.meta.filename === process.argv[1]) main().catch((err) => {
|
|
938
|
+
if (import.meta.filename === realpathSync(process.argv[1])) main().catch((err) => {
|
|
939
939
|
console.error(err);
|
|
940
940
|
process.exit(1);
|
|
941
941
|
});
|
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, 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 === 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 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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"}
|
|
@@ -3,14 +3,39 @@ import { spawn } from "node:child_process";
|
|
|
3
3
|
//#region src/pwa/icon.ts
|
|
4
4
|
/**
|
|
5
5
|
* SVG icon for remobi PWA.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Pixel-art R> on catppuccin mocha base (#1e1e2e).
|
|
7
|
+
* Green R (#a6e3a1), blue chevron (#89b4fa).
|
|
8
|
+
* Uses <rect> elements — renders identically on all platforms.
|
|
9
9
|
*/
|
|
10
10
|
const ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
11
|
-
<rect width="512" height="512" rx="80"
|
|
12
|
-
<
|
|
13
|
-
|
|
11
|
+
<rect width="512" height="512" rx="80" fill="#1e1e2e"/>
|
|
12
|
+
<g fill="#a6e3a1">
|
|
13
|
+
<rect x="76" y="130" width="28" height="28" rx="4"/>
|
|
14
|
+
<rect x="112" y="130" width="28" height="28" rx="4"/>
|
|
15
|
+
<rect x="148" y="130" width="28" height="28" rx="4"/>
|
|
16
|
+
<rect x="184" y="130" width="28" height="28" rx="4"/>
|
|
17
|
+
<rect x="76" y="166" width="28" height="28" rx="4"/>
|
|
18
|
+
<rect x="220" y="166" width="28" height="28" rx="4"/>
|
|
19
|
+
<rect x="76" y="202" width="28" height="28" rx="4"/>
|
|
20
|
+
<rect x="220" y="202" width="28" height="28" rx="4"/>
|
|
21
|
+
<rect x="76" y="238" width="28" height="28" rx="4"/>
|
|
22
|
+
<rect x="112" y="238" width="28" height="28" rx="4"/>
|
|
23
|
+
<rect x="148" y="238" width="28" height="28" rx="4"/>
|
|
24
|
+
<rect x="184" y="238" width="28" height="28" rx="4"/>
|
|
25
|
+
<rect x="76" y="274" width="28" height="28" rx="4"/>
|
|
26
|
+
<rect x="148" y="274" width="28" height="28" rx="4"/>
|
|
27
|
+
<rect x="76" y="310" width="28" height="28" rx="4"/>
|
|
28
|
+
<rect x="184" y="310" width="28" height="28" rx="4"/>
|
|
29
|
+
<rect x="76" y="346" width="28" height="28" rx="4"/>
|
|
30
|
+
<rect x="220" y="346" width="28" height="28" rx="4"/>
|
|
31
|
+
</g>
|
|
32
|
+
<g fill="#89b4fa">
|
|
33
|
+
<rect x="328" y="166" width="28" height="28" rx="4"/>
|
|
34
|
+
<rect x="364" y="202" width="28" height="28" rx="4"/>
|
|
35
|
+
<rect x="400" y="238" width="28" height="28" rx="4"/>
|
|
36
|
+
<rect x="364" y="274" width="28" height="28" rx="4"/>
|
|
37
|
+
<rect x="328" y="310" width="28" height="28" rx="4"/>
|
|
38
|
+
</g>
|
|
14
39
|
</svg>`;
|
|
15
40
|
/** Convert SVG string to a data URI */
|
|
16
41
|
function svgToDataUri(svg) {
|
|
@@ -80,4 +105,4 @@ function spawnProcess(cmd, opts) {
|
|
|
80
105
|
|
|
81
106
|
//#endregion
|
|
82
107
|
export { generatePwaHtml as i, sleep as n, spawnProcess as r, readStdin as t };
|
|
83
|
-
//# sourceMappingURL=node-compat-
|
|
108
|
+
//# sourceMappingURL=node-compat-CqwRIRV_.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-compat-CqwRIRV_.mjs","names":["nodeSpawn"],"sources":["../src/pwa/icon.ts","../src/pwa/meta-tags.ts","../src/util/node-compat.ts"],"sourcesContent":["/**\n * SVG icon for remobi PWA.\n * Pixel-art R> on catppuccin mocha base (#1e1e2e).\n * Green R (#a6e3a1), blue chevron (#89b4fa).\n * Uses <rect> elements — renders identically on all platforms.\n */\nexport const ICON_SVG = `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\">\n <rect width=\"512\" height=\"512\" rx=\"80\" fill=\"#1e1e2e\"/>\n <g fill=\"#a6e3a1\">\n <rect x=\"76\" y=\"130\" width=\"28\" height=\"28\" rx=\"4\"/>\n <rect x=\"112\" y=\"130\" width=\"28\" height=\"28\" rx=\"4\"/>\n <rect x=\"148\" y=\"130\" width=\"28\" height=\"28\" rx=\"4\"/>\n <rect x=\"184\" y=\"130\" width=\"28\" height=\"28\" rx=\"4\"/>\n <rect x=\"76\" y=\"166\" width=\"28\" height=\"28\" rx=\"4\"/>\n <rect x=\"220\" y=\"166\" width=\"28\" height=\"28\" rx=\"4\"/>\n <rect x=\"76\" y=\"202\" width=\"28\" height=\"28\" rx=\"4\"/>\n <rect x=\"220\" y=\"202\" width=\"28\" height=\"28\" rx=\"4\"/>\n <rect x=\"76\" y=\"238\" width=\"28\" height=\"28\" rx=\"4\"/>\n <rect x=\"112\" y=\"238\" width=\"28\" height=\"28\" rx=\"4\"/>\n <rect x=\"148\" y=\"238\" width=\"28\" height=\"28\" rx=\"4\"/>\n <rect x=\"184\" y=\"238\" width=\"28\" height=\"28\" rx=\"4\"/>\n <rect x=\"76\" y=\"274\" width=\"28\" height=\"28\" rx=\"4\"/>\n <rect x=\"148\" y=\"274\" width=\"28\" height=\"28\" rx=\"4\"/>\n <rect x=\"76\" y=\"310\" width=\"28\" height=\"28\" rx=\"4\"/>\n <rect x=\"184\" y=\"310\" width=\"28\" height=\"28\" rx=\"4\"/>\n <rect x=\"76\" y=\"346\" width=\"28\" height=\"28\" rx=\"4\"/>\n <rect x=\"220\" y=\"346\" width=\"28\" height=\"28\" rx=\"4\"/>\n </g>\n <g fill=\"#89b4fa\">\n <rect x=\"328\" y=\"166\" width=\"28\" height=\"28\" rx=\"4\"/>\n <rect x=\"364\" y=\"202\" width=\"28\" height=\"28\" rx=\"4\"/>\n <rect x=\"400\" y=\"238\" width=\"28\" height=\"28\" rx=\"4\"/>\n <rect x=\"364\" y=\"274\" width=\"28\" height=\"28\" rx=\"4\"/>\n <rect x=\"328\" y=\"310\" width=\"28\" height=\"28\" rx=\"4\"/>\n </g>\n</svg>`\n\n/** Convert SVG string to a data URI */\nexport function svgToDataUri(svg: string): string {\n\treturn `data:image/svg+xml;base64,${Buffer.from(svg).toString('base64')}`\n}\n","import type { PwaConfig } from '../types'\nimport { ICON_SVG, svgToDataUri } from './icon'\n\n/** Escape a string for safe use in HTML attribute values */\nexport function escapeAttr(value: string): string {\n\treturn value\n\t\t.replace(/&/g, '&')\n\t\t.replace(/\"/g, '"')\n\t\t.replace(/</g, '<')\n\t\t.replace(/>/g, '>')\n}\n\n/** Generate PWA HTML to inject into </head> */\nexport function generatePwaHtml(name: string, pwa: PwaConfig): string {\n\tconst svgDataUri = svgToDataUri(ICON_SVG)\n\treturn [\n\t\t`<link rel=\"manifest\" href=\"/manifest.json\">`,\n\t\t`<meta name=\"theme-color\" content=\"${escapeAttr(pwa.themeColor)}\">`,\n\t\t`<meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\">`,\n\t\t`<meta name=\"apple-mobile-web-app-title\" content=\"${escapeAttr(name)}\">`,\n\t\t`<link rel=\"apple-touch-icon\" href=\"/apple-touch-icon.png\">`,\n\t\t`<link rel=\"icon\" type=\"image/svg+xml\" href=\"${svgDataUri}\">`,\n\t].join('\\n')\n}\n","import { spawn as nodeSpawn } from 'node:child_process'\nimport type { Readable } from 'node:stream'\n\nexport const sleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms))\n\nexport async function readStdin(): Promise<string> {\n\tconst chunks: Buffer[] = []\n\tfor await (const chunk of process.stdin) {\n\t\tchunks.push(Buffer.from(chunk))\n\t}\n\treturn Buffer.concat(chunks).toString('utf-8')\n}\n\nexport interface SpawnedProcess {\n\treadonly pid: number | undefined\n\treadonly stdout: Readable | null\n\treadonly stderr: Readable | null\n\treadonly stdin: NodeJS.WritableStream | null\n\tkill(signal?: NodeJS.Signals): boolean\n\treadonly exited: Promise<number>\n}\n\nexport function spawnProcess(\n\tcmd: readonly string[],\n\topts?: {\n\t\tcwd?: string\n\t\tstdin?: 'ignore' | 'pipe'\n\t\tstdout?: 'ignore' | 'pipe'\n\t\tstderr?: 'ignore' | 'pipe'\n\t},\n): SpawnedProcess {\n\tconst [command, ...args] = cmd\n\tif (!command) throw new Error('spawnProcess requires at least one argument')\n\n\tconst proc = nodeSpawn(command, args, {\n\t\tcwd: opts?.cwd,\n\t\tstdio: [opts?.stdin ?? 'ignore', opts?.stdout ?? 'ignore', opts?.stderr ?? 'ignore'],\n\t})\n\n\tconst exited = new Promise<number>((resolve, reject) => {\n\t\tproc.on('close', (code) => resolve(code ?? 1))\n\t\tproc.on('error', reject)\n\t})\n\n\treturn {\n\t\tget pid() {\n\t\t\treturn proc.pid\n\t\t},\n\t\tget stdout() {\n\t\t\treturn proc.stdout\n\t\t},\n\t\tget stderr() {\n\t\t\treturn proc.stderr\n\t\t},\n\t\tget stdin() {\n\t\t\treturn proc.stdin\n\t\t},\n\t\tkill(signal?: NodeJS.Signals) {\n\t\t\treturn proc.kill(signal)\n\t\t},\n\t\texited,\n\t}\n}\n\nexport async function collectStream(stream: Readable | null): Promise<string> {\n\tif (!stream) return ''\n\tconst chunks: Buffer[] = []\n\tfor await (const chunk of stream) {\n\t\tchunks.push(Buffer.from(chunk))\n\t}\n\treturn Buffer.concat(chunks).toString('utf-8')\n}\n"],"mappings":";;;;;;;;;AAMA,MAAa,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCxB,SAAgB,aAAa,KAAqB;AACjD,QAAO,6BAA6B,OAAO,KAAK,IAAI,CAAC,SAAS,SAAS;;;;;;ACnCxE,SAAgB,WAAW,OAAuB;AACjD,QAAO,MACL,QAAQ,MAAM,QAAQ,CACtB,QAAQ,MAAM,SAAS,CACvB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,OAAO;;;AAIxB,SAAgB,gBAAgB,MAAc,KAAwB;CACrE,MAAM,aAAa,aAAa,SAAS;AACzC,QAAO;EACN;EACA,qCAAqC,WAAW,IAAI,WAAW,CAAC;EAChE;EACA,oDAAoD,WAAW,KAAK,CAAC;EACrE;EACA,+CAA+C,WAAW;EAC1D,CAAC,KAAK,KAAK;;;;;ACnBb,MAAa,SAAS,OAA8B,IAAI,SAAS,MAAM,WAAW,GAAG,GAAG,CAAC;AAEzF,eAAsB,YAA6B;CAClD,MAAM,SAAmB,EAAE;AAC3B,YAAW,MAAM,SAAS,QAAQ,MACjC,QAAO,KAAK,OAAO,KAAK,MAAM,CAAC;AAEhC,QAAO,OAAO,OAAO,OAAO,CAAC,SAAS,QAAQ;;AAY/C,SAAgB,aACf,KACA,MAMiB;CACjB,MAAM,CAAC,SAAS,GAAG,QAAQ;AAC3B,KAAI,CAAC,QAAS,OAAM,IAAI,MAAM,8CAA8C;CAE5E,MAAM,OAAOA,MAAU,SAAS,MAAM;EACrC,KAAK,MAAM;EACX,OAAO;GAAC,MAAM,SAAS;GAAU,MAAM,UAAU;GAAU,MAAM,UAAU;GAAS;EACpF,CAAC;AAOF,QAAO;EACN,IAAI,MAAM;AACT,UAAO,KAAK;;EAEb,IAAI,SAAS;AACZ,UAAO,KAAK;;EAEb,IAAI,SAAS;AACZ,UAAO,KAAK;;EAEb,IAAI,QAAQ;AACX,UAAO,KAAK;;EAEb,KAAK,QAAyB;AAC7B,UAAO,KAAK,KAAK,OAAO;;EAEzB,QArBc,IAAI,SAAiB,SAAS,WAAW;AACvD,QAAK,GAAG,UAAU,SAAS,QAAQ,QAAQ,EAAE,CAAC;AAC9C,QAAK,GAAG,SAAS,OAAO;IACvB;EAmBD"}
|
package/package.json
CHANGED
|
@@ -1,88 +1,102 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
2
|
+
"name": "remobi",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Monitor and control your coding agents from your phone. Touch controls for tmux over the web.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"remobi": "dist/cli.mjs"
|
|
8
|
+
},
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/src/index.d.mts",
|
|
12
|
+
"default": "./dist/src/index.mjs"
|
|
13
|
+
},
|
|
14
|
+
"./config": {
|
|
15
|
+
"types": "./dist/src/config.d.mts",
|
|
16
|
+
"default": "./dist/src/config.mjs"
|
|
17
|
+
},
|
|
18
|
+
"./types": {
|
|
19
|
+
"types": "./dist/src/types.d.mts"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist/",
|
|
24
|
+
"styles/",
|
|
25
|
+
"src/pwa/icons/",
|
|
26
|
+
"README.md",
|
|
27
|
+
"CHANGELOG.md",
|
|
28
|
+
"LICENSE"
|
|
29
|
+
],
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"provenance": true
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=22.0.0"
|
|
35
|
+
},
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/connorads/remobi.git"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/connorads/remobi",
|
|
41
|
+
"bugs": "https://github.com/connorads/remobi/issues",
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build:overlay": "tsx scripts/build-overlay.ts",
|
|
44
|
+
"build:dist": "tsdown && pnpm run build:overlay",
|
|
45
|
+
"build": "tsx cli.ts build",
|
|
46
|
+
"check": "biome check .",
|
|
47
|
+
"check:fix": "biome check --fix .",
|
|
48
|
+
"lint:knip": "knip",
|
|
49
|
+
"lint:ox": "oxlint --import-plugin --promise-plugin",
|
|
50
|
+
"lint:publint": "publint",
|
|
51
|
+
"lint:typos": "typos",
|
|
52
|
+
"test": "vitest run",
|
|
53
|
+
"test:coverage": "vitest run --coverage",
|
|
54
|
+
"release": "semantic-release",
|
|
55
|
+
"prepublishOnly": "pnpm run lint:ox && pnpm run lint:knip && pnpm run build:dist && pnpm run lint:publint"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@biomejs/biome": "^1.9.0",
|
|
59
|
+
"@happy-dom/global-registrator": "^20.6.1",
|
|
60
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
61
|
+
"@semantic-release/git": "^10.0.1",
|
|
62
|
+
"@types/node": "^22.0.0",
|
|
63
|
+
"@types/ws": "^8.18.0",
|
|
64
|
+
"esbuild": "^0.25.0",
|
|
65
|
+
"knip": "^5.85.0",
|
|
66
|
+
"oxlint": "^1.50.0",
|
|
67
|
+
"publint": "^0.3.17",
|
|
68
|
+
"semantic-release": "^25.0.3",
|
|
69
|
+
"tsdown": "^0.20.0",
|
|
70
|
+
"tsx": "^4.19.0",
|
|
71
|
+
"typescript": "^5.9.3",
|
|
72
|
+
"vitest": "^3.0.0"
|
|
73
|
+
},
|
|
74
|
+
"license": "MIT",
|
|
75
|
+
"author": "Connor",
|
|
76
|
+
"keywords": [
|
|
77
|
+
"ttyd",
|
|
78
|
+
"tmux",
|
|
79
|
+
"terminal",
|
|
80
|
+
"mobile",
|
|
81
|
+
"overlay",
|
|
82
|
+
"touch",
|
|
83
|
+
"node",
|
|
84
|
+
"agents",
|
|
85
|
+
"claude",
|
|
86
|
+
"remote",
|
|
87
|
+
"pwa"
|
|
88
|
+
],
|
|
89
|
+
"dependencies": {
|
|
90
|
+
"@hono/node-server": "^1.14.0",
|
|
91
|
+
"@hono/node-ws": "^1.1.0",
|
|
92
|
+
"hono": "^4.7.0",
|
|
93
|
+
"valibot": "^1.2.0",
|
|
94
|
+
"ws": "^8.18.0"
|
|
95
|
+
},
|
|
96
|
+
"pnpm": {
|
|
97
|
+
"onlyBuiltDependencies": [
|
|
98
|
+
"@biomejs/biome",
|
|
99
|
+
"esbuild"
|
|
100
|
+
]
|
|
101
|
+
}
|
|
102
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"node-compat-BzXgbTV9.mjs","names":["nodeSpawn"],"sources":["../src/pwa/icon.ts","../src/pwa/meta-tags.ts","../src/util/node-compat.ts"],"sourcesContent":["/**\n * SVG icon for remobi PWA.\n * Dark rounded-rect background (#1e1e2e catppuccin mocha base),\n * green chevron (›) in #a6e3a1 (catppuccin green),\n * underscore cursor in #89b4fa (catppuccin blue).\n */\nexport const ICON_SVG = `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\">\n <rect width=\"512\" height=\"512\" rx=\"80\" ry=\"80\" fill=\"#1e1e2e\"/>\n <text x=\"100\" y=\"330\" font-family=\"monospace\" font-size=\"260\" font-weight=\"bold\" fill=\"#a6e3a1\">›</text>\n <text x=\"195\" y=\"400\" font-family=\"monospace\" font-size=\"140\" font-weight=\"bold\" fill=\"#89b4fa\">_</text>\n</svg>`\n\n/** Convert SVG string to a data URI */\nexport function svgToDataUri(svg: string): string {\n\treturn `data:image/svg+xml;base64,${Buffer.from(svg).toString('base64')}`\n}\n","import type { PwaConfig } from '../types'\nimport { ICON_SVG, svgToDataUri } from './icon'\n\n/** Escape a string for safe use in HTML attribute values */\nexport function escapeAttr(value: string): string {\n\treturn value\n\t\t.replace(/&/g, '&')\n\t\t.replace(/\"/g, '"')\n\t\t.replace(/</g, '<')\n\t\t.replace(/>/g, '>')\n}\n\n/** Generate PWA HTML to inject into </head> */\nexport function generatePwaHtml(name: string, pwa: PwaConfig): string {\n\tconst svgDataUri = svgToDataUri(ICON_SVG)\n\treturn [\n\t\t`<link rel=\"manifest\" href=\"/manifest.json\">`,\n\t\t`<meta name=\"theme-color\" content=\"${escapeAttr(pwa.themeColor)}\">`,\n\t\t`<meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\">`,\n\t\t`<meta name=\"apple-mobile-web-app-title\" content=\"${escapeAttr(name)}\">`,\n\t\t`<link rel=\"apple-touch-icon\" href=\"/apple-touch-icon.png\">`,\n\t\t`<link rel=\"icon\" type=\"image/svg+xml\" href=\"${svgDataUri}\">`,\n\t].join('\\n')\n}\n","import { spawn as nodeSpawn } from 'node:child_process'\nimport type { Readable } from 'node:stream'\n\nexport const sleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms))\n\nexport async function readStdin(): Promise<string> {\n\tconst chunks: Buffer[] = []\n\tfor await (const chunk of process.stdin) {\n\t\tchunks.push(Buffer.from(chunk))\n\t}\n\treturn Buffer.concat(chunks).toString('utf-8')\n}\n\nexport interface SpawnedProcess {\n\treadonly pid: number | undefined\n\treadonly stdout: Readable | null\n\treadonly stderr: Readable | null\n\treadonly stdin: NodeJS.WritableStream | null\n\tkill(signal?: NodeJS.Signals): boolean\n\treadonly exited: Promise<number>\n}\n\nexport function spawnProcess(\n\tcmd: readonly string[],\n\topts?: {\n\t\tcwd?: string\n\t\tstdin?: 'ignore' | 'pipe'\n\t\tstdout?: 'ignore' | 'pipe'\n\t\tstderr?: 'ignore' | 'pipe'\n\t},\n): SpawnedProcess {\n\tconst [command, ...args] = cmd\n\tif (!command) throw new Error('spawnProcess requires at least one argument')\n\n\tconst proc = nodeSpawn(command, args, {\n\t\tcwd: opts?.cwd,\n\t\tstdio: [opts?.stdin ?? 'ignore', opts?.stdout ?? 'ignore', opts?.stderr ?? 'ignore'],\n\t})\n\n\tconst exited = new Promise<number>((resolve, reject) => {\n\t\tproc.on('close', (code) => resolve(code ?? 1))\n\t\tproc.on('error', reject)\n\t})\n\n\treturn {\n\t\tget pid() {\n\t\t\treturn proc.pid\n\t\t},\n\t\tget stdout() {\n\t\t\treturn proc.stdout\n\t\t},\n\t\tget stderr() {\n\t\t\treturn proc.stderr\n\t\t},\n\t\tget stdin() {\n\t\t\treturn proc.stdin\n\t\t},\n\t\tkill(signal?: NodeJS.Signals) {\n\t\t\treturn proc.kill(signal)\n\t\t},\n\t\texited,\n\t}\n}\n\nexport async function collectStream(stream: Readable | null): Promise<string> {\n\tif (!stream) return ''\n\tconst chunks: Buffer[] = []\n\tfor await (const chunk of stream) {\n\t\tchunks.push(Buffer.from(chunk))\n\t}\n\treturn Buffer.concat(chunks).toString('utf-8')\n}\n"],"mappings":";;;;;;;;;AAMA,MAAa,WAAW;;;;;;AAOxB,SAAgB,aAAa,KAAqB;AACjD,QAAO,6BAA6B,OAAO,KAAK,IAAI,CAAC,SAAS,SAAS;;;;;;ACVxE,SAAgB,WAAW,OAAuB;AACjD,QAAO,MACL,QAAQ,MAAM,QAAQ,CACtB,QAAQ,MAAM,SAAS,CACvB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,OAAO;;;AAIxB,SAAgB,gBAAgB,MAAc,KAAwB;CACrE,MAAM,aAAa,aAAa,SAAS;AACzC,QAAO;EACN;EACA,qCAAqC,WAAW,IAAI,WAAW,CAAC;EAChE;EACA,oDAAoD,WAAW,KAAK,CAAC;EACrE;EACA,+CAA+C,WAAW;EAC1D,CAAC,KAAK,KAAK;;;;;ACnBb,MAAa,SAAS,OAA8B,IAAI,SAAS,MAAM,WAAW,GAAG,GAAG,CAAC;AAEzF,eAAsB,YAA6B;CAClD,MAAM,SAAmB,EAAE;AAC3B,YAAW,MAAM,SAAS,QAAQ,MACjC,QAAO,KAAK,OAAO,KAAK,MAAM,CAAC;AAEhC,QAAO,OAAO,OAAO,OAAO,CAAC,SAAS,QAAQ;;AAY/C,SAAgB,aACf,KACA,MAMiB;CACjB,MAAM,CAAC,SAAS,GAAG,QAAQ;AAC3B,KAAI,CAAC,QAAS,OAAM,IAAI,MAAM,8CAA8C;CAE5E,MAAM,OAAOA,MAAU,SAAS,MAAM;EACrC,KAAK,MAAM;EACX,OAAO;GAAC,MAAM,SAAS;GAAU,MAAM,UAAU;GAAU,MAAM,UAAU;GAAS;EACpF,CAAC;AAOF,QAAO;EACN,IAAI,MAAM;AACT,UAAO,KAAK;;EAEb,IAAI,SAAS;AACZ,UAAO,KAAK;;EAEb,IAAI,SAAS;AACZ,UAAO,KAAK;;EAEb,IAAI,QAAQ;AACX,UAAO,KAAK;;EAEb,KAAK,QAAyB;AAC7B,UAAO,KAAK,KAAK,OAAO;;EAEzB,QArBc,IAAI,SAAiB,SAAS,WAAW;AACvD,QAAK,GAAG,UAAU,SAAS,QAAQ,QAAQ,EAAE,CAAC;AAC9C,QAAK,GAAG,SAAS,OAAO;IACvB;EAmBD"}
|