@moneysiren/cli 0.1.0-alpha.1 → 0.1.0-alpha.10

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.
Files changed (36) hide show
  1. package/README.md +11 -3
  2. package/dist/apps/cli/src/cli.d.ts +3 -0
  3. package/dist/apps/cli/src/cli.js +18 -2
  4. package/dist/apps/cli/src/commands/install.js +10 -1
  5. package/dist/apps/cli/src/commands/modes.js +16 -11
  6. package/dist/apps/cli/src/commands/runtime.d.ts +5 -0
  7. package/dist/apps/cli/src/commands/runtime.js +366 -0
  8. package/dist/apps/cli/src/desktop-runtime.d.ts +54 -0
  9. package/dist/apps/cli/src/desktop-runtime.js +720 -0
  10. package/dist/apps/cli/src/home.js +27 -0
  11. package/dist/apps/cli/src/postinstall.js +1 -1
  12. package/dist/apps/cli/src/release-installer.d.ts +4 -1
  13. package/dist/apps/cli/src/release-installer.js +47 -8
  14. package/dist/apps/cli/src/runtime-adapter.js +1 -1
  15. package/dist/apps/cli/src/slash.js +27 -0
  16. package/dist/apps/cli/src/version.d.ts +1 -1
  17. package/dist/apps/cli/src/version.js +1 -1
  18. package/dist/packages/config/src/load.js +3 -0
  19. package/dist/packages/config/src/schema.d.ts +3 -0
  20. package/dist/packages/config/src/schema.js +3 -0
  21. package/dist/packages/local-api/src/server.js +1 -1
  22. package/dist/packages/view-model/src/hud-model.d.ts +74 -0
  23. package/dist/packages/view-model/src/hud-model.js +295 -0
  24. package/dist/packages/view-model/src/index.d.ts +5 -2
  25. package/dist/packages/view-model/src/index.js +4 -1
  26. package/dist/packages/view-model/src/notification-preferences-model.d.ts +30 -2
  27. package/dist/packages/view-model/src/notification-preferences-model.js +183 -1
  28. package/dist/packages/view-model/src/notification-preferences.d.ts +1 -1
  29. package/dist/packages/view-model/src/notification-preferences.js +1 -1
  30. package/dist/packages/view-model/src/sync-state.d.ts +47 -0
  31. package/dist/packages/view-model/src/sync-state.js +140 -0
  32. package/dist/packages/view-model/src/usage-progress.d.ts +22 -0
  33. package/dist/packages/view-model/src/usage-progress.js +57 -0
  34. package/dist/packages/view-model/src/view-model.d.ts +22 -0
  35. package/dist/packages/view-model/src/view-model.js +142 -0
  36. package/package.json +3 -2
package/README.md CHANGED
@@ -12,7 +12,15 @@ MoneySiren is local-first. The CLI reads configuration and secrets from the proc
12
12
 
13
13
  ## Published Alpha Usage
14
14
 
15
- After an alpha is published:
15
+ For normal source-free installs, prefer the app package because it downloads the matching Web/HUD release assets during global npm install:
16
+
17
+ ```bash
18
+ npm install -g @moneysiren/app@alpha
19
+ msiren start
20
+ msiren hud
21
+ ```
22
+
23
+ For CLI-only automation:
16
24
 
