@moneysiren/cli 0.1.0-alpha.0 → 0.1.0-alpha.2
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 +6 -3
- package/dist/apps/cli/src/cli.d.ts +4 -1
- package/dist/apps/cli/src/cli.js +11 -3
- package/dist/apps/cli/src/commands/install.js +125 -6
- package/dist/apps/cli/src/commands/modes.js +15 -10
- package/dist/apps/cli/src/commands/runtime.d.ts +2 -0
- package/dist/apps/cli/src/commands/runtime.js +167 -0
- package/dist/apps/cli/src/desktop-runtime.d.ts +31 -0
- package/dist/apps/cli/src/desktop-runtime.js +441 -0
- package/dist/apps/cli/src/home.js +16 -0
- package/dist/apps/cli/src/index.js +0 -0
- package/dist/apps/cli/src/postinstall.js +1 -1
- package/dist/apps/cli/src/release-installer.d.ts +54 -0
- package/dist/apps/cli/src/release-installer.js +393 -0
- package/dist/apps/cli/src/slash.js +12 -0
- package/dist/apps/cli/src/version.d.ts +2 -0
- package/dist/apps/cli/src/version.js +2 -0
- 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
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import { execFile, spawn } from "node:child_process";
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import { access, mkdir, readFile, readdir, stat } from "node:fs/promises";
|
|
4
|
+
import { basename, dirname, extname, join, resolve } from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { resolveReleaseInstallDir } from "./release-installer.js";
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
|
+
const DEFAULT_WEB_PORT = 3000;
|
|
9
|
+
const DEFAULT_HEALTH_TIMEOUT_MS = 30_000;
|
|
10
|
+
export function createFallbackDesktopRuntimeAdapter(context) {
|
|
11
|
+
return {
|
|
12
|
+
async startWebRuntime(options) {
|
|
13
|
+
const port = options.port ?? configuredPort(context.env);
|
|
14
|
+
const dashboardUrl = `http://127.0.0.1:${port}/ko/dashboard/overview`;
|
|
15
|
+
const healthUrl = `http://127.0.0.1:${port}/api/local/health`;
|
|
16
|
+
if (await isWebRuntimeHealthy(healthUrl, context.fetch)) {
|
|
17
|
+
return {
|
|
18
|
+
status: "running",
|
|
19
|
+
dashboardUrl,
|
|
20
|
+
notes: ["Existing local dashboard runtime is healthy."],
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const startScript = await resolveWebRuntimeStartScript(context);
|
|
24
|
+
if (startScript.status === "unavailable") {
|
|
25
|
+
return startScript;
|
|
26
|
+
}
|
|
27
|
+
const child = spawn(process.execPath, [startScript.path], {
|
|
28
|
+
cwd: dirname(startScript.path),
|
|
29
|
+
detached: true,
|
|
30
|
+
env: {
|
|
31
|
+
...process.env,
|
|
32
|
+
...context.env,
|
|
33
|
+
HOSTNAME: "127.0.0.1",
|
|
34
|
+
PORT: String(port),
|
|
35
|
+
},
|
|
36
|
+
stdio: "ignore",
|
|
37
|
+
windowsHide: true,
|
|
38
|
+
});
|
|
39
|
+
child.unref();
|
|
40
|
+
if (!await waitForWebRuntime(healthUrl, context.fetch, DEFAULT_HEALTH_TIMEOUT_MS)) {
|
|
41
|
+
return {
|
|
42
|
+
status: "unavailable",
|
|
43
|
+
reason: "Started the local web runtime, but the health check did not become ready.",
|
|
44
|
+
guidance: [
|
|
45
|
+
`Check ${dashboardUrl} in your browser.`,
|
|
46
|
+
"If the port is already in use, run `msiren start --port <port>`.",
|
|
47
|
+
],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
status: "started",
|
|
52
|
+
dashboardUrl,
|
|
53
|
+
...(child.pid === undefined ? {} : { pid: child.pid }),
|
|
54
|
+
notes: startScript.notes,
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
async startHud(options) {
|
|
58
|
+
const executable = await resolveDesktopExecutable(context);
|
|
59
|
+
if (isUnavailable(executable)) {
|
|
60
|
+
return executable;
|
|
61
|
+
}
|
|
62
|
+
const child = spawn(executable.command, executable.args, {
|
|
63
|
+
...(executable.cwd === undefined ? {} : { cwd: executable.cwd }),
|
|
64
|
+
detached: true,
|
|
65
|
+
env: {
|
|
66
|
+
...process.env,
|
|
67
|
+
...context.env,
|
|
68
|
+
MONEYSIREN_DESKTOP_MODE: "hud",
|
|
69
|
+
MONEYSIREN_WEB_URL: `http://127.0.0.1:${options.port ?? configuredPort(context.env)}`,
|
|
70
|
+
},
|
|
71
|
+
stdio: "ignore",
|
|
72
|
+
windowsHide: true,
|
|
73
|
+
});
|
|
74
|
+
child.unref();
|
|
75
|
+
return {
|
|
76
|
+
status: "started",
|
|
77
|
+
executablePath: executable.executablePath,
|
|
78
|
+
...(child.pid === undefined ? {} : { pid: child.pid }),
|
|
79
|
+
notes: ["Desktop HUD shell launched with MONEYSIREN_DESKTOP_MODE=hud."],
|
|
80
|
+
};
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
async function resolveWebRuntimeStartScript(context) {
|
|
85
|
+
const configured = trimToNull(context.env.MONEYSIREN_WEB_RUNTIME_DIR);
|
|
86
|
+
if (configured !== null) {
|
|
87
|
+
const configuredStart = await findStartScript(resolve(context.cwd, configured));
|
|
88
|
+
if (configuredStart !== null) {
|
|
89
|
+
return {
|
|
90
|
+
status: "ready",
|
|
91
|
+
path: configuredStart,
|
|
92
|
+
notes: ["Using MONEYSIREN_WEB_RUNTIME_DIR."],
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
status: "unavailable",
|
|
97
|
+
reason: "MONEYSIREN_WEB_RUNTIME_DIR does not contain a MoneySiren web runtime.",
|
|
98
|
+
guidance: [
|
|
99
|
+
"Point MONEYSIREN_WEB_RUNTIME_DIR at the extracted moneysiren-web-runtime directory.",
|
|
100
|
+
"Or run `msiren install --web` and then `msiren start`.",
|
|
101
|
+
],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
const installDir = resolveReleaseInstallDir({ env: context.env });
|
|
105
|
+
const extractedRoot = join(installDir, "web-runtime");
|
|
106
|
+
const existingStart = await findStartScript(extractedRoot) ?? await findStartScript(join(installDir, "moneysiren-web-runtime"));
|
|
107
|
+
if (existingStart !== null) {
|
|
108
|
+
return {
|
|
109
|
+
status: "ready",
|
|
110
|
+
path: existingStart,
|
|
111
|
+
notes: ["Using previously extracted GitHub Release web runtime."],
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const manifest = await readInstallManifest(installDir);
|
|
115
|
+
const webAsset = manifest?.assets.find((asset) => asset.surface === "web");
|
|
116
|
+
if (webAsset === undefined) {
|
|
117
|
+
return {
|
|
118
|
+
status: "unavailable",
|
|
119
|
+
reason: "No installed web runtime asset was found.",
|
|
120
|
+
guidance: [
|
|
121
|
+
"Install the release web runtime first: `msiren install --web` or `msiren install --all`.",
|
|
122
|
+
"If you already extracted it manually, set MONEYSIREN_WEB_RUNTIME_DIR to that directory.",
|
|
123
|
+
],
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (!await pathExists(webAsset.path)) {
|
|
127
|
+
return {
|
|
128
|
+
status: "unavailable",
|
|
129
|
+
reason: "The installed web runtime archive listed in the manifest is missing.",
|
|
130
|
+
guidance: [
|
|
131
|
+
"Run `msiren install --web` again.",
|
|
132
|
+
"If you moved the runtime, set MONEYSIREN_WEB_RUNTIME_DIR to the extracted directory.",
|
|
133
|
+
],
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
await mkdir(extractedRoot, { recursive: true });
|
|
138
|
+
await execFileAsync("tar", ["-xzf", webAsset.path, "-C", extractedRoot], {
|
|
139
|
+
windowsHide: true,
|
|
140
|
+
timeout: 120_000,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
return {
|
|
145
|
+
status: "unavailable",
|
|
146
|
+
reason: `Could not extract the installed web runtime archive: ${errorMessage(error)}`,
|
|
147
|
+
guidance: [
|
|
148
|
+
"Install a system tar command, or extract the moneysiren-web-runtime archive manually.",
|
|
149
|
+
"Then set MONEYSIREN_WEB_RUNTIME_DIR to the extracted directory and rerun `msiren start`.",
|
|
150
|
+
],
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const extractedStart = await findStartScript(extractedRoot);
|
|
154
|
+
if (extractedStart === null) {
|
|
155
|
+
return {
|
|
156
|
+
status: "unavailable",
|
|
157
|
+
reason: "The extracted web runtime did not contain start.mjs.",
|
|
158
|
+
guidance: [
|
|
159
|
+
"Run `msiren install --web` again.",
|
|
160
|
+
"If this repeats, the GitHub Release web runtime asset is incomplete.",
|
|
161
|
+
],
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
status: "ready",
|
|
166
|
+
path: extractedStart,
|
|
167
|
+
notes: ["Extracted GitHub Release web runtime automatically."],
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
async function resolveDesktopExecutable(context) {
|
|
171
|
+
const configured = trimToNull(context.env.MONEYSIREN_DESKTOP_APP);
|
|
172
|
+
if (configured !== null) {
|
|
173
|
+
const executable = await executableFromPath(resolve(context.cwd, configured), true);
|
|
174
|
+
if (executable !== null) {
|
|
175
|
+
return executable;
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
status: "unavailable",
|
|
179
|
+
reason: "MONEYSIREN_DESKTOP_APP does not point to a runnable MoneySiren desktop app.",
|
|
180
|
+
guidance: [
|
|
181
|
+
"Set MONEYSIREN_DESKTOP_APP to the installed MoneySiren executable or macOS .app bundle.",
|
|
182
|
+
"Or run `msiren install --hud` and use a release desktop artifact.",
|
|
183
|
+
],
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
const installedExecutable = await findInstalledDesktopApp(context.env);
|
|
187
|
+
if (installedExecutable !== null) {
|
|
188
|
+
return installedExecutable;
|
|
189
|
+
}
|
|
190
|
+
const installDir = resolveReleaseInstallDir({ env: context.env });
|
|
191
|
+
const manifest = await readInstallManifest(installDir);
|
|
192
|
+
const hudAsset = manifest?.assets.find((asset) => asset.surface === "hud");
|
|
193
|
+
if (hudAsset === undefined) {
|
|
194
|
+
return {
|
|
195
|
+
status: "unavailable",
|
|
196
|
+
reason: "No installed HUD desktop artifact was found.",
|
|
197
|
+
guidance: [
|
|
198
|
+
"Install the desktop artifact first: `msiren install --hud` or `msiren install --all`.",
|
|
199
|
+
"If MoneySiren is already installed, set MONEYSIREN_DESKTOP_APP to the app path.",
|
|
200
|
+
],
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
if (!await pathExists(hudAsset.path)) {
|
|
204
|
+
return {
|
|
205
|
+
status: "unavailable",
|
|
206
|
+
reason: "The installed HUD artifact listed in the manifest is missing.",
|
|
207
|
+
guidance: [
|
|
208
|
+
"Run `msiren install --hud` again.",
|
|
209
|
+
"If the desktop app is installed elsewhere, set MONEYSIREN_DESKTOP_APP to that path.",
|
|
210
|
+
],
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
const executable = await executableFromPath(hudAsset.path, false);
|
|
214
|
+
if (executable !== null) {
|
|
215
|
+
return executable;
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
status: "unavailable",
|
|
219
|
+
reason: "The installed HUD artifact is an installer or archive, not a directly runnable desktop shell.",
|
|
220
|
+
guidance: [
|
|
221
|
+
"Run the downloaded desktop installer once, then rerun `msiren hud`.",
|
|
222
|
+
"If the installed app is not found automatically, set MONEYSIREN_DESKTOP_APP to the executable or .app path.",
|
|
223
|
+
"Portable desktop artifacts can be launched directly by `msiren hud` when published.",
|
|
224
|
+
],
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
async function executableFromPath(path, allowInstaller) {
|
|
228
|
+
const pathStat = await stat(path).catch(() => null);
|
|
229
|
+
if (pathStat === null) {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
if (process.platform === "darwin" && pathStat.isDirectory() && path.endsWith(".app")) {
|
|
233
|
+
const executable = await findMacAppExecutable(path);
|
|
234
|
+
return executable === null
|
|
235
|
+
? null
|
|
236
|
+
: {
|
|
237
|
+
command: executable,
|
|
238
|
+
args: [],
|
|
239
|
+
executablePath: path,
|
|
240
|
+
cwd: dirname(executable),
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
if (pathStat.isDirectory()) {
|
|
244
|
+
const startScript = await findStartScript(path);
|
|
245
|
+
return startScript === null
|
|
246
|
+
? null
|
|
247
|
+
: {
|
|
248
|
+
command: process.execPath,
|
|
249
|
+
args: [startScript],
|
|
250
|
+
executablePath: startScript,
|
|
251
|
+
cwd: dirname(startScript),
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
if (process.platform === "darwin" && /\.tar\.gz$/i.test(path)) {
|
|
255
|
+
const extractRoot = join(dirname(path), "desktop");
|
|
256
|
+
await mkdir(extractRoot, { recursive: true });
|
|
257
|
+
await execFileAsync("tar", ["-xzf", path, "-C", extractRoot], {
|
|
258
|
+
windowsHide: true,
|
|
259
|
+
timeout: 120_000,
|
|
260
|
+
}).catch(() => undefined);
|
|
261
|
+
const app = await findFirstMacApp(extractRoot);
|
|
262
|
+
return app === null ? null : executableFromPath(app, allowInstaller);
|
|
263
|
+
}
|
|
264
|
+
if (process.platform === "win32" && /\.(exe)$/i.test(path)) {
|
|
265
|
+
const fileName = basename(path).toLowerCase();
|
|
266
|
+
if (!allowInstaller && /(setup|install|installer)/i.test(fileName)) {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
command: path,
|
|
271
|
+
args: [],
|
|
272
|
+
executablePath: path,
|
|
273
|
+
cwd: dirname(path),
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
async function findInstalledDesktopApp(env) {
|
|
279
|
+
if (process.platform === "win32") {
|
|
280
|
+
const roots = [
|
|
281
|
+
trimToNull(env.LOCALAPPDATA),
|
|
282
|
+
trimToNull(env.ProgramFiles),
|
|
283
|
+
trimToNull(env["ProgramFiles(x86)"]),
|
|
284
|
+
].filter((value) => value !== null);
|
|
285
|
+
const candidates = roots.flatMap((root) => [
|
|
286
|
+
join(root, "Programs", "MoneySiren Tray", "MoneySiren Tray.exe"),
|
|
287
|
+
join(root, "Programs", "MoneySiren Tray", "moneysiren-tray.exe"),
|
|
288
|
+
join(root, "MoneySiren Tray", "MoneySiren Tray.exe"),
|
|
289
|
+
join(root, "MoneySiren Tray", "moneysiren-tray.exe"),
|
|
290
|
+
]);
|
|
291
|
+
for (const candidate of candidates) {
|
|
292
|
+
const executable = await executableFromPath(candidate, true);
|
|
293
|
+
if (executable !== null) {
|
|
294
|
+
return executable;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (process.platform === "darwin") {
|
|
299
|
+
return await executableFromPath("/Applications/MoneySiren Tray.app", true);
|
|
300
|
+
}
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
async function readInstallManifest(installDir) {
|
|
304
|
+
try {
|
|
305
|
+
const parsed = JSON.parse(await readFile(join(installDir, "install-manifest.json"), "utf8"));
|
|
306
|
+
if (!isRecord(parsed) || !Array.isArray(parsed.assets)) {
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
return {
|
|
310
|
+
assets: parsed.assets.filter(isInstallManifestAsset),
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
function isInstallManifestAsset(value) {
|
|
318
|
+
return isRecord(value) &&
|
|
319
|
+
(value.surface === "web" || value.surface === "hud") &&
|
|
320
|
+
typeof value.name === "string" &&
|
|
321
|
+
typeof value.path === "string";
|
|
322
|
+
}
|
|
323
|
+
function isUnavailable(value) {
|
|
324
|
+
return "status" in value && value.status === "unavailable";
|
|
325
|
+
}
|
|
326
|
+
async function findStartScript(root) {
|
|
327
|
+
const candidates = [
|
|
328
|
+
join(root, "start.mjs"),
|
|
329
|
+
join(root, "moneysiren-web-runtime", "start.mjs"),
|
|
330
|
+
];
|
|
331
|
+
for (const candidate of candidates) {
|
|
332
|
+
if (await isReadableFile(candidate)) {
|
|
333
|
+
return candidate;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
async function findFirstMacApp(root) {
|
|
339
|
+
const entries = await readdir(root, { withFileTypes: true }).catch(() => []);
|
|
340
|
+
for (const entry of entries) {
|
|
341
|
+
const path = join(root, entry.name);
|
|
342
|
+
if (entry.isDirectory() && extname(entry.name) === ".app") {
|
|
343
|
+
return path;
|
|
344
|
+
}
|
|
345
|
+
if (entry.isDirectory()) {
|
|
346
|
+
const nested = await findFirstMacApp(path);
|
|
347
|
+
if (nested !== null) {
|
|
348
|
+
return nested;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
async function findMacAppExecutable(appPath) {
|
|
355
|
+
const macOsDir = join(appPath, "Contents", "MacOS");
|
|
356
|
+
const entries = await readdir(macOsDir, { withFileTypes: true }).catch(() => []);
|
|
357
|
+
for (const entry of entries) {
|
|
358
|
+
const path = join(macOsDir, entry.name);
|
|
359
|
+
if (entry.isFile() && await isExecutableFile(path)) {
|
|
360
|
+
return path;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
async function waitForWebRuntime(url, fetchImpl, timeoutMs) {
|
|
366
|
+
const deadline = Date.now() + timeoutMs;
|
|
367
|
+
while (Date.now() < deadline) {
|
|
368
|
+
if (await isWebRuntimeHealthy(url, fetchImpl)) {
|
|
369
|
+
return true;
|
|
370
|
+
}
|
|
371
|
+
await new Promise((resolveTimeout) => setTimeout(resolveTimeout, 1_000));
|
|
372
|
+
}
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
async function isWebRuntimeHealthy(url, fetchImpl) {
|
|
376
|
+
const controller = new AbortController();
|
|
377
|
+
const timeout = setTimeout(() => controller.abort(), 2_000);
|
|
378
|
+
try {
|
|
379
|
+
const response = await fetchImpl(url, {
|
|
380
|
+
cache: "no-store",
|
|
381
|
+
signal: controller.signal,
|
|
382
|
+
});
|
|
383
|
+
return response.ok;
|
|
384
|
+
}
|
|
385
|
+
catch {
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
finally {
|
|
389
|
+
clearTimeout(timeout);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
async function isReadableFile(path) {
|
|
393
|
+
try {
|
|
394
|
+
const pathStat = await stat(path);
|
|
395
|
+
if (!pathStat.isFile()) {
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
await access(path, constants.R_OK);
|
|
399
|
+
return true;
|
|
400
|
+
}
|
|
401
|
+
catch {
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
async function isExecutableFile(path) {
|
|
406
|
+
try {
|
|
407
|
+
const pathStat = await stat(path);
|
|
408
|
+
if (!pathStat.isFile()) {
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
await access(path, constants.X_OK);
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
414
|
+
catch {
|
|
415
|
+
return process.platform === "win32" && /\.(exe|cmd|bat)$/i.test(path);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
async function pathExists(path) {
|
|
419
|
+
try {
|
|
420
|
+
await access(path, constants.F_OK);
|
|
421
|
+
return true;
|
|
422
|
+
}
|
|
423
|
+
catch {
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
function configuredPort(env) {
|
|
428
|
+
const parsed = Number.parseInt(env.PORT ?? "", 10);
|
|
429
|
+
return Number.isSafeInteger(parsed) && parsed > 0 && parsed <= 65_535 ? parsed : DEFAULT_WEB_PORT;
|
|
430
|
+
}
|
|
431
|
+
function trimToNull(value) {
|
|
432
|
+
const trimmed = value?.trim();
|
|
433
|
+
return trimmed === undefined || trimmed.length === 0 ? null : trimmed;
|
|
434
|
+
}
|
|
435
|
+
function errorMessage(error) {
|
|
436
|
+
return error instanceof Error ? error.message : String(error);
|
|
437
|
+
}
|
|
438
|
+
function isRecord(value) {
|
|
439
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
440
|
+
}
|
|
441
|
+
//# sourceMappingURL=desktop-runtime.js.map
|
|
@@ -10,6 +10,8 @@ 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`,
|
|
13
15
|
` ${theme.command("/init")} Create local SQLite storage`,
|
|
14
16
|
` ${theme.command("/dashboard")} Check the local dashboard API`,
|
|
15
17
|
` ${theme.command("/dashboard check")} Same as /dashboard`,
|
|
@@ -27,6 +29,11 @@ export function renderHomeScreen(input) {
|
|
|
27
29
|
` ${theme.command("/quit")} Exit the slash prompt`,
|
|
28
30
|
"",
|
|
29
31
|
theme.heading("Classic CLI"),
|
|
32
|
+
" msiren start",
|
|
33
|
+
" msiren hud",
|
|
34
|
+
" msiren install --all",
|
|
35
|
+
"",
|
|
36
|
+
theme.heading("Full command"),
|
|
30
37
|
" moneysiren doctor",
|
|
31
38
|
" moneysiren install",
|
|
32
39
|
" moneysiren install --status",
|
|
@@ -54,6 +61,11 @@ export function renderHelpScreen(version) {
|
|
|
54
61
|
|
|
55
62
|
Local-first cloud/SaaS usage, status, and expected billing dashboard.
|
|
56
63
|
|
|
64
|
+
Short command:
|
|
65
|
+
msiren start
|
|
66
|
+
msiren hud
|
|
67
|
+
msiren install --all
|
|
68
|
+
|
|
57
69
|
Usage:
|
|
58
70
|
moneysiren
|
|
59
71
|
moneysiren --help
|
|
@@ -62,6 +74,8 @@ Usage:
|
|
|
62
74
|
moneysiren install [--status|--all|--cli|--web|--hud|--no-cli|--no-web|--no-hud]
|
|
63
75
|
moneysiren doctor
|
|
64
76
|
moneysiren modes
|
|
77
|
+
moneysiren start [--port <port>] [--open|--no-open] [--hud]
|
|
78
|
+
moneysiren hud [--port <port>]
|
|
65
79
|
moneysiren dashboard check [--url <local-dashboard-url>]
|
|
66
80
|
moneysiren serve [--port <port>]
|
|
67
81
|
moneysiren open
|
|
@@ -81,6 +95,8 @@ Slash commands:
|
|
|
81
95
|
moneysiren /doctor
|
|
82
96
|
moneysiren /install
|
|
83
97
|
moneysiren /modes
|
|
98
|
+
moneysiren /start
|
|
99
|
+
moneysiren /hud
|
|
84
100
|
moneysiren /init
|
|
85
101
|
moneysiren /dashboard
|
|
86
102
|
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 `
|
|
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,54 @@
|
|
|
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.2";
|
|
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
|
+
}
|
|
32
|
+
export interface ReleaseAssetSignatureVerifier {
|
|
33
|
+
verify(input: ReleaseAssetSignatureVerificationInput): Promise<ReleaseAssetSignatureVerificationResult>;
|
|
34
|
+
}
|
|
35
|
+
export interface ReleaseAssetSignatureVerificationInput {
|
|
36
|
+
assetName: string;
|
|
37
|
+
expectedSignerThumbprints?: readonly string[];
|
|
38
|
+
path: string;
|
|
39
|
+
platform: NodeJS.Platform;
|
|
40
|
+
surface: Exclude<InstallSurface, "cli">;
|
|
41
|
+
}
|
|
42
|
+
export interface ReleaseAssetSignatureVerificationResult {
|
|
43
|
+
verified: boolean;
|
|
44
|
+
status: string;
|
|
45
|
+
message: string;
|
|
46
|
+
}
|
|
47
|
+
export declare function installReleaseAssets(options: ReleaseInstallOptions): Promise<ReleaseInstallResult>;
|
|
48
|
+
export declare function resolveReleaseInstallDir(input?: {
|
|
49
|
+
env?: Record<string, string | undefined>;
|
|
50
|
+
installDir?: string;
|
|
51
|
+
platform?: NodeJS.Platform;
|
|
52
|
+
tag?: string;
|
|
53
|
+
}): string;
|
|
54
|
+
//# sourceMappingURL=release-installer.d.ts.map
|