@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.
- package/LICENSE +21 -0
- package/README.md +155 -0
- package/dist/apps/cli/src/cli.d.ts +56 -0
- package/dist/apps/cli/src/cli.js +182 -0
- package/dist/apps/cli/src/commands/dashboard.d.ts +3 -0
- package/dist/apps/cli/src/commands/dashboard.js +239 -0
- package/dist/apps/cli/src/commands/doctor.d.ts +3 -0
- package/dist/apps/cli/src/commands/doctor.js +25 -0
- package/dist/apps/cli/src/commands/init.d.ts +3 -0
- package/dist/apps/cli/src/commands/init.js +18 -0
- package/dist/apps/cli/src/commands/install.d.ts +3 -0
- package/dist/apps/cli/src/commands/install.js +116 -0
- package/dist/apps/cli/src/commands/modes.d.ts +3 -0
- package/dist/apps/cli/src/commands/modes.js +65 -0
- package/dist/apps/cli/src/commands/notify.d.ts +3 -0
- package/dist/apps/cli/src/commands/notify.js +430 -0
- package/dist/apps/cli/src/commands/report.d.ts +3 -0
- package/dist/apps/cli/src/commands/report.js +206 -0
- package/dist/apps/cli/src/commands/runtime.d.ts +5 -0
- package/dist/apps/cli/src/commands/runtime.js +133 -0
- package/dist/apps/cli/src/commands/shared.d.ts +9 -0
- package/dist/apps/cli/src/commands/shared.js +29 -0
- package/dist/apps/cli/src/commands/summary.d.ts +3 -0
- package/dist/apps/cli/src/commands/summary.js +15 -0
- package/dist/apps/cli/src/commands/sync.d.ts +3 -0
- package/dist/apps/cli/src/commands/sync.js +393 -0
- package/dist/apps/cli/src/commands/theme.d.ts +3 -0
- package/dist/apps/cli/src/commands/theme.js +181 -0
- package/dist/apps/cli/src/home.d.ts +7 -0
- package/dist/apps/cli/src/home.js +97 -0
- package/dist/apps/cli/src/index.d.ts +3 -0
- package/dist/apps/cli/src/index.js +14 -0
- package/dist/apps/cli/src/install-profile.d.ts +35 -0
- package/dist/apps/cli/src/install-profile.js +124 -0
- package/dist/apps/cli/src/install-selector.d.ts +10 -0
- package/dist/apps/cli/src/install-selector.js +66 -0
- package/dist/apps/cli/src/interactive.d.ts +3 -0
- package/dist/apps/cli/src/interactive.js +32 -0
- package/dist/apps/cli/src/postinstall.d.ts +3 -0
- package/dist/apps/cli/src/postinstall.js +42 -0
- package/dist/apps/cli/src/runtime-adapter.d.ts +24 -0
- package/dist/apps/cli/src/runtime-adapter.js +185 -0
- package/dist/apps/cli/src/slash.d.ts +15 -0
- package/dist/apps/cli/src/slash.js +202 -0
- package/dist/apps/cli/src/summary-model.d.ts +51 -0
- package/dist/apps/cli/src/summary-model.js +136 -0
- package/dist/apps/cli/src/theme.d.ts +18 -0
- package/dist/apps/cli/src/theme.js +118 -0
- package/dist/packages/config/src/index.d.ts +3 -0
- package/dist/packages/config/src/index.js +3 -0
- package/dist/packages/config/src/load.d.ts +3 -0
- package/dist/packages/config/src/load.js +77 -0
- package/dist/packages/config/src/schema.d.ts +46 -0
- package/dist/packages/config/src/schema.js +25 -0
- package/dist/packages/connectors/aws/src/cost-explorer.d.ts +34 -0
- package/dist/packages/connectors/aws/src/cost-explorer.js +43 -0
- package/dist/packages/connectors/aws/src/index.d.ts +35 -0
- package/dist/packages/connectors/aws/src/index.js +67 -0
- package/dist/packages/connectors/aws/src/normalize.d.ts +69 -0
- package/dist/packages/connectors/aws/src/normalize.js +141 -0
- package/dist/packages/connectors/aws/src/sdk-client.d.ts +6 -0
- package/dist/packages/connectors/aws/src/sdk-client.js +21 -0
- package/dist/packages/connectors/cloudflare/src/client.d.ts +23 -0
- package/dist/packages/connectors/cloudflare/src/client.js +107 -0
- package/dist/packages/connectors/cloudflare/src/index.d.ts +33 -0
- package/dist/packages/connectors/cloudflare/src/index.js +81 -0
- package/dist/packages/connectors/cloudflare/src/normalize.d.ts +113 -0
- package/dist/packages/connectors/cloudflare/src/normalize.js +288 -0
- package/dist/packages/connectors/mock/src/index.d.ts +58 -0
- package/dist/packages/connectors/mock/src/index.js +66 -0
- package/dist/packages/connectors/openai/src/index.d.ts +55 -0
- package/dist/packages/connectors/openai/src/index.js +169 -0
- package/dist/packages/connectors/openai/src/normalize.d.ts +91 -0
- package/dist/packages/connectors/openai/src/normalize.js +180 -0
- package/dist/packages/connectors/supabase/src/client.d.ts +22 -0
- package/dist/packages/connectors/supabase/src/client.js +132 -0
- package/dist/packages/connectors/supabase/src/index.d.ts +33 -0
- package/dist/packages/connectors/supabase/src/index.js +87 -0
- package/dist/packages/connectors/supabase/src/normalize.d.ts +106 -0
- package/dist/packages/connectors/supabase/src/normalize.js +266 -0
- package/dist/packages/core/src/collector.d.ts +12 -0
- package/dist/packages/core/src/collector.js +68 -0
- package/dist/packages/core/src/index.d.ts +5 -0
- package/dist/packages/core/src/index.js +4 -0
- package/dist/packages/core/src/provider.d.ts +18 -0
- package/dist/packages/core/src/provider.js +2 -0
- package/dist/packages/core/src/risk-engine.d.ts +9 -0
- package/dist/packages/core/src/risk-engine.js +4 -0
- package/dist/packages/core/src/snapshots.d.ts +49 -0
- package/dist/packages/core/src/snapshots.js +9 -0
- package/dist/packages/db/src/client.d.ts +11 -0
- package/dist/packages/db/src/client.js +14 -0
- package/dist/packages/db/src/index.d.ts +6 -0
- package/dist/packages/db/src/index.js +6 -0
- package/dist/packages/db/src/local-store.d.ts +161 -0
- package/dist/packages/db/src/local-store.js +623 -0
- package/dist/packages/db/src/migrate.d.ts +17 -0
- package/dist/packages/db/src/migrate.js +35 -0
- package/dist/packages/db/src/schema.d.ts +5 -0
- package/dist/packages/db/src/schema.js +120 -0
- package/dist/packages/db/src/sqlite-bin.d.ts +3 -0
- package/dist/packages/db/src/sqlite-bin.js +16 -0
- package/dist/packages/local-api/src/index.d.ts +2 -0
- package/dist/packages/local-api/src/index.js +2 -0
- package/dist/packages/local-api/src/server.d.ts +36 -0
- package/dist/packages/local-api/src/server.js +310 -0
- package/dist/packages/report/src/daily.d.ts +24 -0
- package/dist/packages/report/src/daily.js +9 -0
- package/dist/packages/report/src/index.d.ts +4 -0
- package/dist/packages/report/src/index.js +4 -0
- package/dist/packages/report/src/korean.d.ts +3 -0
- package/dist/packages/report/src/korean.js +62 -0
- package/dist/packages/report/src/slack.d.ts +34 -0
- package/dist/packages/report/src/slack.js +134 -0
- package/dist/packages/runtime/src/index.d.ts +2 -0
- package/dist/packages/runtime/src/index.js +2 -0
- package/dist/packages/runtime/src/runtime.d.ts +26 -0
- package/dist/packages/runtime/src/runtime.js +182 -0
- package/dist/packages/view-model/src/index.d.ts +3 -0
- package/dist/packages/view-model/src/index.js +3 -0
- package/dist/packages/view-model/src/notification-preferences-model.d.ts +47 -0
- package/dist/packages/view-model/src/notification-preferences-model.js +218 -0
- package/dist/packages/view-model/src/notification-preferences.d.ts +6 -0
- package/dist/packages/view-model/src/notification-preferences.js +36 -0
- package/dist/packages/view-model/src/view-model.d.ts +193 -0
- package/dist/packages/view-model/src/view-model.js +684 -0
- package/package.json +49 -0
- 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,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,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
|