@moneysiren/cli 0.1.0-alpha.0 → 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 (37) hide show
  1. package/README.md +15 -4
  2. package/dist/apps/cli/src/cli.d.ts +4 -1
  3. package/dist/apps/cli/src/cli.js +20 -3
  4. package/dist/apps/cli/src/commands/install.js +134 -6
  5. package/dist/apps/cli/src/commands/modes.js +18 -10
  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/index.js +0 -0
  12. package/dist/apps/cli/src/postinstall.js +1 -1
  13. package/dist/apps/cli/src/release-installer.d.ts +57 -0
  14. package/dist/apps/cli/src/release-installer.js +432 -0
  15. package/dist/apps/cli/src/runtime-adapter.js +1 -1
  16. package/dist/apps/cli/src/slash.js +27 -0
  17. package/dist/apps/cli/src/version.d.ts +2 -0
  18. package/dist/apps/cli/src/version.js +2 -0
  19. package/dist/packages/config/src/load.js +3 -0
  20. package/dist/packages/config/src/schema.d.ts +3 -0
  21. package/dist/packages/config/src/schema.js +3 -0
  22. package/dist/packages/local-api/src/server.js +1 -1
  23. package/dist/packages/view-model/src/hud-model.d.ts +74 -0
  24. package/dist/packages/view-model/src/hud-model.js +295 -0
  25. package/dist/packages/view-model/src/index.d.ts +5 -2
  26. package/dist/packages/view-model/src/index.js +4 -1
  27. package/dist/packages/view-model/src/notification-preferences-model.d.ts +30 -2
  28. package/dist/packages/view-model/src/notification-preferences-model.js +183 -1
  29. package/dist/packages/view-model/src/notification-preferences.d.ts +1 -1
  30. package/dist/packages/view-model/src/notification-preferences.js +1 -1
  31. package/dist/packages/view-model/src/sync-state.d.ts +47 -0
  32. package/dist/packages/view-model/src/sync-state.js +140 -0
  33. package/dist/packages/view-model/src/usage-progress.d.ts +22 -0
  34. package/dist/packages/view-model/src/usage-progress.js +57 -0
  35. package/dist/packages/view-model/src/view-model.d.ts +22 -0
  36. package/dist/packages/view-model/src/view-model.js +142 -0
  37. package/package.json +3 -2
@@ -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