@moneysiren/app 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 (140) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +45 -0
  3. package/dist/apps/cli/src/cli.d.ts +59 -0
  4. package/dist/apps/cli/src/cli.js +199 -0
  5. package/dist/apps/cli/src/commands/dashboard.d.ts +3 -0
  6. package/dist/apps/cli/src/commands/dashboard.js +239 -0
  7. package/dist/apps/cli/src/commands/doctor.d.ts +3 -0
  8. package/dist/apps/cli/src/commands/doctor.js +25 -0
  9. package/dist/apps/cli/src/commands/init.d.ts +3 -0
  10. package/dist/apps/cli/src/commands/init.js +18 -0
  11. package/dist/apps/cli/src/commands/install.d.ts +3 -0
  12. package/dist/apps/cli/src/commands/install.js +244 -0
  13. package/dist/apps/cli/src/commands/modes.d.ts +3 -0
  14. package/dist/apps/cli/src/commands/modes.js +73 -0
  15. package/dist/apps/cli/src/commands/notify.d.ts +3 -0
  16. package/dist/apps/cli/src/commands/notify.js +430 -0
  17. package/dist/apps/cli/src/commands/report.d.ts +3 -0
  18. package/dist/apps/cli/src/commands/report.js +206 -0
  19. package/dist/apps/cli/src/commands/runtime.d.ts +10 -0
  20. package/dist/apps/cli/src/commands/runtime.js +499 -0
  21. package/dist/apps/cli/src/commands/shared.d.ts +9 -0
  22. package/dist/apps/cli/src/commands/shared.js +29 -0
  23. package/dist/apps/cli/src/commands/summary.d.ts +3 -0
  24. package/dist/apps/cli/src/commands/summary.js +15 -0
  25. package/dist/apps/cli/src/commands/sync.d.ts +3 -0
  26. package/dist/apps/cli/src/commands/sync.js +393 -0
  27. package/dist/apps/cli/src/commands/theme.d.ts +3 -0
  28. package/dist/apps/cli/src/commands/theme.js +181 -0
  29. package/dist/apps/cli/src/desktop-runtime.d.ts +54 -0
  30. package/dist/apps/cli/src/desktop-runtime.js +720 -0
  31. package/dist/apps/cli/src/home.d.ts +7 -0
  32. package/dist/apps/cli/src/home.js +124 -0
  33. package/dist/apps/cli/src/index.d.ts +3 -0
  34. package/dist/apps/cli/src/index.js +14 -0
  35. package/dist/apps/cli/src/install-profile.d.ts +35 -0
  36. package/dist/apps/cli/src/install-profile.js +124 -0
  37. package/dist/apps/cli/src/install-selector.d.ts +10 -0
  38. package/dist/apps/cli/src/install-selector.js +66 -0
  39. package/dist/apps/cli/src/interactive.d.ts +3 -0
  40. package/dist/apps/cli/src/interactive.js +32 -0
  41. package/dist/apps/cli/src/postinstall.d.ts +3 -0
  42. package/dist/apps/cli/src/postinstall.js +42 -0
  43. package/dist/apps/cli/src/release-installer.d.ts +57 -0
  44. package/dist/apps/cli/src/release-installer.js +432 -0
  45. package/dist/apps/cli/src/runtime-adapter.d.ts +24 -0
  46. package/dist/apps/cli/src/runtime-adapter.js +185 -0
  47. package/dist/apps/cli/src/slash.d.ts +15 -0
  48. package/dist/apps/cli/src/slash.js +229 -0
  49. package/dist/apps/cli/src/summary-model.d.ts +51 -0
  50. package/dist/apps/cli/src/summary-model.js +136 -0
  51. package/dist/apps/cli/src/theme.d.ts +18 -0
  52. package/dist/apps/cli/src/theme.js +118 -0
  53. package/dist/apps/cli/src/version.d.ts +2 -0
  54. package/dist/apps/cli/src/version.js +2 -0
  55. package/dist/packages/config/src/index.d.ts +3 -0
  56. package/dist/packages/config/src/index.js +3 -0
  57. package/dist/packages/config/src/load.d.ts +3 -0
  58. package/dist/packages/config/src/load.js +80 -0
  59. package/dist/packages/config/src/schema.d.ts +49 -0
  60. package/dist/packages/config/src/schema.js +28 -0
  61. package/dist/packages/connectors/aws/src/cost-explorer.d.ts +34 -0
  62. package/dist/packages/connectors/aws/src/cost-explorer.js +43 -0
  63. package/dist/packages/connectors/aws/src/index.d.ts +35 -0
  64. package/dist/packages/connectors/aws/src/index.js +67 -0
  65. package/dist/packages/connectors/aws/src/normalize.d.ts +69 -0
  66. package/dist/packages/connectors/aws/src/normalize.js +141 -0
  67. package/dist/packages/connectors/aws/src/sdk-client.d.ts +6 -0
  68. package/dist/packages/connectors/aws/src/sdk-client.js +21 -0
  69. package/dist/packages/connectors/cloudflare/src/client.d.ts +23 -0
  70. package/dist/packages/connectors/cloudflare/src/client.js +107 -0
  71. package/dist/packages/connectors/cloudflare/src/index.d.ts +33 -0
  72. package/dist/packages/connectors/cloudflare/src/index.js +81 -0
  73. package/dist/packages/connectors/cloudflare/src/normalize.d.ts +113 -0
  74. package/dist/packages/connectors/cloudflare/src/normalize.js +288 -0
  75. package/dist/packages/connectors/mock/src/index.d.ts +58 -0
  76. package/dist/packages/connectors/mock/src/index.js +66 -0
  77. package/dist/packages/connectors/openai/src/index.d.ts +55 -0
  78. package/dist/packages/connectors/openai/src/index.js +169 -0
  79. package/dist/packages/connectors/openai/src/normalize.d.ts +91 -0
  80. package/dist/packages/connectors/openai/src/normalize.js +180 -0
  81. package/dist/packages/connectors/supabase/src/client.d.ts +22 -0
  82. package/dist/packages/connectors/supabase/src/client.js +132 -0
  83. package/dist/packages/connectors/supabase/src/index.d.ts +33 -0
  84. package/dist/packages/connectors/supabase/src/index.js +87 -0
  85. package/dist/packages/connectors/supabase/src/normalize.d.ts +106 -0
  86. package/dist/packages/connectors/supabase/src/normalize.js +266 -0
  87. package/dist/packages/core/src/collector.d.ts +12 -0
  88. package/dist/packages/core/src/collector.js +68 -0
  89. package/dist/packages/core/src/index.d.ts +5 -0
  90. package/dist/packages/core/src/index.js +4 -0
  91. package/dist/packages/core/src/provider.d.ts +18 -0
  92. package/dist/packages/core/src/provider.js +2 -0
  93. package/dist/packages/core/src/risk-engine.d.ts +9 -0
  94. package/dist/packages/core/src/risk-engine.js +4 -0
  95. package/dist/packages/core/src/snapshots.d.ts +49 -0
  96. package/dist/packages/core/src/snapshots.js +9 -0
  97. package/dist/packages/db/src/client.d.ts +11 -0
  98. package/dist/packages/db/src/client.js +14 -0
  99. package/dist/packages/db/src/index.d.ts +6 -0
  100. package/dist/packages/db/src/index.js +6 -0
  101. package/dist/packages/db/src/local-store.d.ts +161 -0
  102. package/dist/packages/db/src/local-store.js +623 -0
  103. package/dist/packages/db/src/migrate.d.ts +17 -0
  104. package/dist/packages/db/src/migrate.js +35 -0
  105. package/dist/packages/db/src/schema.d.ts +5 -0
  106. package/dist/packages/db/src/schema.js +120 -0
  107. package/dist/packages/db/src/sqlite-bin.d.ts +3 -0
  108. package/dist/packages/db/src/sqlite-bin.js +16 -0
  109. package/dist/packages/local-api/src/index.d.ts +2 -0
  110. package/dist/packages/local-api/src/index.js +2 -0
  111. package/dist/packages/local-api/src/server.d.ts +36 -0
  112. package/dist/packages/local-api/src/server.js +310 -0
  113. package/dist/packages/report/src/daily.d.ts +24 -0
  114. package/dist/packages/report/src/daily.js +9 -0
  115. package/dist/packages/report/src/index.d.ts +4 -0
  116. package/dist/packages/report/src/index.js +4 -0
  117. package/dist/packages/report/src/korean.d.ts +3 -0
  118. package/dist/packages/report/src/korean.js +62 -0
  119. package/dist/packages/report/src/slack.d.ts +34 -0
  120. package/dist/packages/report/src/slack.js +134 -0
  121. package/dist/packages/runtime/src/index.d.ts +2 -0
  122. package/dist/packages/runtime/src/index.js +2 -0
  123. package/dist/packages/runtime/src/runtime.d.ts +26 -0
  124. package/dist/packages/runtime/src/runtime.js +182 -0
  125. package/dist/packages/view-model/src/hud-model.d.ts +74 -0
  126. package/dist/packages/view-model/src/hud-model.js +295 -0
  127. package/dist/packages/view-model/src/index.d.ts +6 -0
  128. package/dist/packages/view-model/src/index.js +6 -0
  129. package/dist/packages/view-model/src/notification-preferences-model.d.ts +75 -0
  130. package/dist/packages/view-model/src/notification-preferences-model.js +400 -0
  131. package/dist/packages/view-model/src/notification-preferences.d.ts +6 -0
  132. package/dist/packages/view-model/src/notification-preferences.js +36 -0
  133. package/dist/packages/view-model/src/sync-state.d.ts +47 -0
  134. package/dist/packages/view-model/src/sync-state.js +140 -0
  135. package/dist/packages/view-model/src/usage-progress.d.ts +22 -0
  136. package/dist/packages/view-model/src/usage-progress.js +57 -0
  137. package/dist/packages/view-model/src/view-model.d.ts +215 -0
  138. package/dist/packages/view-model/src/view-model.js +826 -0
  139. package/package.json +40 -0
  140. package/scripts/postinstall.mjs +69 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MoneySiren contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # MoneySiren App
