@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
@@ -10,6 +10,10 @@ export function renderHomeScreen(input) {
10
10
  ` ${theme.command("/doctor")} Check local readiness without printing secrets`,
11
11
  ` ${theme.command("/install")} Choose CLI, web dashboard, and HUD components`,
12
12
  ` ${theme.command("/modes")} Show the CLI, web, and desktop modes`,
13
+ ` ${theme.command("/start")} Start the installed dashboard runtime`,
14
+ ` ${theme.command("/hud")} Start the installed runtime and open HUD`,
15
+ ` ${theme.command("/status")} Show managed web, HUD, and local API runtime status`,
16
+ ` ${theme.command("/stop")} Stop managed web, HUD, and local API runtimes`,
13
17
  ` ${theme.command("/init")} Create local SQLite storage`,
14
18
  ` ${theme.command("/dashboard")} Check the local dashboard API`,
15
19
  ` ${theme.command("/dashboard check")} Same as /dashboard`,
@@ -27,12 +31,21 @@ export function renderHomeScreen(input) {
27
31
  ` ${theme.command("/quit")} Exit the slash prompt`,
28
32
  "",
29
33
  theme.heading("Classic CLI"),
34
+ " msiren start",
35
+ " msiren hud",
36
+ " msiren status",
37
+ " msiren stop",
38
+ " msiren install --all",
39
+ "",
40
+ theme.heading("Full command"),
30
41
  " moneysiren doctor",
31
42
  " moneysiren install",
32
43
  " moneysiren install --status",
33
44
  " moneysiren modes",
34
45
  " moneysiren init",
35
46
  " moneysiren serve [--port <port>]",
47
+ " moneysiren stop [--web|--hud|--api|--all]",
48
+ " moneysiren restart [--port <port>] [--open|--no-open] [--hud]",
36
49
  " moneysiren open",
37
50
  " moneysiren sync --provider mock",
38
51
  " moneysiren summary --json",
@@ -54,6 +67,11 @@ export function renderHelpScreen(version) {
54
67
 
55
68
  Local-first cloud/SaaS usage, status, and expected billing dashboard.
56
69
 
70
+ Short command:
71
+ msiren start
72
+ msiren hud
73
+ msiren install --all
74
+
57
75
  Usage:
58
76
  moneysiren
59
77
  moneysiren --help
@@ -62,6 +80,11 @@ Usage:
62
80
  moneysiren install [--status|--all|--cli|--web|--hud|--no-cli|--no-web|--no-hud]
63
81
  moneysiren doctor
64
82
  moneysiren modes
83
+ moneysiren start [--port <port>] [--open|--no-open] [--hud]
84
+ moneysiren hud [--port <port>]
85
+ moneysiren status
86
+ moneysiren stop [--web|--hud|--api|--all]
87
+ moneysiren restart [--port <port>] [--open|--no-open] [--hud]
65
88
  moneysiren dashboard check [--url <local-dashboard-url>]
66
89
  moneysiren serve [--port <port>]
67
90
  moneysiren open
@@ -81,6 +104,10 @@ Slash commands:
81
104
  moneysiren /doctor
82
105
  moneysiren /install
83
106
  moneysiren /modes
107
+ moneysiren /start
108
+ moneysiren /hud
109
+ moneysiren /status
110
+ moneysiren /stop [--web|--hud|--api|--all]
84
111
  moneysiren /init
85
112
  moneysiren /dashboard
86
113
  moneysiren /dashboard check
File without changes
@@ -10,7 +10,7 @@ try {
10
10
  });
11
11
  console.log("MoneySiren setup profile saved.");
12
12
  console.log(formatInstallSelectionLine(profile.selectedSurfaces));
13
- console.log("Run `moneysiren install --status` to review or `moneysiren install` to change it.");
13
+ console.log("Run `msiren install --status` to review, `msiren install` to change it, or `msiren start` after installing release assets.");
14
14
  }
15
15
  catch (error) {
16
16
  console.warn(`MoneySiren setup profile skipped: ${error instanceof Error ? error.message : String(error)}`);
@@ -0,0 +1,57 @@
1
+ import type { InstallSurface } from "./install-profile.js";
2
+ export declare const DEFAULT_RELEASE_REPOSITORY = "ztwz11/moneysiren";
3
+ export declare const DEFAULT_RELEASE_TAG = "v0.1.0-alpha.10";
4
+ export interface ReleaseInstallOptions {
5
+ env?: Record<string, string | undefined>;
6
+ fetchImpl: typeof fetch;
7
+ installDir?: string;
8
+ now?: () => Date;
9
+ platform?: NodeJS.Platform;
10
+ repository?: string;
11
+ selectedSurfaces: readonly InstallSurface[];
12
+ signatureVerifier?: ReleaseAssetSignatureVerifier;
13
+ tag?: string;
14
+ trustedWindowsSignerThumbprints?: readonly string[];
15
+ }
16
+ export interface ReleaseInstallResult {
17
+ repository: string;
18
+ tag: string;
19
+ installDir: string;
20
+ releaseUrl: string;
21
+ assets: readonly InstalledReleaseAsset[];
22
+ }
23
+ export interface InstalledReleaseAsset {
24
+ surface: Exclude<InstallSurface, "cli">;
25
+ name: string;
26
+ path: string;
27
+ size: number;
28
+ sha256: string;
29
+ checksumVerified: boolean;
30
+ signatureVerified: boolean;
31
+ signatureStatus: string;
32
+ }
33
+ export interface ReleaseAssetSignatureVerifier {
34
+ verify(input: ReleaseAssetSignatureVerificationInput): Promise<ReleaseAssetSignatureVerificationResult>;
35
+ }
36
+ export interface ReleaseAssetSignatureVerificationInput {
37
+ assetName: string;
38
+ env: Record<string, string | undefined>;
39
+ expectedSignerThumbprints?: readonly string[];
40
+ path: string;
41
+ platform: NodeJS.Platform;
42
+ surface: Exclude<InstallSurface, "cli">;
43
+ tag: string;
44
+ }
45
+ export interface ReleaseAssetSignatureVerificationResult {
46
+ verified: boolean;
47
+ status: string;
48
+ message: string;
49
+ }
50
+ export declare function installReleaseAssets(options: ReleaseInstallOptions): Promise<ReleaseInstallResult>;
51
+ export declare function resolveReleaseInstallDir(input?: {
52
+ env?: Record<string, string | undefined>;
53
+ installDir?: string;
54
+ platform?: NodeJS.Platform;
55
+ tag?: string;
56
+ }): string;
57
+ //# sourceMappingURL=release-installer.d.ts.map
@@ -0,0 +1,432 @@
1
+ import { createHash } from "node:crypto";
2
+ import { execFile } from "node:child_process";
3
+ import { mkdir, unlink, writeFile } from "node:fs/promises";
4
+ import { homedir } from "node:os";
5
+ import { basename, join, posix, resolve, win32 } from "node:path";
6
+ import { promisify } from "node:util";
7
+ const execFileAsync = promisify(execFile);
8
+ export const DEFAULT_RELEASE_REPOSITORY = "ztwz11/moneysiren";
9
+ // Keep the source-free installer pinned to the latest published desktop/web release tag.
10
+ export const DEFAULT_RELEASE_TAG = "v0.1.0-alpha.10";
11
+ const RELEASE_REPOSITORY_ENV_KEY = "MONEYSIREN_RELEASE_REPOSITORY";
12
+ const RELEASE_TAG_ENV_KEY = "MONEYSIREN_RELEASE_TAG";
13
+ const RELEASE_INSTALL_DIR_ENV_KEY = "MONEYSIREN_RELEASE_INSTALL_DIR";
14
+ const RELEASE_PLATFORM_ENV_KEY = "MONEYSIREN_RELEASE_PLATFORM";
15
+ const WINDOWS_SIGNER_THUMBPRINTS_ENV_KEY = "MONEYSIREN_WINDOWS_SIGNER_THUMBPRINTS";
16
+ const ALLOW_UNSIGNED_HUD_ENV_KEY = "MONEYSIREN_ALLOW_UNSIGNED_HUD";
17
+ export async function installReleaseAssets(options) {
18
+ const env = options.env ?? process.env;
19
+ const repository = normalizeRepository(options.repository ?? env[RELEASE_REPOSITORY_ENV_KEY] ?? DEFAULT_RELEASE_REPOSITORY);
20
+ const tag = normalizeTag(options.tag ?? env[RELEASE_TAG_ENV_KEY] ?? DEFAULT_RELEASE_TAG);
21
+ const platform = normalizePlatform(options.platform ?? env[RELEASE_PLATFORM_ENV_KEY] ?? process.platform);
22
+ const configuredInstallDir = options.installDir ?? env[RELEASE_INSTALL_DIR_ENV_KEY];
23
+ const installDir = resolveReleaseInstallDir({
24
+ env,
25
+ ...(configuredInstallDir === undefined ? {} : { installDir: configuredInstallDir }),
26
+ platform,
27
+ tag,
28
+ });
29
+ const release = await fetchRelease({
30
+ fetchImpl: options.fetchImpl,
31
+ repository,
32
+ tag,
33
+ });
34
+ const releaseAssets = parseReleaseAssets(release.assets);
35
+ const checksumAssets = releaseAssets.filter((asset) => asset.name.toLowerCase().includes("sha256sums"));
36
+ const requestedSurfaces = options.selectedSurfaces.filter((surface) => surface === "web" || surface === "hud");
37
+ const installedAssets = [];
38
+ await mkdir(installDir, { recursive: true });
39
+ for (const surface of requestedSurfaces) {
40
+ const asset = selectSurfaceAsset(surface, platform, releaseAssets);
41
+ if (asset === null) {
42
+ throw new Error(`No ${surface} release asset found for ${platform} in ${repository}@${tag}.`);
43
+ }
44
+ const downloaded = await downloadAsset(options.fetchImpl, asset.browser_download_url);
45
+ const sha256 = sha256Hex(downloaded);
46
+ const checksum = await findChecksum({
47
+ assetName: asset.name,
48
+ checksumAssets,
49
+ fetchImpl: options.fetchImpl,
50
+ });
51
+ if (checksumAssets.length > 0 && checksum === null) {
52
+ throw new Error(`SHA256 checksum entry missing for ${asset.name}.`);
53
+ }
54
+ if (checksum !== null && checksum.toLowerCase() !== sha256) {
55
+ throw new Error(`SHA256 mismatch for ${asset.name}.`);
56
+ }
57
+ const outputPath = join(installDir, sanitizeAssetFileName(asset.name));
58
+ await writeFile(outputPath, downloaded);
59
+ let signature;
60
+ try {
61
+ signature = await verifyReleaseAssetSignature({
62
+ assetName: asset.name,
63
+ env,
64
+ fetchImpl: options.fetchImpl,
65
+ path: outputPath,
66
+ platform,
67
+ releaseAssets,
68
+ surface,
69
+ tag,
70
+ ...(options.signatureVerifier === undefined ? {} : { signatureVerifier: options.signatureVerifier }),
71
+ ...(options.trustedWindowsSignerThumbprints === undefined
72
+ ? {}
73
+ : { trustedWindowsSignerThumbprints: options.trustedWindowsSignerThumbprints }),
74
+ });
75
+ }
76
+ catch (error) {
77
+ await unlink(outputPath).catch(() => undefined);
78
+ throw error;
79
+ }
80
+ if (!signature.verified) {
81
+ await unlink(outputPath).catch(() => undefined);
82
+ throw new Error(`Release asset signature verification failed for ${asset.name}: ${signature.status} ${signature.message}`.trim());
83
+ }
84
+ installedAssets.push({
85
+ surface,
86
+ name: asset.name,
87
+ path: outputPath,
88
+ size: downloaded.byteLength,
89
+ sha256,
90
+ checksumVerified: checksum !== null,
91
+ signatureVerified: isVerifiedSignatureStatus(signature.status),
92
+ signatureStatus: signature.status,
93
+ });
94
+ }
95
+ await writeFile(join(installDir, "install-manifest.json"), `${JSON.stringify({
96
+ version: 1,
97
+ repository,
98
+ tag,
99
+ releaseUrl: typeof release.html_url === "string" ? release.html_url : releaseUrl(repository, tag),
100
+ installedAt: (options.now ?? (() => new Date()))().toISOString(),
101
+ selectedSurfaces: options.selectedSurfaces,
102
+ assets: installedAssets.map((asset) => ({
103
+ surface: asset.surface,
104
+ name: asset.name,
105
+ path: asset.path,
106
+ size: asset.size,
107
+ sha256: asset.sha256,
108
+ checksumVerified: asset.checksumVerified,
109
+ signatureVerified: asset.signatureVerified,
110
+ signatureStatus: asset.signatureStatus,
111
+ })),
112
+ }, null, 2)}\n`, "utf8");
113
+ return {
114
+ repository,
115
+ tag,
116
+ installDir,
117
+ releaseUrl: typeof release.html_url === "string" ? release.html_url : releaseUrl(repository, tag),
118
+ assets: installedAssets,
119
+ };
120
+ }
121
+ export function resolveReleaseInstallDir(input = {}) {
122
+ const env = input.env ?? process.env;
123
+ const platform = input.platform ?? process.platform;
124
+ const tag = input.tag ?? DEFAULT_RELEASE_TAG;
125
+ const configured = trimToNull(input.installDir ?? env[RELEASE_INSTALL_DIR_ENV_KEY]);
126
+ if (configured !== null) {
127
+ return isAbsoluteForPlatform(platform, configured) ? configured : resolve(process.cwd(), configured);
128
+ }
129
+ const root = platform === "win32"
130
+ ? joinForPlatform(platform, trimToNull(env.APPDATA) ?? win32.join(resolveHomeDirectory(env), "AppData", "Roaming"), "MoneySiren")
131
+ : platform === "darwin"
132
+ ? joinForPlatform(platform, resolveHomeDirectory(env), "Library", "Application Support", "MoneySiren")
133
+ : joinForPlatform(platform, trimToNull(env.XDG_DATA_HOME) ?? joinForPlatform(platform, resolveHomeDirectory(env), ".local", "share"), "moneysiren");
134
+ return joinForPlatform(platform, root, "releases", sanitizePathSegment(tag));
135
+ }
136
+ function normalizeRepository(repository) {
137
+ const normalized = repository.trim();
138
+ if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(normalized)) {
139
+ throw new Error("Release repository must be in owner/name form.");
140
+ }
141
+ return normalized;
142
+ }
143
+ function normalizeTag(tag) {
144
+ const normalized = tag.trim();
145
+ if (normalized.length === 0 || normalized.length > 128) {
146
+ throw new Error("Release tag is empty or too long.");
147
+ }
148
+ return normalized;
149
+ }
150
+ function normalizePlatform(platform) {
151
+ if (platform === "win32" || platform === "darwin" || platform === "linux") {
152
+ return platform;
153
+ }
154
+ return process.platform;
155
+ }
156
+ async function fetchRelease(input) {
157
+ const response = await input.fetchImpl(`https://api.github.com/repos/${input.repository}/releases/tags/${encodeURIComponent(input.tag)}`, {
158
+ headers: {
159
+ Accept: "application/vnd.github+json",
160
+ "User-Agent": "moneysiren-cli-release-installer",
161
+ },
162
+ });
163
+ if (!response.ok) {
164
+ throw new Error(`Could not read GitHub Release ${input.repository}@${input.tag}: ${response.status} ${response.statusText}`);
165
+ }
166
+ const body = await response.json();
167
+ if (!isRecord(body)) {
168
+ throw new Error("GitHub Release response was not an object.");
169
+ }
170
+ return body;
171
+ }
172
+ function parseReleaseAssets(value) {
173
+ if (!Array.isArray(value)) {
174
+ return [];
175
+ }
176
+ return value
177
+ .filter(isRecord)
178
+ .flatMap((asset) => {
179
+ const name = asset.name;
180
+ const browserDownloadUrl = asset.browser_download_url;
181
+ if (typeof name !== "string" || typeof browserDownloadUrl !== "string") {
182
+ return [];
183
+ }
184
+ return [{
185
+ name,
186
+ browser_download_url: browserDownloadUrl,
187
+ ...(typeof asset.size === "number" ? { size: asset.size } : {}),
188
+ }];
189
+ });
190
+ }
191
+ function selectSurfaceAsset(surface, platform, assets) {
192
+ const candidates = assets.filter((asset) => !asset.name.toLowerCase().includes("sha256sums"));
193
+ if (surface === "web") {
194
+ return candidates.find((asset) => /^moneysiren-web-runtime-.+\.tar\.gz$/i.test(asset.name)) ?? null;
195
+ }
196
+ if (platform === "win32") {
197
+ return candidates.find(isDirectWindowsHudAsset) ??
198
+ candidates.find((asset) => isWindowsHudAsset(asset.name)) ??
199
+ null;
200
+ }
201
+ if (platform === "darwin") {
202
+ return candidates.find((asset) => /macos/i.test(asset.name) && /\.(tar\.gz|dmg)$/i.test(asset.name)) ?? null;
203
+ }
204
+ return null;
205
+ }
206
+ function isDirectWindowsHudAsset(asset) {
207
+ return isWindowsHudAsset(asset.name) &&
208
+ /\.exe$/i.test(asset.name) &&
209
+ !isInstallerLikeWindowsAsset(asset.name);
210
+ }
211
+ function isWindowsHudAsset(name) {
212
+ return /\.(exe|msi)$/i.test(name);
213
+ }
214
+ function isInstallerLikeWindowsAsset(name) {
215
+ return /\.msi$/i.test(name) || /(?:^|[._ -])(?:setup|install|installer)(?:[._ -]|$)/i.test(name);
216
+ }
217
+ async function findChecksum(input) {
218
+ for (const checksumAsset of input.checksumAssets) {
219
+ const content = await downloadAsset(input.fetchImpl, checksumAsset.browser_download_url);
220
+ const checksum = parseChecksumFile(content.toString("utf8"), input.assetName);
221
+ if (checksum !== null) {
222
+ return checksum;
223
+ }
224
+ }
225
+ return null;
226
+ }
227
+ async function downloadAsset(fetchImpl, url) {
228
+ const parsed = new URL(url);
229
+ if (parsed.protocol !== "https:") {
230
+ throw new Error("Refusing to download a non-HTTPS release asset.");
231
+ }
232
+ const response = await fetchImpl(url, {
233
+ headers: {
234
+ "User-Agent": "moneysiren-cli-release-installer",
235
+ },
236
+ });
237
+ if (!response.ok) {
238
+ throw new Error(`Could not download release asset: ${response.status} ${response.statusText}`);
239
+ }
240
+ return Buffer.from(await response.arrayBuffer());
241
+ }
242
+ function parseChecksumFile(content, assetName) {
243
+ for (const line of content.split(/\r?\n/)) {
244
+ const match = /^([a-f0-9]{64})\s+\*?(.+)$/i.exec(line.trim());
245
+ if (match !== null && basename(match[2] ?? "") === assetName) {
246
+ return match[1] ?? null;
247
+ }
248
+ }
249
+ return null;
250
+ }
251
+ function sha256Hex(content) {
252
+ return createHash("sha256").update(content).digest("hex");
253
+ }
254
+ async function verifyReleaseAssetSignature(input) {
255
+ const verifier = input.signatureVerifier ?? defaultReleaseAssetSignatureVerifier;
256
+ const expectedSignerThumbprints = await findExpectedSignerThumbprints({
257
+ assetName: input.assetName,
258
+ env: input.env,
259
+ fetchImpl: input.fetchImpl,
260
+ platform: input.platform,
261
+ releaseAssets: input.releaseAssets,
262
+ surface: input.surface,
263
+ ...(input.trustedWindowsSignerThumbprints === undefined
264
+ ? {}
265
+ : { trustedWindowsSignerThumbprints: input.trustedWindowsSignerThumbprints }),
266
+ });
267
+ return verifier.verify({
268
+ assetName: input.assetName,
269
+ env: input.env,
270
+ ...(expectedSignerThumbprints === null ? {} : { expectedSignerThumbprints }),
271
+ path: input.path,
272
+ platform: input.platform,
273
+ surface: input.surface,
274
+ tag: input.tag,
275
+ });
276
+ }
277
+ const defaultReleaseAssetSignatureVerifier = {
278
+ async verify(input) {
279
+ if (input.surface !== "hud" || input.platform !== "win32") {
280
+ return {
281
+ verified: true,
282
+ status: "not-required",
283
+ message: "No platform signature check is required for this release asset.",
284
+ };
285
+ }
286
+ if (!/\.(exe|msi)$/i.test(input.assetName)) {
287
+ return {
288
+ verified: false,
289
+ status: "unsupported",
290
+ message: "Windows HUD release assets must be .exe or .msi artifacts.",
291
+ };
292
+ }
293
+ if (input.expectedSignerThumbprints === undefined || input.expectedSignerThumbprints.length === 0) {
294
+ if (isUnsignedPrereleaseHudAllowed(input.env, input.tag)) {
295
+ return {
296
+ verified: true,
297
+ status: "unsigned-prerelease-accepted",
298
+ message: "Unsigned Windows HUD artifact accepted for alpha prerelease.",
299
+ };
300
+ }
301
+ return {
302
+ verified: false,
303
+ status: "missing-signature-metadata",
304
+ message: `Windows HUD release assets require ${WINDOWS_SIGNER_THUMBPRINTS_ENV_KEY} or moneysiren-tray-windows-SIGNATURE.json metadata.`,
305
+ };
306
+ }
307
+ return verifyWindowsAuthenticodeSignature(input.path, input.expectedSignerThumbprints);
308
+ },
309
+ };
310
+ async function findExpectedSignerThumbprints(input) {
311
+ if (input.surface !== "hud" || input.platform !== "win32") {
312
+ return null;
313
+ }
314
+ const trustedThumbprints = normalizeThumbprintList([
315
+ ...(input.trustedWindowsSignerThumbprints ?? []),
316
+ ...parseThumbprintEnv(input.env[WINDOWS_SIGNER_THUMBPRINTS_ENV_KEY]),
317
+ ]);
318
+ if (trustedThumbprints.length > 0) {
319
+ return trustedThumbprints;
320
+ }
321
+ const metadataAsset = input.releaseAssets.find((asset) => /^moneysiren-tray-windows-SIGNATURE\.json$/i.test(asset.name));
322
+ if (metadataAsset === undefined) {
323
+ return null;
324
+ }
325
+ const metadata = JSON.parse((await downloadAsset(input.fetchImpl, metadataAsset.browser_download_url)).toString("utf8"));
326
+ const entries = Array.isArray(metadata) ? metadata : [metadata];
327
+ for (const entry of entries) {
328
+ if (!isRecord(entry) || entry.assetName !== input.assetName || typeof entry.signerThumbprint !== "string") {
329
+ continue;
330
+ }
331
+ return [normalizeThumbprint(entry.signerThumbprint)];
332
+ }
333
+ return null;
334
+ }
335
+ function isVerifiedSignatureStatus(status) {
336
+ return status !== "not-required" && status !== "unsigned-prerelease-accepted";
337
+ }
338
+ function isUnsignedPrereleaseHudAllowed(env, tag) {
339
+ const configured = env[ALLOW_UNSIGNED_HUD_ENV_KEY]?.trim().toLowerCase();
340
+ if (configured !== undefined && configured.length > 0) {
341
+ return ["1", "true", "yes", "on"].includes(configured);
342
+ }
343
+ return /-(?:alpha|beta|rc)(?:[.\d-]*)?$/i.test(tag);
344
+ }
345
+ async function verifyWindowsAuthenticodeSignature(path, expectedSignerThumbprints) {
346
+ const literalPath = powerShellSingleQuotedString(path);
347
+ try {
348
+ const { stdout } = await execFileAsync("powershell.exe", [
349
+ "-NoProfile",
350
+ "-NonInteractive",
351
+ "-Command",
352
+ [
353
+ `$signature = Get-AuthenticodeSignature -LiteralPath ${literalPath}`,
354
+ "$status = [string]$signature.Status",
355
+ "$message = [string]$signature.StatusMessage",
356
+ "if ($signature.Status -ne 'Valid' -or $null -eq $signature.SignerCertificate) {",
357
+ " Write-Output ($status + \"|\" + $message)",
358
+ " exit 1",
359
+ "}",
360
+ "Write-Output ($status + \"|\" + $signature.SignerCertificate.Thumbprint + \"|\" + $signature.SignerCertificate.Subject)",
361
+ ].join("; "),
362
+ ], {
363
+ windowsHide: true,
364
+ timeout: 30_000,
365
+ });
366
+ const [status, signerThumbprint, ...messageParts] = stdout.trim().split("|");
367
+ const normalizedSignerThumbprint = normalizeThumbprint(signerThumbprint ?? "");
368
+ const normalizedExpectedSignerThumbprints = expectedSignerThumbprints.map(normalizeThumbprint);
369
+ if (!normalizedExpectedSignerThumbprints.includes(normalizedSignerThumbprint)) {
370
+ return {
371
+ verified: false,
372
+ status: "signer-mismatch",
373
+ message: `Expected signer ${normalizedExpectedSignerThumbprints.join(", ")}, got ${normalizedSignerThumbprint || "unknown"}.`,
374
+ };
375
+ }
376
+ return {
377
+ verified: true,
378
+ status: status ?? "Valid",
379
+ message: messageParts.join("|"),
380
+ };
381
+ }
382
+ catch (error) {
383
+ const output = isRecord(error) && typeof error.stdout === "string" ? error.stdout.trim() : "";
384
+ const [status, ...messageParts] = output.split("|");
385
+ return {
386
+ verified: false,
387
+ status: status && status.length > 0 ? status : "Unknown",
388
+ message: messageParts.join("|") || (error instanceof Error ? error.message : String(error)),
389
+ };
390
+ }
391
+ }
392
+ function normalizeThumbprint(value) {
393
+ return value.replaceAll(/\s/g, "").toUpperCase();
394
+ }
395
+ function normalizeThumbprintList(values) {
396
+ return Array.from(new Set(values.map(normalizeThumbprint).filter((value) => value.length > 0)));
397
+ }
398
+ function parseThumbprintEnv(value) {
399
+ if (value === undefined) {
400
+ return [];
401
+ }
402
+ return value.split(/[,\s;]+/).map((part) => part.trim()).filter((part) => part.length > 0);
403
+ }
404
+ function powerShellSingleQuotedString(value) {
405
+ return `'${value.replaceAll("'", "''")}'`;
406
+ }
407
+ function sanitizeAssetFileName(name) {
408
+ return basename(name).replace(/[^A-Za-z0-9._ -]/g, "_");
409
+ }
410
+ function sanitizePathSegment(value) {
411
+ return value.replace(/[^A-Za-z0-9._-]/g, "_");
412
+ }
413
+ function releaseUrl(repository, tag) {
414
+ return `https://github.com/${repository}/releases/tag/${encodeURIComponent(tag)}`;
415
+ }
416
+ function resolveHomeDirectory(env) {
417
+ return trimToNull(env.HOME) ?? trimToNull(env.USERPROFILE) ?? homedir();
418
+ }
419
+ function trimToNull(value) {
420
+ const trimmed = value?.trim();
421
+ return trimmed === undefined || trimmed.length === 0 ? null : trimmed;
422
+ }
423
+ function joinForPlatform(platform, ...segments) {
424
+ return platform === "win32" ? win32.join(...segments) : posix.join(...segments);
425
+ }
426
+ function isAbsoluteForPlatform(platform, value) {
427
+ return platform === "win32" ? win32.isAbsolute(value) : posix.isAbsolute(value);
428
+ }
429
+ function isRecord(value) {
430
+ return typeof value === "object" && value !== null && !Array.isArray(value);
431
+ }
432
+ //# sourceMappingURL=release-installer.js.map
@@ -64,7 +64,7 @@ export async function openUrlInBrowser(url) {
64
64
  throw new Error("Refusing to open a non-loopback runtime URL.");
65
65
  }
66
66
  const child = process.platform === "win32"
67
- ? spawn("cmd", ["/c", "start", "", parsedUrl.toString()], {
67
+ ? spawn("rundll32.exe", ["url.dll,FileProtocolHandler", parsedUrl.toString()], {
68
68
  detached: true,
69
69
  stdio: "ignore",
70
70
  windowsHide: true,
@@ -34,6 +34,33 @@ export function resolveSlashCommand(args) {
34
34
  if (command === "/modes") {
35
35
  return noExtraArgs(command, rest, ["modes"]);
36
36
  }
37
+ if (command === "/start") {
38
+ return {
39
+ kind: "dispatch",
40
+ args: ["start", ...rest],
41
+ };
42
+ }
43
+ if (command === "/hud") {
44
+ return {
45
+ kind: "dispatch",
46
+ args: ["hud", ...rest],
47
+ };
48
+ }
49
+ if (command === "/status") {
50
+ return noExtraArgs(command, rest, ["status"]);
51
+ }
52
+ if (command === "/stop") {
53
+ return {
54
+ kind: "dispatch",
55
+ args: ["stop", ...rest],
56
+ };
57
+ }
58
+ if (command === "/restart") {
59
+ return {
60
+ kind: "dispatch",
61
+ args: ["restart", ...rest],
62
+ };
63
+ }
37
64
  if (command === "/init") {
38
65
  return noExtraArgs(command, rest, ["init"]);
39
66
  }
@@ -0,0 +1,2 @@
1
+ export declare const CLI_VERSION = "0.1.0-alpha.10";
2
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1,2 @@
1
+ export const CLI_VERSION = "0.1.0-alpha.10";
2
+ //# sourceMappingURL=version.js.map
@@ -57,7 +57,10 @@ function loadProviders(env) {
57
57
  datadog: loadProviderConfig("datadog", env),
58
58
  sentry: loadProviderConfig("sentry", env),
59
59
  "codex-cli": loadProviderConfig("codex-cli", env),
60
+ "codex-app": loadProviderConfig("codex-app", env),
60
61
  "claude-cli": loadProviderConfig("claude-cli", env),
62
+ "claude-app": loadProviderConfig("claude-app", env),
63
+ antigravity: loadProviderConfig("antigravity", env),
61
64
  };
62
65
  }
63
66
  function loadProviderConfig(provider, env) {
@@ -20,7 +20,10 @@ export declare const PROVIDER_ENV_KEYS: {
20
20
  readonly datadog: readonly ["DATADOG_API_KEY", "DATADOG_APP_KEY", "DATADOG_SITE"];
21
21
  readonly sentry: readonly ["SENTRY_AUTH_TOKEN", "SENTRY_ORG"];
22
22
  readonly "codex-cli": readonly [];
23
+ readonly "codex-app": readonly [];
23
24
  readonly "claude-cli": readonly [];
25
+ readonly "claude-app": readonly [];
26
+ readonly antigravity: readonly [];
24
27
  };
25
28
  export type ConfiguredProvider = keyof typeof PROVIDER_ENV_KEYS;
26
29
  export interface ProviderConfig {
@@ -20,6 +20,9 @@ export const PROVIDER_ENV_KEYS = {
20
20
  datadog: ["DATADOG_API_KEY", "DATADOG_APP_KEY", "DATADOG_SITE"],
21
21
  sentry: ["SENTRY_AUTH_TOKEN", "SENTRY_ORG"],
22
22
  "codex-cli": [],
23
+ "codex-app": [],
23
24
  "claude-cli": [],
25
+ "claude-app": [],
26
+ antigravity: [],
24
27
  };
25
28
  //# sourceMappingURL=schema.js.map
@@ -3,7 +3,7 @@ import { parseNotificationPreferences, readNotificationDigest, readNotificationP
3
3
  import { assertLoopbackHost, isLoopbackHost, removeRuntimeLock, writeRuntimeLock, } from "../../runtime/src/index.js";
4
4
  const DEFAULT_HOST = "127.0.0.1";
5
5
  const DEFAULT_PORT = 47831;
6
- const DEFAULT_VERSION = "0.1.0-alpha.0";
6
+ const DEFAULT_VERSION = "0.1.0-alpha.10";
7
7
  export async function startLocalApiServer(options = {}) {
8
8
  const host = options.host ?? DEFAULT_HOST;
9
9
  const requestedPort = options.port ?? DEFAULT_PORT;