@moneysiren/cli 0.1.0-alpha.1 → 0.1.0-alpha.11
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/README.md +11 -3
- package/dist/apps/cli/src/cli.d.ts +3 -0
- package/dist/apps/cli/src/cli.js +18 -2
- package/dist/apps/cli/src/commands/install.js +10 -1
- package/dist/apps/cli/src/commands/modes.js +16 -11
- package/dist/apps/cli/src/commands/runtime.d.ts +5 -0
- package/dist/apps/cli/src/commands/runtime.js +366 -0
- package/dist/apps/cli/src/desktop-runtime.d.ts +54 -0
- package/dist/apps/cli/src/desktop-runtime.js +720 -0
- package/dist/apps/cli/src/home.js +27 -0
- package/dist/apps/cli/src/postinstall.js +1 -1
- package/dist/apps/cli/src/release-installer.d.ts +4 -1
- package/dist/apps/cli/src/release-installer.js +47 -8
- package/dist/apps/cli/src/runtime-adapter.js +1 -1
- package/dist/apps/cli/src/slash.js +27 -0
- package/dist/apps/cli/src/version.d.ts +1 -1
- package/dist/apps/cli/src/version.js +1 -1
- package/dist/packages/config/src/load.js +3 -0
- package/dist/packages/config/src/schema.d.ts +3 -0
- package/dist/packages/config/src/schema.js +3 -0
- package/dist/packages/local-api/src/server.js +1 -1
- package/dist/packages/view-model/src/hud-model.d.ts +74 -0
- package/dist/packages/view-model/src/hud-model.js +295 -0
- package/dist/packages/view-model/src/index.d.ts +5 -2
- package/dist/packages/view-model/src/index.js +4 -1
- package/dist/packages/view-model/src/notification-preferences-model.d.ts +30 -2
- package/dist/packages/view-model/src/notification-preferences-model.js +183 -1
- package/dist/packages/view-model/src/notification-preferences.d.ts +1 -1
- package/dist/packages/view-model/src/notification-preferences.js +1 -1
- package/dist/packages/view-model/src/sync-state.d.ts +47 -0
- package/dist/packages/view-model/src/sync-state.js +140 -0
- package/dist/packages/view-model/src/usage-progress.d.ts +22 -0
- package/dist/packages/view-model/src/usage-progress.js +57 -0
- package/dist/packages/view-model/src/view-model.d.ts +22 -0
- package/dist/packages/view-model/src/view-model.js +142 -0
- 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
|
-
|
|
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.
|
|
78
|
+
npm install /path/to/moneysiren-cli-0.1.0-alpha.11.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.
|
|
94
|
+
npm install C:\path\to\moneysiren-cli-0.1.0-alpha.11.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;
|
package/dist/apps/cli/src/cli.js
CHANGED
|
@@ -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
|
|
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:
|
|
25
|
-
context.stdout(" Try:
|
|
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:
|
|
30
|
-
context.stdout(" Try:
|
|
31
|
-
context.stdout("
|
|
32
|
-
context.stdout("
|
|
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:
|
|
37
|
-
context.stdout(" Try:
|
|
38
|
-
context.stdout(" Try:
|
|
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:
|
|
41
|
-
context.stdout("
|
|
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
|