17
25
  ```bash
18
26
  npm install -g @moneysiren/cli@alpha
@@ -67,7 +75,7 @@ Install the generated tarball into a temporary project:
67
75
  mkdir -p /tmp/moneysiren-alpha-review
68
76
  cd /tmp/moneysiren-alpha-review
69
77
  npm init -y
70
- npm install /path/to/moneysiren-cli-0.1.0-alpha.1.tgz
78
+ npm install /path/to/moneysiren-cli-0.1.0-alpha.10.tgz
71
79
  npm exec moneysiren
72
80
  npm exec moneysiren -- --version
73
81
  npm exec moneysiren -- /version
@@ -83,7 +91,7 @@ PowerShell equivalent for the temporary project:
83
91
  New-Item -ItemType Directory -Force -Path $env:TEMP\moneysiren-alpha-review
84
92
  Set-Location $env:TEMP\moneysiren-alpha-review
85
93
  npm init -y
86
- npm install C:\path\to\moneysiren-cli-0.1.0-alpha.1.tgz
94
+ npm install C:\path\to\moneysiren-cli-0.1.0-alpha.10.tgz
87
95
  npm exec moneysiren
88
96
  npm exec moneysiren -- --version
89
97
  npm exec moneysiren -- modes
@@ -1,4 +1,5 @@
1
1
  import { type CliLocalRuntimeAdapter } from "./runtime-adapter.js";
2
+ import type { CliDesktopRuntimeAdapter } from "./desktop-runtime.js";
2
3
  import { type Theme } from "./theme.js";
3
4
  import type { SlackReportTransport } from "../../../packages/report/src/index.js";
4
5
  import type { AwsCostExplorerClientAdapter } from "../../../packages/connectors/aws/src/index.js";
@@ -23,6 +24,7 @@ export interface CliRuntime {
23
24
  liveClients?: CliLiveClients;
24
25
  fetch?: typeof fetch;
25
26
  localRuntime?: CliLocalRuntimeAdapter;
27
+ desktopRuntime?: CliDesktopRuntimeAdapter;
26
28
  openUrl?: (url: string) => Promise<void> | void;
27
29
  }
28
30
  export interface CliLiveClients {
@@ -46,6 +48,7 @@ export interface CliExecutionContext {
46
48
  interactive: boolean;
47
49
  theme: Theme;
48
50
  localRuntime?: CliLocalRuntimeAdapter;
51
+ desktopRuntime?: CliDesktopRuntimeAdapter;
49
52
  }
50
53
  export interface CliResult {
51
54
  exitCode: number;
@@ -5,7 +5,7 @@ import { runInstallCommand } from "./commands/install.js";
5
5
  import { runModesCommand } from "./commands/modes.js";
6
6
  import { runNotifyCommand } from "./commands/notify.js";
7
7
  import { runReportCommand } from "./commands/report.js";
8
- import { runDesktopCommand, runOpenCommand, runServeCommand } from "./commands/runtime.js";
8
+ import { runDesktopCommand, runHudCommand, runOpenCommand, runRestartCommand, runServeCommand, runStartCommand, runStatusCommand, runStopCommand, } from "./commands/runtime.js";
9
9
  import { runSummaryCommand } from "./commands/summary.js";
10
10
  import { runSyncCommand } from "./commands/sync.js";
11
11
  import { runThemeCommand } from "./commands/theme.js";
@@ -61,6 +61,7 @@ export async function runCli(args, runtime = {}) {
61
61
  }),
62
62
  theme,
63
63
  ...(runtime.localRuntime === undefined ? {} : { localRuntime: runtime.localRuntime }),
64
+ ...(runtime.desktopRuntime === undefined ? {} : { desktopRuntime: runtime.desktopRuntime }),
64
65
  };
65
66
  context.stdin = runtime.stdin ?? process.stdin;
66
67
  context.output = runtime.output ?? process.stdout;
@@ -130,6 +131,21 @@ async function dispatchCommand(args, context) {
130
131
  if (command === "serve") {
131
132
  return runServeCommand(rest, context);
132
133
  }
134
+ if (command === "start") {
135
+ return runStartCommand(rest, context);
136
+ }
137
+ if (command === "status") {
138
+ return runStatusCommand(rest, context);
139
+ }
140
+ if (command === "stop") {
141
+ return runStopCommand(rest, context);
142
+ }
143
+ if (command === "restart") {
144
+ return runRestartCommand(rest, context);
145
+ }
146
+ if (command === "hud") {
147
+ return runHudCommand(rest, context);
148
+ }
133
149
  if (command === "open") {
134
150
  return runOpenCommand(rest, context);
135
151
  }
@@ -152,7 +168,7 @@ async function dispatchCommand(args, context) {
152
168
  return runThemeCommand(rest, context);
153
169
  }
154
170
  context.stderr(`Unknown command: ${command}`);
155
- context.stderr("Run `moneysiren --help` for usage.");
171
+ context.stderr("Run `msiren --help` or `moneysiren --help` for usage.");
156
172
  return 1;
157
173
  }
158
174
  async function dispatchSlashCommand(args, context) {
@@ -229,7 +229,16 @@ function writeReleaseInstallSummary(context, result) {
229
229
  for (const asset of result.assets) {
230
230
  context.stdout(`Downloaded ${asset.surface}: ${asset.name}`);
231
231
  context.stdout(` SHA256 verified: ${asset.checksumVerified ? "yes" : "checksum unavailable"}`);
232
- context.stdout(` Signature verified: ${asset.signatureVerified ? "yes" : "not required"}`);
232
+ context.stdout(` Signature status: ${formatSignatureStatus(asset)}`);
233
233
  }
234
234
  }
235
+ function formatSignatureStatus(asset) {
236
+ if (asset.signatureVerified) {
237
+ return "verified";
238
+ }
239
+ if (asset.signatureStatus === "unsigned-prerelease-accepted") {
240
+ return "unsigned alpha accepted";
241
+ }
242
+ return "not required";
243
+ }
235
244
  //# sourceMappingURL=install.js.map
@@ -17,28 +17,33 @@ export async function runModesCommand(args, context) {
17
17
  context.stdout(`Platform: ${platformLabel()}`);
18
18
  context.stdout(`Install profile: ${formatInstallSurfaces(selectedSurfaces)}${profile === null ? " (recommended default)" : ""}`);
19
19
  context.stdout("npm install: npm install -g @moneysiren/cli@alpha");
20
+ context.stdout("Short command: msiren");
20
21
  context.stdout(`Runtime lock: ${runtimeLockHint()}`);
21
22
  context.stdout("");
22
23
  context.stdout("1. CLI automation");
23
24
  context.stdout(` Status: ${surfaceStatus("cli", selectedSurfaces)} from the npm CLI package`);
24
- context.stdout(" Try: moneysiren doctor");
25
- context.stdout(" Try: moneysiren sync --provider mock");
25
+ context.stdout(" Try: msiren doctor");
26
+ context.stdout(" Try: msiren sync --provider mock");
26
27
  context.stdout("");
27
28
  context.stdout("2. Local web dashboard/runtime");
28
29
  context.stdout(` Status: ${surfaceStatus("web", selectedSurfaces)} GitHub Release web runtime archive is installed by the CLI`);
29
- context.stdout(" Install: moneysiren install --web");
30
- context.stdout(" Try: moneysiren serve [--port <port>]");
31
- context.stdout(" Try: moneysiren dashboard check");
32
- context.stdout(" Note: the npm CLI starts the local API; full dashboard runtime ships as a release asset.");
30
+ context.stdout(" Install: msiren install --web");
31
+ context.stdout(" Try: msiren start");
32
+ context.stdout(" Stop: msiren stop --web");
33
+ context.stdout(" Try: msiren dashboard check");
34
+ context.stdout(" Note: msiren start runs the installed GitHub Release web runtime.");
33
35
  context.stdout("");
34
36
  context.stdout("3. Desktop tray/notifier");
35
37
  context.stdout(` Status: ${surfaceStatus("hud", selectedSurfaces)} Windows/macOS target is the thin Tauri tray shell from GitHub Releases`);
36
- context.stdout(" Install: moneysiren install --hud");
37
- context.stdout(" Try: moneysiren desktop status");
38
- context.stdout(" Try: moneysiren notify once --dry-run");
38
+ context.stdout(" Install: msiren install --hud");
39
+ context.stdout(" Try: msiren hud");
40
+ context.stdout(" Try: msiren status");
41
+ context.stdout(" Stop: msiren stop --hud");
42
+ context.stdout(" Try: msiren notify once --dry-run");
39
43
  context.stdout("");
40
- context.stdout("Install recommended set: moneysiren install --all");
41
- context.stdout("Change selection only: moneysiren install --profile-only");
44
+ context.stdout("Install recommended set: msiren install --all");
45
+ context.stdout("Stop managed runtimes: msiren stop");
46
+ context.stdout("Change selection only: msiren install --profile-only");
42
47
  return 0;
43
48
  }
44
49
  function surfaceStatus(surface, selectedSurfaces) {
@@ -1,5 +1,10 @@
1
1
  import type { CliExecutionContext } from "../cli.js";
2
2
  export declare function runServeCommand(args: readonly string[], context: CliExecutionContext): Promise<number>;
3
+ export declare function runStatusCommand(args: readonly string[], context: CliExecutionContext): Promise<number>;
4
+ export declare function runStartCommand(args: readonly string[], context: CliExecutionContext): Promise<number>;
5
+ export declare function runStopCommand(args: readonly string[], context: CliExecutionContext): Promise<number>;
6
+ export declare function runRestartCommand(args: readonly string[], context: CliExecutionContext): Promise<number>;
7
+ export declare function runHudCommand(args: readonly string[], context: CliExecutionContext): Promise<number>;
3
8
  export declare function runOpenCommand(args: readonly string[], context: CliExecutionContext): Promise<number>;
4
9
  export declare function runDesktopCommand(args: readonly string[], context: CliExecutionContext): Promise<number>;
5
10
  //# sourceMappingURL=runtime.d.ts.map
@@ -1,5 +1,12 @@
1
+ import { createFallbackDesktopRuntimeAdapter, } from "../desktop-runtime.js";
1
2
  import { createFallbackLocalRuntimeAdapter, } from "../runtime-adapter.js";
3
+ import { removeRuntimeLock } from "../../../../packages/runtime/src/index.js";
2
4
  const SERVE_USAGE = "Usage: moneysiren serve [--port <port>]";
5
+ const START_USAGE = "Usage: msiren start [--port <port>] [--open|--no-open] [--hud]";
6
+ const HUD_USAGE = "Usage: msiren hud [--port <port>]";
7
+ const STATUS_USAGE = "Usage: msiren status";
8
+ const STOP_USAGE = "Usage: msiren stop [--web|--hud|--api|--all]";
9
+ const RESTART_USAGE = "Usage: msiren restart [--port <port>] [--open|--no-open] [--hud]";
3
10
  const OPEN_USAGE = "Usage: moneysiren open";
4
11
  const DESKTOP_USAGE = "Usage: moneysiren desktop status";
5
12
  export async function runServeCommand(args, context) {
@@ -18,6 +25,183 @@ export async function runServeCommand(args, context) {
18
25
  });
19
26
  return writeStartRuntimeResult(context, result, "MoneySiren local runtime");
20
27
  }
28
+ export async function runStatusCommand(args, context) {
29
+ if (args.includes("--help") || args.includes("-h")) {
30
+ context.stdout(STATUS_USAGE);
31
+ return 0;
32
+ }
33
+ if (args.length > 0) {
34
+ context.stderr(STATUS_USAGE);
35
+ return 1;
36
+ }
37
+ const desktop = await desktopRuntimeAdapter(context).status();
38
+ const api = await runtimeAdapter(context).findRuntime();
39
+ context.stdout("MoneySiren status");
40
+ writeDesktopProcessStatus(context, "Web runtime", desktop.web);
41
+ writeDesktopProcessStatus(context, "HUD", desktop.hud);
42
+ context.stdout(`Desktop state: ${desktop.statePath}`);
43
+ if (api === null) {
44
+ context.stdout("Local API runtime: not running");
45
+ return 0;
46
+ }
47
+ const healthy = await runtimeAdapter(context).assertRuntimeHealthy(api);
48
+ context.stdout(`Local API runtime: ${healthy ? "healthy" : "unhealthy"}`);
49
+ context.stdout(` PID: ${api.pid}`);
50
+ context.stdout(` URL: ${api.baseUrl}`);
51
+ return healthy ? 0 : 1;
52
+ }
53
+ export async function runStartCommand(args, context) {
54
+ if (args.includes("--help") || args.includes("-h")) {
55
+ context.stdout(START_USAGE);
56
+ return 0;
57
+ }
58
+ const parsed = parseStartArgs(args);
59
+ if (parsed === undefined) {
60
+ context.stderr(START_USAGE);
61
+ return 1;
62
+ }
63
+ const adapter = desktopRuntimeAdapter(context);
64
+ const web = await adapter.startWebRuntime({
65
+ openBrowser: parsed.openBrowser,
66
+ ...(parsed.port === undefined ? {} : { port: parsed.port }),
67
+ });
68
+ const webExitCode = writeDesktopRuntimeResult(context, web, "MoneySiren dashboard runtime");
69
+ if (webExitCode !== 0) {
70
+ return webExitCode;
71
+ }
72
+ if (web.status !== "unavailable" && parsed.openBrowser) {
73
+ await context.openUrl(web.dashboardUrl);
74
+ context.stdout(`Dashboard URL: ${web.dashboardUrl}`);
75
+ }
76
+ if (!parsed.launchHud) {
77
+ context.stdout("HUD: run `msiren hud` to open the floating desktop widget.");
78
+ return 0;
79
+ }
80
+ const hud = await adapter.startHud({
81
+ ...(parsed.port === undefined ? {} : { port: parsed.port }),
82
+ });
83
+ return writeDesktopShellResult(context, hud, "MoneySiren HUD");
84
+ }
85
+ export async function runStopCommand(args, context) {
86
+ if (args.includes("--help") || args.includes("-h")) {
87
+ context.stdout(STOP_USAGE);
88
+ return 0;
89
+ }
90
+ const selection = parseStopArgs(args);
91
+ if (selection === undefined) {
92
+ context.stderr(STOP_USAGE);
93
+ return 1;
94
+ }
95
+ context.stdout("MoneySiren stop");
96
+ const desktopResults = await desktopRuntimeAdapter(context).stop({
97
+ hud: selection.hud,
98
+ web: selection.web,
99
+ });
100
+ for (const result of desktopResults) {
101
+ writeStopDesktopRuntimeResult(context, result);
102
+ }
103
+ let exitCode = desktopResults.some((result) => result.status === "failed") ? 1 : 0;
104
+ if (selection.api) {
105
+ const apiResult = await stopLocalApiRuntime(context);
106
+ context.stdout(`Local API runtime: ${apiResult.status}`);
107
+ context.stdout(` ${apiResult.detail}`);
108
+ if (apiResult.pid !== undefined) {
109
+ context.stdout(` PID: ${apiResult.pid}`);
110
+ }
111
+ if (apiResult.status === "failed") {
112
+ exitCode = 1;
113
+ }
114
+ }
115
+ return exitCode;
116
+ }
117
+ export async function runRestartCommand(args, context) {
118
+ if (args.includes("--help") || args.includes("-h")) {
119
+ context.stdout(RESTART_USAGE);
120
+ return 0;
121
+ }
122
+ const parsed = parseStartArgs(args);
123
+ if (parsed === undefined) {
124
+ context.stderr(RESTART_USAGE);
125
+ return 1;
126
+ }
127
+ const stopExitCode = await runStopCommand(["--web", "--hud"], context);
128
+ if (stopExitCode !== 0) {
129
+ return stopExitCode;
130
+ }
131
+ return runStartCommand(args, context);
132
+ }
133
+ export async function runHudCommand(args, context) {
134
+ if (args.includes("--help") || args.includes("-h")) {
135
+ context.stdout(HUD_USAGE);
136
+ return 0;
137
+ }
138
+ const parsed = parseHudArgs(args);
139
+ if (parsed === undefined) {
140
+ context.stderr(HUD_USAGE);
141
+ return 1;
142
+ }
143
+ const adapter = desktopRuntimeAdapter(context);
144
+ const web = await adapter.startWebRuntime({
145
+ openBrowser: false,
146
+ ...(parsed.port === undefined ? {} : { port: parsed.port }),
147
+ });
148
+ const webExitCode = writeDesktopRuntimeResult(context, web, "MoneySiren dashboard runtime");
149
+ if (webExitCode !== 0) {
150
+ return webExitCode;
151
+ }
152
+ const hud = await adapter.startHud({
153
+ ...(parsed.port === undefined ? {} : { port: parsed.port }),
154
+ });
155
+ return writeDesktopShellResult(context, hud, "MoneySiren HUD");
156
+ }
157
+ async function stopLocalApiRuntime(context) {
158
+ const adapter = runtimeAdapter(context);
159
+ const runtime = await adapter.findRuntime();
160
+ if (runtime === null) {
161
+ return {
162
+ status: "not-running",
163
+ detail: "No managed local API runtime lock was found.",
164
+ };
165
+ }
166
+ try {
167
+ process.kill(runtime.pid, "SIGTERM");
168
+ await waitForProcessExit(runtime.pid, 3_000);
169
+ if (isProcessAlive(runtime.pid)) {
170
+ return {
171
+ status: "failed",
172
+ detail: "Local API runtime did not exit after SIGTERM.",
173
+ pid: runtime.pid,
174
+ };
175
+ }
176
+ await removeRuntimeLock({
177
+ cwd: context.cwd,
178
+ env: context.env,
179
+ });
180
+ return {
181
+ status: "stopped",
182
+ detail: "Managed local API runtime stopped.",
183
+ pid: runtime.pid,
184
+ };
185
+ }
186
+ catch (error) {
187
+ if (isNodeError(error) && error.code === "ESRCH") {
188
+ await removeRuntimeLock({
189
+ cwd: context.cwd,
190
+ env: context.env,
191
+ });
192
+ return {
193
+ status: "stale",
194
+ detail: "Removed stale local API runtime lock.",
195
+ pid: runtime.pid,
196
+ };
197
+ }
198
+ return {
199
+ status: "failed",
200
+ detail: error instanceof Error ? error.message : String(error),
201
+ pid: runtime.pid,
202
+ };
203
+ }
204
+ }
21
205
  export async function runOpenCommand(args, context) {
22
206
  if (args.includes("--help") || args.includes("-h")) {
23
207
  context.stdout(OPEN_USAGE);
@@ -81,6 +265,9 @@ export async function runDesktopCommand(args, context) {
81
265
  function runtimeAdapter(context) {
82
266
  return context.localRuntime ?? createFallbackLocalRuntimeAdapter(context);
83
267
  }
268
+ function desktopRuntimeAdapter(context) {
269
+ return context.desktopRuntime ?? createFallbackDesktopRuntimeAdapter(context);
270
+ }
84
271
  async function findHealthyRuntime(adapter) {
85
272
  const runtime = await adapter.findRuntime();
86
273
  if (runtime === null) {
@@ -103,6 +290,60 @@ function writeStartRuntimeResult(context, result, heading) {
103
290
  }
104
291
  return 1;
105
292
  }
293
+ function writeDesktopRuntimeResult(context, result, heading) {
294
+ if (result.status !== "unavailable") {
295
+ context.stdout(heading);
296
+ context.stdout(`Runtime: ${result.status}`);
297
+ context.stdout(`Dashboard URL: ${result.dashboardUrl}`);
298
+ if (result.pid !== undefined) {
299
+ context.stdout(`PID: ${result.pid}`);
300
+ }
301
+ for (const note of result.notes) {
302
+ context.stdout(`Note: ${note}`);
303
+ }
304
+ return 0;
305
+ }
306
+ context.stderr(`${heading}: unavailable`);
307
+ context.stderr(result.reason);
308
+ for (const line of result.guidance) {
309
+ context.stderr(line);
310
+ }
311
+ return 1;
312
+ }
313
+ function writeDesktopShellResult(context, result, heading) {
314
+ if (result.status !== "unavailable") {
315
+ context.stdout(heading);
316
+ context.stdout(`Desktop shell: ${result.status}`);
317
+ if (result.pid !== undefined) {
318
+ context.stdout(`PID: ${result.pid}`);
319
+ }
320
+ for (const note of result.notes) {
321
+ context.stdout(`Note: ${note}`);
322
+ }
323
+ return 0;
324
+ }
325
+ context.stderr(`${heading}: unavailable`);
326
+ context.stderr(result.reason);
327
+ for (const line of result.guidance) {
328
+ context.stderr(line);
329
+ }
330
+ return 1;
331
+ }
332
+ function writeDesktopProcessStatus(context, label, status) {
333
+ context.stdout(`${label}: ${status.status}`);
334
+ if (status.pid !== undefined) {
335
+ context.stdout(` PID: ${status.pid}`);
336
+ }
337
+ context.stdout(` ${status.detail}`);
338
+ }
339
+ function writeStopDesktopRuntimeResult(context, result) {
340
+ const label = result.target === "web" ? "Web runtime" : "HUD";
341
+ context.stdout(`${label}: ${result.status}`);
342
+ context.stdout(` ${result.detail}`);
343
+ if (result.pid !== undefined) {
344
+ context.stdout(` PID: ${result.pid}`);
345
+ }
346
+ }
106
347
  function parseServeArgs(args) {
107
348
  let port;
108
349
  for (let index = 0; index < args.length; index += 1) {
@@ -127,7 +368,132 @@ function parseServeArgs(args) {
127
368
  }
128
369
  return Number.isSafeInteger(port) && port > 0 && port <= 65_535 ? { port } : undefined;
129
370
  }
371
+ function parseStopArgs(args) {
372
+ let web = false;
373
+ let hud = false;
374
+ let api = false;
375
+ let all = false;
376
+ for (const arg of args) {
377
+ if (arg === "--web") {
378
+ web = true;
379
+ }
380
+ else if (arg === "--hud") {
381
+ hud = true;
382
+ }
383
+ else if (arg === "--api") {
384
+ api = true;
385
+ }
386
+ else if (arg === "--all") {
387
+ all = true;
388
+ }
389
+ else {
390
+ return undefined;
391
+ }
392
+ }
393
+ if (all || (!web && !hud && !api)) {
394
+ return {
395
+ api: true,
396
+ hud: true,
397
+ web: true,
398
+ };
399
+ }
400
+ return {
401
+ api,
402
+ hud,
403
+ web,
404
+ };
405
+ }
406
+ function parseStartArgs(args) {
407
+ let launchHud = false;
408
+ let openBrowser = true;
409
+ let port;
410
+ for (let index = 0; index < args.length; index += 1) {
411
+ const arg = args[index];
412
+ if (arg === "--hud") {
413
+ launchHud = true;
414
+ continue;
415
+ }
416
+ if (arg === "--open") {
417
+ openBrowser = true;
418
+ continue;
419
+ }
420
+ if (arg === "--no-open") {
421
+ openBrowser = false;
422
+ continue;
423
+ }
424
+ if (arg === "--port") {
425
+ const value = args[index + 1];
426
+ if (value === undefined || value.startsWith("--")) {
427
+ return undefined;
428
+ }
429
+ port = parsePort(value);
430
+ index += 1;
431
+ continue;
432
+ }
433
+ if (arg?.startsWith("--port=")) {
434
+ port = parsePort(arg.slice("--port=".length));
435
+ continue;
436
+ }
437
+ return undefined;
438
+ }
439
+ if (port !== undefined && (!Number.isSafeInteger(port) || port <= 0 || port > 65_535)) {
440
+ return undefined;
441
+ }
442
+ return {
443
+ launchHud,
444
+ openBrowser,
445
+ ...(port === undefined ? {} : { port }),
446
+ };
447
+ }
448
+ function parseHudArgs(args) {
449
+ let port;
450
+ for (let index = 0; index < args.length; index += 1) {
451
+ const arg = args[index];
452
+ if (arg === "--port") {
453
+ const value = args[index + 1];
454
+ if (value === undefined || value.startsWith("--")) {
455
+ return undefined;
456
+ }
457
+ port = parsePort(value);
458
+ index += 1;
459
+ continue;
460
+ }
461
+ if (arg?.startsWith("--port=")) {
462
+ port = parsePort(arg.slice("--port=".length));
463
+ continue;
464
+ }
465
+ return undefined;
466
+ }
467
+ if (port !== undefined && (!Number.isSafeInteger(port) || port <= 0 || port > 65_535)) {
468
+ return undefined;
469
+ }
470
+ return port === undefined ? {} : { port };
471
+ }
130
472
  function parsePort(value) {
131
473
  return Number.parseInt(value, 10);
132
474
  }
475
+ function isProcessAlive(pid) {
476
+ if (pid <= 0) {
477
+ return false;
478
+ }
479
+ try {
480
+ process.kill(pid, 0);
481
+ return true;
482
+ }
483
+ catch (error) {
484
+ return isNodeError(error) && error.code === "EPERM";
485
+ }
486
+ }
487
+ async function waitForProcessExit(pid, timeoutMs) {
488
+ const deadline = Date.now() + timeoutMs;
489
+ while (Date.now() < deadline) {
490
+ if (!isProcessAlive(pid)) {
491
+ return;
492
+ }
493
+ await new Promise((resolveTimeout) => setTimeout(resolveTimeout, 100));
494
+ }
495
+ }
496
+ function isNodeError(value) {
497
+ return value instanceof Error && "code" in value;
498
+ }
133
499
  //# sourceMappingURL=runtime.js.map
@@ -0,0 +1,54 @@
1
+ import type { CliExecutionContext } from "./cli.js";
2
+ export interface StartWebRuntimeOptions {
3
+ openBrowser: boolean;
4
+ port?: number;
5
+ }
6
+ export interface StartHudOptions {
7
+ port?: number;
8
+ }
9
+ export interface DesktopRuntimeStatus {
10
+ statePath: string;
11
+ web: DesktopProcessStatus;
12
+ hud: DesktopProcessStatus;
13
+ }
14
+ export interface DesktopProcessStatus {
15
+ target: "web" | "hud";
16
+ status: "running" | "not-running" | "not-managed" | "stale";
17
+ pid?: number;
18
+ detail: string;
19
+ }
20
+ export interface StopDesktopRuntimeOptions {
21
+ hud: boolean;
22
+ web: boolean;
23
+ }
24
+ export interface StopDesktopRuntimeResult {
25
+ target: "web" | "hud";
26
+ status: "stopped" | "not-running" | "not-managed" | "stale" | "failed";
27
+ pid?: number;
28
+ detail: string;
29
+ }
30
+ export type DesktopRuntimeResult = {
31
+ status: "running" | "started";
32
+ dashboardUrl: string;
33
+ pid?: number;
34
+ notes: readonly string[];
35
+ } | DesktopRuntimeUnavailableResult;
36
+ export type DesktopShellResult = {
37
+ status: "opened" | "started";
38
+ executablePath: string;
39
+ pid?: number;
40
+ notes: readonly string[];
41
+ } | DesktopRuntimeUnavailableResult;
42
+ export interface DesktopRuntimeUnavailableResult {
43
+ status: "unavailable";
44
+ reason: string;
45
+ guidance: readonly string[];
46
+ }
47
+ export interface CliDesktopRuntimeAdapter {
48
+ startWebRuntime(options: StartWebRuntimeOptions): Promise<DesktopRuntimeResult>;
49
+ startHud(options: StartHudOptions): Promise<DesktopShellResult>;
50
+ status(): Promise<DesktopRuntimeStatus>;
51
+ stop(options: StopDesktopRuntimeOptions): Promise<readonly StopDesktopRuntimeResult[]>;
52
+ }
53
+ export declare function createFallbackDesktopRuntimeAdapter(context: CliExecutionContext): CliDesktopRuntimeAdapter;
54
+ //# sourceMappingURL=desktop-runtime.d.ts.map