@moneysiren/cli 0.1.0-alpha.0

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 (128) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +155 -0
  3. package/dist/apps/cli/src/cli.d.ts +56 -0
  4. package/dist/apps/cli/src/cli.js +182 -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 +116 -0
  13. package/dist/apps/cli/src/commands/modes.d.ts +3 -0
  14. package/dist/apps/cli/src/commands/modes.js +65 -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 +5 -0
  20. package/dist/apps/cli/src/commands/runtime.js +133 -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/home.d.ts +7 -0
  30. package/dist/apps/cli/src/home.js +97 -0
  31. package/dist/apps/cli/src/index.d.ts +3 -0
  32. package/dist/apps/cli/src/index.js +14 -0
  33. package/dist/apps/cli/src/install-profile.d.ts +35 -0
  34. package/dist/apps/cli/src/install-profile.js +124 -0
  35. package/dist/apps/cli/src/install-selector.d.ts +10 -0
  36. package/dist/apps/cli/src/install-selector.js +66 -0
  37. package/dist/apps/cli/src/interactive.d.ts +3 -0
  38. package/dist/apps/cli/src/interactive.js +32 -0
  39. package/dist/apps/cli/src/postinstall.d.ts +3 -0
  40. package/dist/apps/cli/src/postinstall.js +42 -0
  41. package/dist/apps/cli/src/runtime-adapter.d.ts +24 -0
  42. package/dist/apps/cli/src/runtime-adapter.js +185 -0
  43. package/dist/apps/cli/src/slash.d.ts +15 -0
  44. package/dist/apps/cli/src/slash.js +202 -0
  45. package/dist/apps/cli/src/summary-model.d.ts +51 -0
  46. package/dist/apps/cli/src/summary-model.js +136 -0
  47. package/dist/apps/cli/src/theme.d.ts +18 -0
  48. package/dist/apps/cli/src/theme.js +118 -0
  49. package/dist/packages/config/src/index.d.ts +3 -0
  50. package/dist/packages/config/src/index.js +3 -0
  51. package/dist/packages/config/src/load.d.ts +3 -0
  52. package/dist/packages/config/src/load.js +77 -0
  53. package/dist/packages/config/src/schema.d.ts +46 -0
  54. package/dist/packages/config/src/schema.js +25 -0
  55. package/dist/packages/connectors/aws/src/cost-explorer.d.ts +34 -0
  56. package/dist/packages/connectors/aws/src/cost-explorer.js +43 -0
  57. package/dist/packages/connectors/aws/src/index.d.ts +35 -0
  58. package/dist/packages/connectors/aws/src/index.js +67 -0
  59. package/dist/packages/connectors/aws/src/normalize.d.ts +69 -0
  60. package/dist/packages/connectors/aws/src/normalize.js +141 -0
  61. package/dist/packages/connectors/aws/src/sdk-client.d.ts +6 -0
  62. package/dist/packages/connectors/aws/src/sdk-client.js +21 -0
  63. package/dist/packages/connectors/cloudflare/src/client.d.ts +23 -0
  64. package/dist/packages/connectors/cloudflare/src/client.js +107 -0
  65. package/dist/packages/connectors/cloudflare/src/index.d.ts +33 -0
  66. package/dist/packages/connectors/cloudflare/src/index.js +81 -0
  67. package/dist/packages/connectors/cloudflare/src/normalize.d.ts +113 -0
  68. package/dist/packages/connectors/cloudflare/src/normalize.js +288 -0
  69. package/dist/packages/connectors/mock/src/index.d.ts +58 -0
  70. package/dist/packages/connectors/mock/src/index.js +66 -0
  71. package/dist/packages/connectors/openai/src/index.d.ts +55 -0
  72. package/dist/packages/connectors/openai/src/index.js +169 -0
  73. package/dist/packages/connectors/openai/src/normalize.d.ts +91 -0
  74. package/dist/packages/connectors/openai/src/normalize.js +180 -0
  75. package/dist/packages/connectors/supabase/src/client.d.ts +22 -0
  76. package/dist/packages/connectors/supabase/src/client.js +132 -0
  77. package/dist/packages/connectors/supabase/src/index.d.ts +33 -0
  78. package/dist/packages/connectors/supabase/src/index.js +87 -0
  79. package/dist/packages/connectors/supabase/src/normalize.d.ts +106 -0
  80. package/dist/packages/connectors/supabase/src/normalize.js +266 -0
  81. package/dist/packages/core/src/collector.d.ts +12 -0
  82. package/dist/packages/core/src/collector.js +68 -0
  83. package/dist/packages/core/src/index.d.ts +5 -0
  84. package/dist/packages/core/src/index.js +4 -0
  85. package/dist/packages/core/src/provider.d.ts +18 -0
  86. package/dist/packages/core/src/provider.js +2 -0
  87. package/dist/packages/core/src/risk-engine.d.ts +9 -0
  88. package/dist/packages/core/src/risk-engine.js +4 -0
  89. package/dist/packages/core/src/snapshots.d.ts +49 -0
  90. package/dist/packages/core/src/snapshots.js +9 -0
  91. package/dist/packages/db/src/client.d.ts +11 -0
  92. package/dist/packages/db/src/client.js +14 -0
  93. package/dist/packages/db/src/index.d.ts +6 -0
  94. package/dist/packages/db/src/index.js +6 -0
  95. package/dist/packages/db/src/local-store.d.ts +161 -0
  96. package/dist/packages/db/src/local-store.js +623 -0
  97. package/dist/packages/db/src/migrate.d.ts +17 -0
  98. package/dist/packages/db/src/migrate.js +35 -0
  99. package/dist/packages/db/src/schema.d.ts +5 -0
  100. package/dist/packages/db/src/schema.js +120 -0
  101. package/dist/packages/db/src/sqlite-bin.d.ts +3 -0
  102. package/dist/packages/db/src/sqlite-bin.js +16 -0
  103. package/dist/packages/local-api/src/index.d.ts +2 -0
  104. package/dist/packages/local-api/src/index.js +2 -0
  105. package/dist/packages/local-api/src/server.d.ts +36 -0
  106. package/dist/packages/local-api/src/server.js +310 -0
  107. package/dist/packages/report/src/daily.d.ts +24 -0
  108. package/dist/packages/report/src/daily.js +9 -0
  109. package/dist/packages/report/src/index.d.ts +4 -0
  110. package/dist/packages/report/src/index.js +4 -0
  111. package/dist/packages/report/src/korean.d.ts +3 -0
  112. package/dist/packages/report/src/korean.js +62 -0
  113. package/dist/packages/report/src/slack.d.ts +34 -0
  114. package/dist/packages/report/src/slack.js +134 -0
  115. package/dist/packages/runtime/src/index.d.ts +2 -0
  116. package/dist/packages/runtime/src/index.js +2 -0
  117. package/dist/packages/runtime/src/runtime.d.ts +26 -0
  118. package/dist/packages/runtime/src/runtime.js +182 -0
  119. package/dist/packages/view-model/src/index.d.ts +3 -0
  120. package/dist/packages/view-model/src/index.js +3 -0
  121. package/dist/packages/view-model/src/notification-preferences-model.d.ts +47 -0
  122. package/dist/packages/view-model/src/notification-preferences-model.js +218 -0
  123. package/dist/packages/view-model/src/notification-preferences.d.ts +6 -0
  124. package/dist/packages/view-model/src/notification-preferences.js +36 -0
  125. package/dist/packages/view-model/src/view-model.d.ts +193 -0
  126. package/dist/packages/view-model/src/view-model.js +684 -0
  127. package/package.json +49 -0
  128. package/scripts/postinstall.mjs +11 -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,155 @@
