@livx.cc/appwrap 0.43.0 → 0.46.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/src/config.ts CHANGED
@@ -89,6 +89,42 @@ export interface AppwrapConfig {
89
89
  * the app dir, so `build desktop` apps must keep their repo (+ node_modules) around at runtime —
90
90
  * embedding the handler file into the `.app` is out of scope for phase-0. macOS-only. */
91
91
  handlers?: string;
92
+ /** Local-server mode: boot a command that serves the app locally, then load it LIVE in the window
93
+ * (instead of the bundled `pwaDist`). The shell shows a bundled splash, spawns `command` (+`args`)
94
+ * in a background thread, polls `http://localhost:<port><healthPath>` until it answers, then
95
+ * navigates the window there; on window-close it kills the child so the stack never outlives the
96
+ * shell. Use for a desktop app that IS a local dev/server stack (e.g. `para-chat`). Absent → the
97
+ * desktop lane loads the bundled `pwaDist` as usual. The CLI resolves `command` (when a bare name)
98
+ * and `cwd` to absolute paths at stamp time (a GUI-launched .app inherits the minimal launchd PATH,
99
+ * so a bare `bun`/PATH lookup would fail). `build desktop` apps must keep the server (+ its repo)
100
+ * present at runtime — bundling the server INTO the .app is a future phase. macOS-only. */
101
+ server?: {
102
+ /** Command to spawn (argv[0]). A bare name is PATH-resolved at stamp time; an absolute path is used as-is. */
103
+ command: string;
104
+ /** Extra arguments passed to the command. */
105
+ args?: string[];
106
+ /** Working directory to spawn in (default: the app project root). */
107
+ cwd?: string;
108
+ /** How the shell learns the URL to load. Provide EXACTLY ONE:
109
+ * - `urlMarker` (preferred for servers that print their address, e.g. vite/para-chat): the shell
110
+ * pipes the server's stdout and, on the first line CONTAINING this marker, extracts the first
111
+ * `http(s)://…` token and loads it. This tracks whatever port the server auto-picked — no
112
+ * hardcoded port, no mismatch. e.g. `"Frontend:"` matches `Frontend: https://localhost:5050`.
113
+ * - `port` (+ optional `https`) for a silent server on a KNOWN fixed port: the shell TCP-polls
114
+ * `localhost:<port>` and loads `<http|https>://localhost:<port>`. */
115
+ urlMarker?: string;
116
+ /** Fixed port the server listens on (used only when `urlMarker` is absent). */
117
+ port?: number;
118
+ /** Load over https in fixed-`port` mode (default false = http). Ignored in `urlMarker` mode (the
119
+ * captured URL carries its own scheme). The server's TLS cert CA must be trusted by the system
120
+ * keychain and its SANs cover `localhost`/`127.0.0.1`, or the WebView shows a cert error. */
121
+ https?: boolean;
122
+ /** Health path polled for readiness (default `/`). Ready = any HTTP response (even 4xx/5xx). */
123
+ healthPath?: string;
124
+ /** Max ms to wait for the port before showing an error in the splash (default 120000 — a first-run
125
+ * production build can be slow). */
126
+ readyTimeoutMs?: number;
127
+ };
92
128
  };
93
129
  /** Custom URL scheme for deep links (e.g. "hellowrap" → hellowrap://...). */
94
130
  urlScheme?: string;