2
+
3
+ One-command alpha installer for MoneySiren.
4
+
5
+ This package bundles the MoneySiren CLI entrypoints and, on global npm installs, runs `msiren install --all` to download the local web dashboard runtime and HUD desktop artifacts from the matching GitHub Release.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install -g @moneysiren/app@alpha
11
+ msiren start
12
+ msiren hud
13
+ ```
14
+
15
+ The package installs both commands:
16
+
17
+ - `moneysiren`
18
+ - `msiren`
19
+
20
+ ## What It Installs
21
+
22
+ - CLI command surface.
23
+ - Local web dashboard runtime.
24
+ - HUD desktop artifact.
25
+
26
+ The Web/HUD artifacts are verified against published SHA256 checksums. Alpha Windows HUD artifacts may be unsigned until release signing is fully configured.
27
+
28
+ ## Opt Out
29
+
30
+ To install only the package command and skip Web/HUD asset download:
31
+
32
+ ```bash
33
+ MONEYSIREN_SKIP_APP_POSTINSTALL=1 npm install -g @moneysiren/app@alpha
34
+ msiren install --all
35
+ ```
36
+
37
+ For local non-global package review, postinstall does not download release assets automatically. Run `msiren install --all` explicitly when needed.
38
+
39
+ ## Stable Channel
40
+
41
+ During alpha, install with `@alpha`. Stable releases will use:
42
+
43
+ ```bash
44
+ npm install -g @moneysiren/app
45
+ ```
@@ -0,0 +1,59 @@
1
+ import { type CliLocalRuntimeAdapter } from "./runtime-adapter.js";
2
+ import type { CliDesktopRuntimeAdapter } from "./desktop-runtime.js";
3
+ import { type Theme } from "./theme.js";
4
+ import type { SlackReportTransport } from "../../../packages/report/src/index.js";
5
+ import type { AwsCostExplorerClientAdapter } from "../../../packages/connectors/aws/src/index.js";
6
+ import type { CloudflareBillingUsageClient } from "../../../packages/connectors/cloudflare/src/index.js";
7
+ import type { OpenAiUsageCostsClient } from "../../../packages/connectors/openai/src/index.js";
8
+ import type { SupabaseManagementClient } from "../../../packages/connectors/supabase/src/index.js";
9
+ export { CLI_VERSION } from "./version.js";
10
+ export interface CliRuntime {
11
+ cwd?: string;
12
+ env?: Record<string, string | undefined>;
13
+ now?: () => Date;
14
+ stdin?: NodeJS.ReadableStream;
15
+ output?: NodeJS.WritableStream;
16
+ stdinIsTTY?: boolean;
17
+ stdoutIsTTY?: boolean;
18
+ interactive?: boolean;
19
+ stdout?: (line: string) => void;
20
+ stderr?: (line: string) => void;
21
+ stdoutBuffer?: string[];
22
+ stderrBuffer?: string[];
23
+ slackTransport?: SlackReportTransport;
24
+ liveClients?: CliLiveClients;
25
+ fetch?: typeof fetch;
26
+ localRuntime?: CliLocalRuntimeAdapter;
27
+ desktopRuntime?: CliDesktopRuntimeAdapter;
28
+ openUrl?: (url: string) => Promise<void> | void;
29
+ }
30
+ export interface CliLiveClients {
31
+ awsCostExplorer?: AwsCostExplorerClientAdapter;
32
+ cloudflareBillingUsage?: CloudflareBillingUsageClient;
33
+ openaiUsageCosts?: OpenAiUsageCostsClient;
34
+ supabaseUsageHealth?: SupabaseManagementClient;
35
+ }
36
+ export interface CliExecutionContext {
37
+ cwd: string;
38
+ env: Record<string, string | undefined>;
39
+ now: () => Date;
40
+ stdout(line: string): void;
41
+ stderr(line: string): void;
42
+ slackTransport?: SlackReportTransport;
43
+ liveClients?: CliLiveClients;
44
+ fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
45
+ openUrl(url: string): Promise<void> | void;
46
+ stdin?: NodeJS.ReadableStream;
47
+ output?: NodeJS.WritableStream;
48
+ interactive: boolean;
49
+ theme: Theme;
50
+ localRuntime?: CliLocalRuntimeAdapter;
51
+ desktopRuntime?: CliDesktopRuntimeAdapter;
52
+ }
53
+ export interface CliResult {
54
+ exitCode: number;
55
+ stdout: readonly string[];
56
+ stderr: readonly string[];
57
+ }
58
+ export declare function runCli(args: readonly string[], runtime?: CliRuntime): Promise<CliResult>;
59
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1,199 @@
1
+ import { runDashboardCommand } from "./commands/dashboard.js";
2
+ import { runDoctorCommand } from "./commands/doctor.js";
3
+ import { runInitCommand } from "./commands/init.js";
4
+ import { runInstallCommand } from "./commands/install.js";
5
+ import { runModesCommand } from "./commands/modes.js";
6
+ import { runNotifyCommand } from "./commands/notify.js";
7
+ import { runReportCommand } from "./commands/report.js";
8
+ import { runDesktopCommand, runHudCommand, runOpenCommand, runRestartCommand, runServeCommand, runStartCommand, runStatusCommand, runStopCommand, } from "./commands/runtime.js";
9
+ import { runSummaryCommand } from "./commands/summary.js";
10
+ import { runSyncCommand } from "./commands/sync.js";
11
+ import { runThemeCommand } from "./commands/theme.js";
12
+ import { renderHelpScreen, renderHomeScreen } from "./home.js";
13
+ import { runSlashPrompt } from "./interactive.js";
14
+ import { openUrlInBrowser } from "./runtime-adapter.js";
15
+ import { resolveSlashCommand } from "./slash.js";
16
+ import { createTheme } from "./theme.js";
17
+ import { CLI_VERSION } from "./version.js";
18
+ export { CLI_VERSION } from "./version.js";
19
+ const HELP = renderHelpScreen(CLI_VERSION);
20
+ export async function runCli(args, runtime = {}) {
21
+ const stdout = runtime.stdoutBuffer ?? [];
22
+ const stderr = runtime.stderrBuffer ?? [];
23
+ const env = runtime.env ?? process.env;
24
+ const stdinIsTTY = runtime.stdinIsTTY ?? Boolean(process.stdin.isTTY);
25
+ const stdoutIsTTY = runtime.stdoutIsTTY ?? Boolean(process.stdout.isTTY);
26
+ const theme = createTheme({
27
+ cwd: runtime.cwd ?? process.cwd(),
28
+ env,
29
+ stdoutIsTTY,
30
+ });
31
+ const context = {
32
+ cwd: runtime.cwd ?? process.cwd(),
33
+ env,
34
+ now: runtime.now ?? (() => new Date()),
35
+ stdout(line) {
36
+ if (runtime.stdoutBuffer === undefined && runtime.stdout !== undefined) {
37
+ stdout.push(line);
38
+ }
39
+ if (runtime.stdout === undefined) {
40
+ stdout.push(line);
41
+ return;
42
+ }
43
+ runtime.stdout(line);
44
+ },
45
+ stderr(line) {
46
+ if (runtime.stderrBuffer === undefined && runtime.stderr !== undefined) {
47
+ stderr.push(line);
48
+ }
49
+ if (runtime.stderr === undefined) {
50
+ stderr.push(line);
51
+ return;
52
+ }
53
+ runtime.stderr(line);
54
+ },
55
+ fetch: runtime.fetch ?? globalThis.fetch,
56
+ openUrl: runtime.openUrl ?? openUrlInBrowser,
57
+ interactive: runtime.interactive ?? shouldEnterInteractivePrompt({
58
+ env,
59
+ stdinIsTTY,
60
+ stdoutIsTTY,
61
+ }),
62
+ theme,
63
+ ...(runtime.localRuntime === undefined ? {} : { localRuntime: runtime.localRuntime }),
64
+ ...(runtime.desktopRuntime === undefined ? {} : { desktopRuntime: runtime.desktopRuntime }),
65
+ };
66
+ context.stdin = runtime.stdin ?? process.stdin;
67
+ context.output = runtime.output ?? process.stdout;
68
+ if (runtime.slackTransport !== undefined) {
69
+ context.slackTransport = runtime.slackTransport;
70
+ }
71
+ if (runtime.liveClients !== undefined) {
72
+ context.liveClients = runtime.liveClients;
73
+ }
74
+ try {
75
+ return {
76
+ exitCode: await dispatchCommand(stripLeadingArgumentSeparator(args), context),
77
+ stdout,
78
+ stderr,
79
+ };
80
+ }
81
+ catch (error) {
82
+ context.stderr(error instanceof Error ? error.message : String(error));
83
+ return {
84
+ exitCode: 1,
85
+ stdout,
86
+ stderr,
87
+ };
88
+ }
89
+ }
90
+ function stripLeadingArgumentSeparator(args) {
91
+ return args[0] === "--" ? args.slice(1) : args;
92
+ }
93
+ async function dispatchCommand(args, context) {
94
+ const [command, ...rest] = args;
95
+ if (command === undefined) {
96
+ context.stdout(renderHomeScreen({
97
+ version: CLI_VERSION,
98
+ theme: context.theme,
99
+ }));
100
+ if (context.interactive) {
101
+ return runSlashPrompt(context, (promptArgs) => dispatchCommand(promptArgs, context));
102
+ }
103
+ return 0;
104
+ }
105
+ if (command.startsWith("/")) {
106
+ return dispatchSlashCommand(args, context);
107
+ }
108
+ if (command === "--help" || command === "-h" || command === "help") {
109
+ context.stdout(HELP);
110
+ return 0;
111
+ }
112
+ if (command === "--version" || command === "-v" || command === "version") {
113
+ context.stdout(CLI_VERSION);
114
+ return 0;
115
+ }
116
+ if (command === "init") {
117
+ return runInitCommand(rest, context);
118
+ }
119
+ if (command === "install") {
120
+ return runInstallCommand(rest, context);
121
+ }
122
+ if (command === "doctor") {
123
+ return runDoctorCommand(rest, context);
124
+ }
125
+ if (command === "modes") {
126
+ return runModesCommand(rest, context);
127
+ }
128
+ if (command === "dashboard") {
129
+ return runDashboardCommand(rest, context);
130
+ }
131
+ if (command === "serve") {
132
+ return runServeCommand(rest, context);
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
+ }
149
+ if (command === "open") {
150
+ return runOpenCommand(rest, context);
151
+ }
152
+ if (command === "sync") {
153
+ return runSyncCommand(rest, context);
154
+ }
155
+ if (command === "summary") {
156
+ return runSummaryCommand(rest, context);
157
+ }
158
+ if (command === "notify") {
159
+ return runNotifyCommand(rest, context);
160
+ }
161
+ if (command === "desktop") {
162
+ return runDesktopCommand(rest, context);
163
+ }
164
+ if (command === "report") {
165
+ return runReportCommand(rest, context);
166
+ }
167
+ if (command === "theme") {
168
+ return runThemeCommand(rest, context);
169
+ }
170
+ context.stderr(`Unknown command: ${command}`);
171
+ context.stderr("Run `msiren --help` or `moneysiren --help` for usage.");
172
+ return 1;
173
+ }
174
+ async function dispatchSlashCommand(args, context) {
175
+ const resolved = resolveSlashCommand(args);
176
+ if (resolved.kind === "dispatch") {
177
+ return dispatchCommand(resolved.args, context);
178
+ }
179
+ if (resolved.kind === "quit") {
180
+ context.stdout("Bye.");
181
+ return 0;
182
+ }
183
+ context.stderr(resolved.message);
184
+ if (resolved.usage !== undefined) {
185
+ context.stderr(resolved.usage);
186
+ }
187
+ return 1;
188
+ }
189
+ function shouldEnterInteractivePrompt(input) {
190
+ return input.stdinIsTTY && input.stdoutIsTTY && !isTruthyEnvValue(input.env.CI);
191
+ }
192
+ function isTruthyEnvValue(value) {
193
+ if (value === undefined) {
194
+ return false;
195
+ }
196
+ const normalized = value.trim().toLowerCase();
197
+ return normalized.length > 0 && normalized !== "0" && normalized !== "false" && normalized !== "no";
198
+ }
199
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1,3 @@
1
+ import type { CliExecutionContext } from "../cli.js";
2
+ export declare function runDashboardCommand(args: readonly string[], context: CliExecutionContext): Promise<number>;
3
+ //# sourceMappingURL=dashboard.d.ts.map
@@ -0,0 +1,239 @@
1
+ import { stat } from "node:fs/promises";
2
+ import { loadCliConfig, resolveDbPath } from "./shared.js";
3
+ const DEFAULT_DASHBOARD_URL = "http://localhost:3000";
4
+ const DASHBOARD_CHECK_TIMEOUT_MS = 2_000;
5
+ const DASHBOARD_CHECK_USAGE = "Usage: moneysiren dashboard check [--url <local-dashboard-url>]";
6
+ class DashboardCheckTimeoutError extends Error {
7
+ constructor(timeoutMs) {
8
+ super(`Dashboard API request timed out after ${timeoutMs}ms.`);
9
+ }
10
+ }
11
+ export async function runDashboardCommand(args, context) {
12
+ const [subcommand, ...rest] = args;
13
+ if (subcommand === "--help" || subcommand === "-h" || subcommand === "help") {
14
+ context.stdout(DASHBOARD_CHECK_USAGE);
15
+ return 0;
16
+ }
17
+ if (subcommand !== "check") {
18
+ context.stderr(DASHBOARD_CHECK_USAGE);
19
+ return 1;
20
+ }
21
+ if (rest.includes("--help") || rest.includes("-h")) {
22
+ context.stdout(DASHBOARD_CHECK_USAGE);
23
+ return 0;
24
+ }
25
+ const parsedArgs = parseDashboardCheckArgs(rest);
26
+ if (parsedArgs === undefined) {
27
+ context.stderr(DASHBOARD_CHECK_USAGE);
28
+ return 1;
29
+ }
30
+ const sanitizedUrl = sanitizeDashboardUrl(parsedArgs.dashboardUrl);
31
+ if (sanitizedUrl instanceof Error) {
32
+ context.stderr(sanitizedUrl.message);
33
+ context.stderr(DASHBOARD_CHECK_USAGE);
34
+ return 1;
35
+ }
36
+ const config = loadCliConfig(context.env);
37
+ const dbPath = resolveDbPath(context.cwd, config.dbPath);
38
+ const dbExists = await pathExists(dbPath);
39
+ context.stdout("MoneySiren dashboard check");
40
+ context.stdout(`Dashboard URL: ${sanitizedUrl.dashboardUrl}`);
41
+ context.stdout(`API endpoint: ${sanitizedUrl.apiUrl}`);
42
+ if (sanitizedUrl.ignoredUnsafeParts) {
43
+ context.stdout("URL note: path, query, and hash were ignored for safety.");
44
+ }
45
+ context.stdout(`DB path: ${config.dbPath}`);
46
+ context.stdout(`DB exists locally: ${dbExists}`);
47
+ try {
48
+ const response = await fetchDashboardApi(context, sanitizedUrl.apiUrl, DASHBOARD_CHECK_TIMEOUT_MS);
49
+ context.stdout(`API status: ${response.status}${response.ok ? " OK" : ""}`);
50
+ if (!response.ok) {
51
+ writeDashboardDevGuidance(context);
52
+ return 1;
53
+ }
54
+ const payload = parseDashboardPayload(await response.json());
55
+ if (payload === undefined) {
56
+ context.stderr("Dashboard API returned an unexpected payload shape.");
57
+ writeDashboardDevGuidance(context);
58
+ return 1;
59
+ }
60
+ const dashboardState = isSafeEmptyDashboardState(payload) ? "safe empty state" : "data available";
61
+ context.stdout(`Payload source: ${payload.source}`);
62
+ context.stdout(`Provider count: ${payload.providerCount}`);
63
+ context.stdout(`Generated at: ${payload.generatedAt}`);
64
+ context.stdout(`Payload database: ${payload.databaseAvailable ? "available" : "missing"} (${payload.databaseReason})`);
65
+ context.stdout(`Dashboard state: ${dashboardState}`);
66
+ writeDashboardSuccessGuidance(context, payload);
67
+ return 0;
68
+ }
69
+ catch (error) {
70
+ context.stdout(error instanceof DashboardCheckTimeoutError
71
+ ? `API status: timeout after ${DASHBOARD_CHECK_TIMEOUT_MS}ms`
72
+ : "API status: unreachable");
73
+ writeDashboardDevGuidance(context);
74
+ return 1;
75
+ }
76
+ }
77
+ function parseDashboardCheckArgs(args) {
78
+ let dashboardUrl = DEFAULT_DASHBOARD_URL;
79
+ for (let index = 0; index < args.length; index += 1) {
80
+ const arg = args[index];
81
+ if (arg === "--url") {
82
+ const value = args[index + 1];
83
+ if (value === undefined || value.startsWith("--")) {
84
+ return undefined;
85
+ }
86
+ dashboardUrl = value;
87
+ index += 1;
88
+ continue;
89
+ }
90
+ if (arg?.startsWith("--url=")) {
91
+ const value = arg.slice("--url=".length);
92
+ if (value.trim().length === 0) {
93
+ return undefined;
94
+ }
95
+ dashboardUrl = value;
96
+ continue;
97
+ }
98
+ return undefined;
99
+ }
100
+ return {
101
+ dashboardUrl,
102
+ };
103
+ }
104
+ function sanitizeDashboardUrl(rawUrl) {
105
+ let parsedUrl;
106
+ try {
107
+ parsedUrl = new URL(rawUrl.trim());
108
+ }
109
+ catch {
110
+ return new Error("Dashboard URL must be a valid http:// or https:// URL.");
111
+ }
112
+ if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
113
+ return new Error("Dashboard URL must use http:// or https://.");
114
+ }
115
+ if (parsedUrl.username.length > 0 || parsedUrl.password.length > 0) {
116
+ return new Error("Dashboard URL must not include credentials.");
117
+ }
118
+ if (!isLocalDashboardHost(parsedUrl.hostname)) {
119
+ return new Error("Dashboard URL must point to localhost, 127.0.0.1, ::1, or 0.0.0.0.");
120
+ }
121
+ const dashboardUrl = parsedUrl.origin;
122
+ const apiUrl = new URL("/api/dashboard", dashboardUrl).toString();
123
+ const ignoredUnsafeParts = parsedUrl.pathname !== "/" || parsedUrl.search.length > 0 || parsedUrl.hash.length > 0;
124
+ return {
125
+ dashboardUrl,
126
+ apiUrl,
127
+ ignoredUnsafeParts,
128
+ };
129
+ }
130
+ function isLocalDashboardHost(hostname) {
131
+ return hostname === "localhost" ||
132
+ hostname === "127.0.0.1" ||
133
+ hostname === "0.0.0.0" ||
134
+ hostname === "::1" ||
135
+ hostname === "[::1]";
136
+ }
137
+ async function pathExists(path) {
138
+ try {
139
+ await stat(path);
140
+ return "yes";
141
+ }
142
+ catch (error) {
143
+ if (isNodeError(error) && error.code === "ENOENT") {
144
+ return "no";
145
+ }
146
+ return "unknown";
147
+ }
148
+ }
149
+ async function fetchDashboardApi(context, apiUrl, timeoutMs) {
150
+ const controller = new AbortController();
151
+ let timeout;
152
+ const fetchPromise = context.fetch(apiUrl, {
153
+ method: "GET",
154
+ headers: {
155
+ Accept: "application/json",
156
+ },
157
+ signal: controller.signal,
158
+ });
159
+ fetchPromise.catch(() => undefined);
160
+ const timeoutPromise = new Promise((_resolve, reject) => {
161
+ timeout = setTimeout(() => {
162
+ controller.abort();
163
+ reject(new DashboardCheckTimeoutError(timeoutMs));
164
+ }, timeoutMs);
165
+ });
166
+ try {
167
+ return await Promise.race([fetchPromise, timeoutPromise]);
168
+ }
169
+ finally {
170
+ if (timeout !== undefined) {
171
+ clearTimeout(timeout);
172
+ }
173
+ }
174
+ }
175
+ function parseDashboardPayload(payload) {
176
+ if (!isRecord(payload)) {
177
+ return undefined;
178
+ }
179
+ const source = payload.source;
180
+ const generatedAt = payload.generatedAt;
181
+ const database = payload.database;
182
+ const summary = payload.summary;
183
+ if ((source !== "sqlite" && source !== "empty") || !isSafeIsoTimestamp(generatedAt)) {
184
+ return undefined;
185
+ }
186
+ if (!isRecord(database) || !isRecord(summary)) {
187
+ return undefined;
188
+ }
189
+ const databaseAvailable = database.available;
190
+ const databaseReason = database.reason;
191
+ const providerCount = summary.providerCount;
192
+ if (typeof databaseAvailable !== "boolean" ||
193
+ (databaseReason !== "ok" && databaseReason !== "missing") ||
194
+ typeof providerCount !== "number" ||
195
+ !Number.isSafeInteger(providerCount) ||
196
+ providerCount < 0) {
197
+ return undefined;
198
+ }
199
+ return {
200
+ source,
201
+ generatedAt,
202
+ providerCount,
203
+ databaseAvailable,
204
+ databaseReason,
205
+ };
206
+ }
207
+ function isSafeEmptyDashboardState(payload) {
208
+ return payload.source === "empty" &&
209
+ payload.providerCount === 0 &&
210
+ !payload.databaseAvailable &&
211
+ payload.databaseReason === "missing";
212
+ }
213
+ function isSafeIsoTimestamp(value) {
214
+ return typeof value === "string" &&
215
+ value.length <= 40 &&
216
+ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/.test(value) &&
217
+ !Number.isNaN(Date.parse(value));
218
+ }
219
+ function isRecord(value) {
220
+ return typeof value === "object" && value !== null && !Array.isArray(value);
221
+ }
222
+ function isNodeError(error) {
223
+ return error instanceof Error && "code" in error;
224
+ }
225
+ function writeDashboardDevGuidance(context) {
226
+ context.stderr("Next step: start the local dashboard from the repo root:");
227
+ context.stderr(" pnpm --filter @moneysiren/web dev");
228
+ context.stderr("Then re-run:");
229
+ context.stderr(" pnpm --filter moneysiren dev -- dashboard check");
230
+ }
231
+ function writeDashboardSuccessGuidance(context, payload) {
232
+ if (isSafeEmptyDashboardState(payload)) {
233
+ context.stdout("Next step: sync mock data if you want a populated dashboard review:");
234
+ context.stdout(" pnpm --filter moneysiren dev -- sync --provider mock");
235
+ return;
236
+ }
237
+ context.stdout("Next step: open the dashboard URL above in your browser.");
238
+ }
239
+ //# sourceMappingURL=dashboard.js.map
@@ -0,0 +1,3 @@
1
+ import type { CliExecutionContext } from "../cli.js";
2
+ export declare function runDoctorCommand(args: readonly string[], context: CliExecutionContext): Promise<number>;
3
+ //# sourceMappingURL=doctor.d.ts.map
@@ -0,0 +1,25 @@
1
+ import { loadCliConfig } from "./shared.js";
2
+ const PROVIDER_ORDER = ["aws", "openai", "supabase", "cloudflare"];
3
+ export async function runDoctorCommand(args, context) {
4
+ if (args.length > 0) {
5
+ context.stderr("Usage: moneysiren doctor");
6
+ return 1;
7
+ }
8
+ const config = loadCliConfig(context.env);
9
+ context.stdout("MoneySiren doctor");
10
+ context.stdout(`DB path: ${config.dbPath}`);
11
+ context.stdout(`telemetry: ${config.telemetryEnabled ? "enabled" : "disabled"}`);
12
+ context.stdout("mock provider: available");
13
+ for (const provider of PROVIDER_ORDER) {
14
+ context.stdout(`${provider}: ${formatProviderReadiness(config.providers[provider])}`);
15
+ }
16
+ context.stdout(`slack: ${config.slack.webhookConfigured ? "configured" : "not configured"}`);
17
+ return 0;
18
+ }
19
+ function formatProviderReadiness(providerConfig) {
20
+ if (providerConfig.configured) {
21
+ return "configured";
22
+ }
23
+ return `missing ${providerConfig.missingEnvKeys.join(", ")}`;
24
+ }
25
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1,3 @@
1
+ import type { CliExecutionContext } from "../cli.js";
2
+ export declare function runInitCommand(args: readonly string[], context: CliExecutionContext): Promise<number>;
3
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1,18 @@
1
+ import { initializeLocalStore } from "../../../../packages/db/src/index.js";
2
+ import { loadCliConfig, resolveDbPath } from "./shared.js";
3
+ export async function runInitCommand(args, context) {
4
+ if (args.length > 0) {
5
+ context.stderr("Usage: moneysiren init");
6
+ return 1;
7
+ }
8
+ const config = loadCliConfig(context.env);
9
+ const dbPath = resolveDbPath(context.cwd, config.dbPath);
10
+ const result = await initializeLocalStore({ dbPath });
11
+ const migrationSummary = result.appliedMigrationIds.length > 0
12
+ ? `applied migrations: ${result.appliedMigrationIds.join(", ")}`
13
+ : `migrations already applied: ${result.skippedMigrationIds.join(", ")}`;
14
+ context.stdout(`Initialized MoneySiren local storage at ${config.dbPath}`);
15
+ context.stdout(migrationSummary);
16
+ return 0;
17
+ }
18
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1,3 @@
1
+ import type { CliExecutionContext } from "../cli.js";
2
+ export declare function runInstallCommand(args: readonly string[], context: CliExecutionContext): Promise<number>;
3
+ //# sourceMappingURL=install.d.ts.map