1
+ # MoneySiren CLI
2
+
3
+ Public alpha packaging metadata for `moneysiren`.
4
+
5
+ MoneySiren is local-first. The CLI reads configuration and secrets from the process environment only, writes normalized data to local SQLite, and does not enable telemetry by default.
6
+
7
+ ## Requirements
8
+
9
+ - Node.js 20.11 or newer.
10
+ - MoneySiren uses the Node SQLite runtime when available. `sqlite3` on `PATH` or `MONEYSIREN_SQLITE_BIN` is an optional fallback.
11
+ - No live provider credentials are required for `moneysiren --version`, `moneysiren doctor`, or `moneysiren sync --provider mock`.
12
+
13
+ ## Published Alpha Usage
14
+
15
+ After an alpha is published:
16
+
17
+ ```bash
18
+ npm install -g @moneysiren/cli@alpha
19
+ moneysiren
20
+ moneysiren --version
21
+ moneysiren /version
22
+ moneysiren install --status
23
+ moneysiren modes
24
+ moneysiren /modes
25
+ moneysiren doctor
26
+ moneysiren /doctor
27
+ moneysiren dashboard check
28
+ ```
29
+
30
+ During a PowerShell, cmd, or shell install with an interactive TTY, `postinstall` prompts for the local surfaces to enable:
31
+
32
+ - CLI
33
+ - Web dashboard
34
+ - HUD
35
+
36
+ Press Enter to accept the recommended default, which selects all three. In CI or non-interactive npm installs, MoneySiren writes that all-selected profile automatically. Re-run `moneysiren install` later to change the profile, or use `moneysiren install --status` to inspect it.
37
+
38
+ One-off execution:
39
+
40
+ ```bash
41
+ npx --package @moneysiren/cli@alpha moneysiren
42
+ npx --package @moneysiren/cli@alpha moneysiren --version
43
+ npx --package @moneysiren/cli@alpha moneysiren /version
44
+ npx --package @moneysiren/cli@alpha moneysiren modes
45
+ npx --package @moneysiren/cli@alpha moneysiren doctor
46
+ npx --package @moneysiren/cli@alpha moneysiren /doctor
47
+ npx --package @moneysiren/cli@alpha moneysiren dashboard check
48
+ npx --package @moneysiren/cli@alpha moneysiren sync --provider aws --profile <profile>
49
+ ```
50
+
51
+ `aws sso login --profile <profile>` refreshes AWS SSO credentials, but it does not set `AWS_PROFILE` in the current shell. Pass `--profile <profile>` or export `AWS_PROFILE` before live AWS sync.
52
+
53
+ ## Local Tarball Review
54
+
55
+ From the repository root:
56
+
57
+ ```bash
58
+ pnpm --filter moneysiren build
59
+ cd apps/cli
60
+ npm pack
61
+ ```
62
+
63
+ Install the generated tarball into a temporary project:
64
+
65
+ ```bash
66
+ mkdir -p /tmp/moneysiren-alpha-review
67
+ cd /tmp/moneysiren-alpha-review
68
+ npm init -y
69
+ npm install /path/to/moneysiren-cli-0.1.0-alpha.0.tgz
70
+ npm exec moneysiren
71
+ npm exec moneysiren -- --version
72
+ npm exec moneysiren -- /version
73
+ npm exec moneysiren -- modes
74
+ npm exec moneysiren -- doctor
75
+ npm exec moneysiren -- /doctor
76
+ npm exec moneysiren -- dashboard check
77
+ ```
78
+
79
+ PowerShell equivalent for the temporary project:
80
+
81
+ ```powershell
82
+ New-Item -ItemType Directory -Force -Path $env:TEMP\moneysiren-alpha-review
83
+ Set-Location $env:TEMP\moneysiren-alpha-review
84
+ npm init -y
85
+ npm install C:\path\to\moneysiren-cli-0.1.0-alpha.0.tgz
86
+ npm exec moneysiren
87
+ npm exec moneysiren -- --version
88
+ npm exec moneysiren -- modes
89
+ npm exec moneysiren -- /doctor
90
+ ```
91
+
92
+ Do not create `.env`, paste real API keys, or write Slack webhook URLs into local project files. Fixture mode and `mock` sync are the intended no-credentials review paths.
93
+
94
+ Live provider sync is read-only and env-only. Use fixture mode for no-credentials review; export live credentials only in the shell for one run.
95
+
96
+ ## Publishing the Alpha
97
+
98
+ From the repository root:
99
+
100
+ ```bash
101
+ npm run publish:cli:dry-run
102
+ npm run publish:cli:alpha
103
+ ```
104
+
105
+ The dry run checks the full secret scan, package metadata, npm registry version availability, and tarball contents. The publish command requires `npm login` in the local terminal and publishes this package with the `alpha` tag and public access.
106
+
107
+ ## Slash Home
108
+
109
+ Running `moneysiren` without subcommands prints a readable slash-command home guide. In a TTY it may enter a minimal line-based slash prompt; in CI or non-TTY package review it prints the guide and exits `0`.
110
+
111
+ Supported slash aliases:
112
+
113
+ ```bash
114
+ moneysiren /help
115
+ moneysiren /version
116
+ moneysiren /doctor
117
+ moneysiren /install
118
+ moneysiren /modes
119
+ moneysiren /init
120
+ moneysiren /dashboard
121
+ moneysiren /dashboard check
122
+ moneysiren /sync mock
123
+ moneysiren /sync aws
124
+ moneysiren /sync openai
125
+ moneysiren /sync supabase
126
+ moneysiren /sync cloudflare
127
+ moneysiren /report ko
128
+ moneysiren /quit
129
+ ```
130
+
131
+ Slash aliases are thin wrappers around the existing CLI commands. Home/help does not call provider APIs, read secret values, create `.env`, or enable telemetry. ANSI color respects `NO_COLOR`, `FORCE_COLOR`, and `TERM=dumb`.
132
+
133
+ ## Runtime Modes
134
+
135
+ `moneysiren modes` prints the three supported surfaces after an npm install:
136
+
137
+ - CLI automation from the npm package.
138
+ - Local web dashboard/runtime, with `moneysiren serve` providing the sanitized local API runtime.
139
+ - Desktop tray/notifier status and notification preview commands, while the native Tauri tray binary remains a separate repo/native build artifact for this alpha.
140
+
141
+ The mode list includes the local install profile selected by npm `postinstall` or `moneysiren install`.
142
+
143
+ The shared runtime lock defaults to `%APPDATA%\MoneySiren\runtime.json` on Windows and `~/Library/Application Support/MoneySiren/runtime.json` on macOS so a globally installed CLI and the desktop tray can discover the same local runtime. Set `MONEYSIREN_RUNTIME_LOCK_PATH` only when you intentionally need an isolated runtime lock for testing.
144
+
145
+ ## Dashboard Check
146
+
147
+ `moneysiren dashboard check` probes `http://localhost:3000/api/dashboard` by default and reports the sanitized dashboard URL, API status, local DB path/existence, payload source, provider count, and generated time.
148
+
149
+ Use `--url` only for a local dashboard origin:
150
+
151
+ ```bash
152
+ moneysiren dashboard check --url http://localhost:3000
153
+ ```
154
+
155
+ Path, query, and hash values are ignored before printing or probing, and URLs with credentials are rejected. The command does not package, start, or serve the Next.js dashboard; from this repository, start it with `pnpm --filter @moneysiren/web dev`.
@@ -0,0 +1,56 @@
1
+ import { type CliLocalRuntimeAdapter } from "./runtime-adapter.js";
2
+ import { type Theme } from "./theme.js";
3
+ import type { SlackReportTransport } from "../../../packages/report/src/index.js";
4
+ import type { AwsCostExplorerClientAdapter } from "../../../packages/connectors/aws/src/index.js";
5
+ import type { CloudflareBillingUsageClient } from "../../../packages/connectors/cloudflare/src/index.js";
6
+ import type { OpenAiUsageCostsClient } from "../../../packages/connectors/openai/src/index.js";
7
+ import type { SupabaseManagementClient } from "../../../packages/connectors/supabase/src/index.js";
8
+ export declare const CLI_VERSION = "0.1.0-alpha.0";
9
+ export interface CliRuntime {
10
+ cwd?: string;
11
+ env?: Record<string, string | undefined>;
12
+ now?: () => Date;
13
+ stdin?: NodeJS.ReadableStream;
14
+ output?: NodeJS.WritableStream;
15
+ stdinIsTTY?: boolean;
16
+ stdoutIsTTY?: boolean;
17
+ interactive?: boolean;
18
+ stdout?: (line: string) => void;
19
+ stderr?: (line: string) => void;
20
+ stdoutBuffer?: string[];
21
+ stderrBuffer?: string[];
22
+ slackTransport?: SlackReportTransport;
23
+ liveClients?: CliLiveClients;
24
+ fetch?: typeof fetch;
25
+ localRuntime?: CliLocalRuntimeAdapter;
26
+ openUrl?: (url: string) => Promise<void> | void;
27
+ }
28
+ export interface CliLiveClients {
29
+ awsCostExplorer?: AwsCostExplorerClientAdapter;
30
+ cloudflareBillingUsage?: CloudflareBillingUsageClient;
31
+ openaiUsageCosts?: OpenAiUsageCostsClient;
32
+ supabaseUsageHealth?: SupabaseManagementClient;
33
+ }
34
+ export interface CliExecutionContext {
35
+ cwd: string;
36
+ env: Record<string, string | undefined>;
37
+ now: () => Date;
38
+ stdout(line: string): void;
39
+ stderr(line: string): void;
40
+ slackTransport?: SlackReportTransport;
41
+ liveClients?: CliLiveClients;
42
+ fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
43
+ openUrl(url: string): Promise<void> | void;
44
+ stdin?: NodeJS.ReadableStream;
45
+ output?: NodeJS.WritableStream;
46
+ interactive: boolean;
47
+ theme: Theme;
48
+ localRuntime?: CliLocalRuntimeAdapter;
49
+ }
50
+ export interface CliResult {
51
+ exitCode: number;
52
+ stdout: readonly string[];
53
+ stderr: readonly string[];
54
+ }
55
+ export declare function runCli(args: readonly string[], runtime?: CliRuntime): Promise<CliResult>;
56
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1,182 @@
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, runOpenCommand, runServeCommand } 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
+ export const CLI_VERSION = "0.1.0-alpha.0";
18
+ const HELP = renderHelpScreen(CLI_VERSION);
19
+ export async function runCli(args, runtime = {}) {
20
+ const stdout = runtime.stdoutBuffer ?? [];
21
+ const stderr = runtime.stderrBuffer ?? [];
22
+ const env = runtime.env ?? process.env;
23
+ const stdinIsTTY = runtime.stdinIsTTY ?? Boolean(process.stdin.isTTY);
24
+ const stdoutIsTTY = runtime.stdoutIsTTY ?? Boolean(process.stdout.isTTY);
25
+ const theme = createTheme({
26
+ cwd: runtime.cwd ?? process.cwd(),
27
+ env,
28
+ stdoutIsTTY,
29
+ });
30
+ const context = {
31
+ cwd: runtime.cwd ?? process.cwd(),
32
+ env,
33
+ now: runtime.now ?? (() => new Date()),
34
+ stdout(line) {
35
+ if (runtime.stdoutBuffer === undefined && runtime.stdout !== undefined) {
36
+ stdout.push(line);
37
+ }
38
+ if (runtime.stdout === undefined) {
39
+ stdout.push(line);
40
+ return;
41
+ }
42
+ runtime.stdout(line);
43
+ },
44
+ stderr(line) {
45
+ if (runtime.stderrBuffer === undefined && runtime.stderr !== undefined) {
46
+ stderr.push(line);
47
+ }
48
+ if (runtime.stderr === undefined) {
49
+ stderr.push(line);
50
+ return;
51
+ }
52
+ runtime.stderr(line);
53
+ },
54
+ fetch: runtime.fetch ?? globalThis.fetch,
55
+ openUrl: runtime.openUrl ?? openUrlInBrowser,
56
+ interactive: runtime.interactive ?? shouldEnterInteractivePrompt({
57
+ env,
58
+ stdinIsTTY,
59
+ stdoutIsTTY,
60
+ }),
61
+ theme,
62
+ ...(runtime.localRuntime === undefined ? {} : { localRuntime: runtime.localRuntime }),
63
+ };
64
+ context.stdin = runtime.stdin ?? process.stdin;
65
+ context.output = runtime.output ?? process.stdout;
66
+ if (runtime.slackTransport !== undefined) {
67
+ context.slackTransport = runtime.slackTransport;
68
+ }
69
+ if (runtime.liveClients !== undefined) {
70
+ context.liveClients = runtime.liveClients;
71
+ }
72
+ try {
73
+ return {
74
+ exitCode: await dispatchCommand(stripLeadingArgumentSeparator(args), context),
75
+ stdout,
76
+ stderr,
77
+ };
78
+ }
79
+ catch (error) {
80
+ context.stderr(error instanceof Error ? error.message : String(error));
81
+ return {
82
+ exitCode: 1,
83
+ stdout,
84
+ stderr,
85
+ };
86
+ }
87
+ }
88
+ function stripLeadingArgumentSeparator(args) {
89
+ return args[0] === "--" ? args.slice(1) : args;
90
+ }
91
+ async function dispatchCommand(args, context) {
92
+ const [command, ...rest] = args;
93
+ if (command === undefined) {
94
+ context.stdout(renderHomeScreen({
95
+ version: CLI_VERSION,
96
+ theme: context.theme,
97
+ }));
98
+ if (context.interactive) {
99
+ return runSlashPrompt(context, (promptArgs) => dispatchCommand(promptArgs, context));
100
+ }
101
+ return 0;
102
+ }
103
+ if (command.startsWith("/")) {
104
+ return dispatchSlashCommand(args, context);
105
+ }
106
+ if (command === "--help" || command === "-h" || command === "help") {
107
+ context.stdout(HELP);
108
+ return 0;
109
+ }
110
+ if (command === "--version" || command === "-v" || command === "version") {
111
+ context.stdout(CLI_VERSION);
112
+ return 0;
113
+ }
114
+ if (command === "init") {
115
+ return runInitCommand(rest, context);
116
+ }
117
+ if (command === "install") {
118
+ return runInstallCommand(rest, context);
119
+ }
120
+ if (command === "doctor") {
121
+ return runDoctorCommand(rest, context);
122
+ }
123
+ if (command === "modes") {
124
+ return runModesCommand(rest, context);
125
+ }
126
+ if (command === "dashboard") {
127
+ return runDashboardCommand(rest, context);
128
+ }
129
+ if (command === "serve") {
130
+ return runServeCommand(rest, context);
131
+ }
132
+ if (command === "open") {
133
+ return runOpenCommand(rest, context);
134
+ }
135
+ if (command === "sync") {
136
+ return runSyncCommand(rest, context);
137
+ }
138
+ if (command === "summary") {
139
+ return runSummaryCommand(rest, context);
140
+ }
141
+ if (command === "notify") {
142
+ return runNotifyCommand(rest, context);
143
+ }
144
+ if (command === "desktop") {
145
+ return runDesktopCommand(rest, context);
146
+ }
147
+ if (command === "report") {
148
+ return runReportCommand(rest, context);
149
+ }
150
+ if (command === "theme") {
151
+ return runThemeCommand(rest, context);
152
+ }
153
+ context.stderr(`Unknown command: ${command}`);
154
+ context.stderr("Run `moneysiren --help` for usage.");
155
+ return 1;
156
+ }
157
+ async function dispatchSlashCommand(args, context) {
158
+ const resolved = resolveSlashCommand(args);
159
+ if (resolved.kind === "dispatch") {
160
+ return dispatchCommand(resolved.args, context);
161
+ }
162
+ if (resolved.kind === "quit") {
163
+ context.stdout("Bye.");
164
+ return 0;
165
+ }
166
+ context.stderr(resolved.message);
167
+ if (resolved.usage !== undefined) {
168
+ context.stderr(resolved.usage);
169
+ }
170
+ return 1;
171
+ }
172
+ function shouldEnterInteractivePrompt(input) {
173
+ return input.stdinIsTTY && input.stdoutIsTTY && !isTruthyEnvValue(input.env.CI);
174
+ }
175
+ function isTruthyEnvValue(value) {
176
+ if (value === undefined) {
177
+ return false;
178
+ }
179
+ const normalized = value.trim().toLowerCase();
180
+ return normalized.length > 0 && normalized !== "0" && normalized !== "false" && normalized !== "no";
181
+ }
182
+ //# 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