package/src/desktop.ts CHANGED
@@ -44,6 +44,31 @@ export interface DesktopShellConfig {
44
44
  * ~/.bun/bin, so a bare `bun` spawn fails. The Rust shell falls back to `bun` when this is
45
45
  * missing/nonexistent. */
46
46
  handlersRuntime: string;
47
+ /** True when `modules` includes 'media' → the .app carries an NSMicrophoneUsageDescription so
48
+ * `getUserMedia` (voice mode) can pass macOS TCC. The WebKit-layer grant (popup_mac) is
49
+ * unconditional; this only adds the OS usage string an app that actually uses the mic needs. */
50
+ microphone: boolean;
51
+ /** Local-server mode. When present the Rust shell boots `command` (+`args`) in a background thread,
52
+ * polls `http://localhost:<port><healthPath>` until it answers, then navigates the window there
53
+ * (instead of the bundled dist); it kills the child on window-close. Absent → bundled-PWA load.
54
+ * `command`/`cwd` are resolved to ABSOLUTE by the CLI (see `regenerateDesktop`) because a
55
+ * GUI-launched .app has the minimal launchd PATH (no ~/.bun/bin). Empty when the app isn't a
56
+ * local-server app. */
57
+ server?: {
58
+ command: string;
59
+ args: string[];
60
+ cwd: string;
61
+ /** Stdout marker → capture the URL the server prints (dynamic port). '' when using fixed `port`. */
62
+ urlMarker: string;
63
+ /** Fixed port (used only when `urlMarker` is ''). 0 when unused. */
64
+ port: number;
65
+ https: boolean;
66
+ healthPath: string;
67
+ readyTimeoutMs: number;
68
+ /** PATH the child is spawned with — the CLI stamps its own (build-time) PATH so a GUI launch can
69
+ * still resolve neighbouring tools (bun, node) the launchd PATH omits. */
70
+ path: string;
71
+ };
47
72
  }
48
73
 
49
74
  /** Resolve the desktop shell config from an appwrap config: identity + `urlScheme` come from the
@@ -65,6 +90,21 @@ export function deriveDesktopConfig(cfg: AppwrapConfig): DesktopShellConfig {
65
90
  handlers: d.handlers ?? '',
66
91
  pushSigned: false, // stamped by the CLI when a signing identity + macOS profile resolve — see buildDesktop
67
92
  handlersRuntime: '', // stamped by the CLI (process.execPath) when handlers is set — see regenerateDesktop
93
+ microphone: (cfg.modules ?? []).includes('media'),
94
+ // `command`/`cwd`/`path` are resolved to absolute by the CLI (regenerateDesktop); carried as authored here.
95
+ server: d.server
96
+ ? {
97
+ command: d.server.command,
98
+ args: d.server.args ?? [],
99
+ cwd: d.server.cwd ?? '',
100
+ urlMarker: d.server.urlMarker ?? '',
101
+ port: d.server.port ?? 0,
102
+ https: d.server.https ?? false,
103
+ healthPath: d.server.healthPath ?? '/',
104
+ readyTimeoutMs: d.server.readyTimeoutMs ?? 120_000,
105
+ path: '',
106
+ }
107
+ : undefined,
68
108
  };
69
109
  }
70
110
 
@@ -134,6 +174,13 @@ export function buildInfoPlist(
134
174
  <key>CFBundleIconName</key>
135
175
  <string>${xmlEscape(iconName)}</string>`
136
176
  : '';
177
+ // NSMicrophoneUsageDescription: required for getUserMedia (voice mode) to pass macOS TCC — without
178
+ // it the OS silently denies mic access (no prompt). Added only when the app declares the `media` module.
179
+ const mic = shell.microphone
180
+ ? `
181
+ <key>NSMicrophoneUsageDescription</key>
182
+ <string>${xmlEscape(shell.name)} uses the microphone for voice input.</string>`
183
+ : '';
137
184
  return `<?xml version="1.0" encoding="UTF-8"?>
138
185
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
139
186
  <plist version="1.0">
@@ -153,7 +200,7 @@ export function buildInfoPlist(
153
200
  <key>CFBundlePackageType</key>
154
201
  <string>APPL</string>
155
202
  <key>NSHighResolutionCapable</key>
156
- <true/>${icon}${iconAsset}${urlTypes}
203
+ <true/>${icon}${iconAsset}${mic}${urlTypes}
157
204
  </dict>
158
205
  </plist>
159
206
  `;