@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.
- package/LICENSE +21 -0
- package/README.md +45 -0
- package/dist/apps/cli/src/cli.d.ts +59 -0
- package/dist/apps/cli/src/cli.js +199 -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 +244 -0
- package/dist/apps/cli/src/commands/modes.d.ts +3 -0
- package/dist/apps/cli/src/commands/modes.js +73 -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 +10 -0
- package/dist/apps/cli/src/commands/runtime.js +499 -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/desktop-runtime.d.ts +54 -0
- package/dist/apps/cli/src/desktop-runtime.js +720 -0
- package/dist/apps/cli/src/home.d.ts +7 -0
- package/dist/apps/cli/src/home.js +124 -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/release-installer.d.ts +57 -0
- package/dist/apps/cli/src/release-installer.js +432 -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 +229 -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/apps/cli/src/version.d.ts +2 -0
- package/dist/apps/cli/src/version.js +2 -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 +80 -0
- package/dist/packages/config/src/schema.d.ts +49 -0
- package/dist/packages/config/src/schema.js +28 -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/hud-model.d.ts +74 -0
- package/dist/packages/view-model/src/hud-model.js +295 -0
- package/dist/packages/view-model/src/index.d.ts +6 -0
- package/dist/packages/view-model/src/index.js +6 -0
- package/dist/packages/view-model/src/notification-preferences-model.d.ts +75 -0
- package/dist/packages/view-model/src/notification-preferences-model.js +400 -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/sync-state.d.ts +47 -0
- package/dist/packages/view-model/src/sync-state.js +140 -0
- package/dist/packages/view-model/src/usage-progress.d.ts +22 -0
- package/dist/packages/view-model/src/usage-progress.js +57 -0
- package/dist/packages/view-model/src/view-model.d.ts +215 -0
- package/dist/packages/view-model/src/view-model.js +826 -0
- package/package.json +40 -0
- package/scripts/postinstall.mjs +69 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
const SUPPORTED_SYNC_PROVIDERS = ["mock", "aws", "openai", "supabase", "cloudflare"];
|
|
2
|
+
export function parseSlashInput(input) {
|
|
3
|
+
const trimmed = input.trim();
|
|
4
|
+
if (trimmed.length === 0) {
|
|
5
|
+
return [];
|
|
6
|
+
}
|
|
7
|
+
return trimmed.split(/\s+/);
|
|
8
|
+
}
|
|
9
|
+
export function isSlashQuit(args) {
|
|
10
|
+
return args.length === 1 && args[0]?.toLowerCase() === "/quit";
|
|
11
|
+
}
|
|
12
|
+
export function resolveSlashCommand(args) {
|
|
13
|
+
const [rawCommand, ...rest] = args;
|
|
14
|
+
if (rawCommand === undefined || !rawCommand.startsWith("/")) {
|
|
15
|
+
return {
|
|
16
|
+
kind: "error",
|
|
17
|
+
message: "Slash commands must start with `/`.",
|
|
18
|
+
usage: "Run `moneysiren /help` for slash usage.",
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const command = rawCommand.toLowerCase();
|
|
22
|
+
if (command === "/help") {
|
|
23
|
+
return noExtraArgs(command, rest, ["--help"]);
|
|
24
|
+
}
|
|
25
|
+
if (command === "/version") {
|
|
26
|
+
return noExtraArgs(command, rest, ["--version"]);
|
|
27
|
+
}
|
|
28
|
+
if (command === "/doctor") {
|
|
29
|
+
return noExtraArgs(command, rest, ["doctor"]);
|
|
30
|
+
}
|
|
31
|
+
if (command === "/install") {
|
|
32
|
+
return resolveInstallSlash(rest);
|
|
33
|
+
}
|
|
34
|
+
if (command === "/modes") {
|
|
35
|
+
return noExtraArgs(command, rest, ["modes"]);
|
|
36
|
+
}
|
|
37
|
+
if (command === "/start") {
|
|
38
|
+
return {
|
|
39
|
+
kind: "dispatch",
|
|
40
|
+
args: ["start", ...rest],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
if (command === "/hud") {
|
|
44
|
+
return {
|
|
45
|
+
kind: "dispatch",
|
|
46
|
+
args: ["hud", ...rest],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (command === "/status") {
|
|
50
|
+
return noExtraArgs(command, rest, ["status"]);
|
|
51
|
+
}
|
|
52
|
+
if (command === "/stop") {
|
|
53
|
+
return {
|
|
54
|
+
kind: "dispatch",
|
|
55
|
+
args: ["stop", ...rest],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (command === "/restart") {
|
|
59
|
+
return {
|
|
60
|
+
kind: "dispatch",
|
|
61
|
+
args: ["restart", ...rest],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (command === "/init") {
|
|
65
|
+
return noExtraArgs(command, rest, ["init"]);
|
|
66
|
+
}
|
|
67
|
+
if (command === "/dashboard") {
|
|
68
|
+
return resolveDashboardSlash(rest);
|
|
69
|
+
}
|
|
70
|
+
if (command === "/summary") {
|
|
71
|
+
return resolveSummarySlash(rest);
|
|
72
|
+
}
|
|
73
|
+
if (command === "/notify") {
|
|
74
|
+
return resolveNotifySlash(rest);
|
|
75
|
+
}
|
|
76
|
+
if (command === "/desktop") {
|
|
77
|
+
return resolveDesktopSlash(rest);
|
|
78
|
+
}
|
|
79
|
+
if (command === "/sync") {
|
|
80
|
+
return resolveSyncSlash(rest);
|
|
81
|
+
}
|
|
82
|
+
if (command === "/report") {
|
|
83
|
+
return resolveReportSlash(rest);
|
|
84
|
+
}
|
|
85
|
+
if (command === "/theme") {
|
|
86
|
+
return resolveThemeSlash(rest);
|
|
87
|
+
}
|
|
88
|
+
if (command === "/quit") {
|
|
89
|
+
if (rest.length === 0) {
|
|
90
|
+
return {
|
|
91
|
+
kind: "quit",
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return invalidUsage(command, "Usage: moneysiren /quit");
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
kind: "error",
|
|
98
|
+
message: `Unknown slash command: ${formatSlashCommand(args)}`,
|
|
99
|
+
usage: "Run `moneysiren /help` for slash usage.",
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function noExtraArgs(command, rest, mappedArgs) {
|
|
103
|
+
if (rest.length > 0) {
|
|
104
|
+
return invalidUsage(command, `Usage: moneysiren ${command}`);
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
kind: "dispatch",
|
|
108
|
+
args: mappedArgs,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function resolveDashboardSlash(rest) {
|
|
112
|
+
if (rest.length === 0) {
|
|
113
|
+
return {
|
|
114
|
+
kind: "dispatch",
|
|
115
|
+
args: ["dashboard", "check"],
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const [subcommand, ...subcommandRest] = rest;
|
|
119
|
+
if (subcommand === "check") {
|
|
120
|
+
return {
|
|
121
|
+
kind: "dispatch",
|
|
122
|
+
args: ["dashboard", "check", ...subcommandRest],
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
return invalidUsage("/dashboard", "Usage: moneysiren /dashboard [check]");
|
|
126
|
+
}
|
|
127
|
+
function resolveInstallSlash(rest) {
|
|
128
|
+
if (rest.length === 0) {
|
|
129
|
+
return {
|
|
130
|
+
kind: "dispatch",
|
|
131
|
+
args: ["install"],
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
if (rest.length === 1 && rest[0] === "status") {
|
|
135
|
+
return {
|
|
136
|
+
kind: "dispatch",
|
|
137
|
+
args: ["install", "--status"],
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
kind: "dispatch",
|
|
142
|
+
args: ["install", ...rest],
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function resolveSyncSlash(rest) {
|
|
146
|
+
const [provider, ...extra] = rest;
|
|
147
|
+
if (provider === undefined || extra.length > 0 || !isSupportedSyncProvider(provider)) {
|
|
148
|
+
return invalidUsage("/sync", "Usage: moneysiren /sync <mock|aws|openai|supabase|cloudflare>");
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
kind: "dispatch",
|
|
152
|
+
args: ["sync", "--provider", provider],
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function resolveSummarySlash(rest) {
|
|
156
|
+
if (rest.length !== 1 || rest[0] !== "json") {
|
|
157
|
+
return invalidUsage("/summary", "Usage: moneysiren /summary json");
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
kind: "dispatch",
|
|
161
|
+
args: ["summary", "--json"],
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function resolveNotifySlash(rest) {
|
|
165
|
+
if (rest.length === 1 && rest[0] === "dry-run") {
|
|
166
|
+
return {
|
|
167
|
+
kind: "dispatch",
|
|
168
|
+
args: ["notify", "once", "--dry-run"],
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
if (rest.length === 1 && rest[0] === "prefs") {
|
|
172
|
+
return {
|
|
173
|
+
kind: "dispatch",
|
|
174
|
+
args: ["notify", "prefs", "list"],
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
return invalidUsage("/notify", "Usage: moneysiren /notify <dry-run|prefs>");
|
|
178
|
+
}
|
|
179
|
+
function resolveDesktopSlash(rest) {
|
|
180
|
+
if (rest.length !== 1 || rest[0] !== "status") {
|
|
181
|
+
return invalidUsage("/desktop", "Usage: moneysiren /desktop status");
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
kind: "dispatch",
|
|
185
|
+
args: ["desktop", "status"],
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function resolveReportSlash(rest) {
|
|
189
|
+
if (rest.length !== 1 || rest[0] !== "ko") {
|
|
190
|
+
return invalidUsage("/report", "Usage: moneysiren /report ko");
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
kind: "dispatch",
|
|
194
|
+
args: ["report", "daily", "--lang", "ko"],
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function resolveThemeSlash(rest) {
|
|
198
|
+
const [subcommand, ...extra] = rest;
|
|
199
|
+
if (subcommand === "preview" || subcommand === "image-prompt") {
|
|
200
|
+
if (extra.length > 0) {
|
|
201
|
+
return invalidUsage("/theme", "Usage: moneysiren /theme <preview|image-prompt|image-generate>");
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
kind: "dispatch",
|
|
205
|
+
args: ["theme", subcommand],
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
if (subcommand === "image-generate") {
|
|
209
|
+
return {
|
|
210
|
+
kind: "dispatch",
|
|
211
|
+
args: ["theme", subcommand, ...extra],
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
return invalidUsage("/theme", "Usage: moneysiren /theme <preview|image-prompt|image-generate>");
|
|
215
|
+
}
|
|
216
|
+
function invalidUsage(command, usage) {
|
|
217
|
+
return {
|
|
218
|
+
kind: "error",
|
|
219
|
+
message: `Invalid slash command usage: ${command}`,
|
|
220
|
+
usage,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function isSupportedSyncProvider(provider) {
|
|
224
|
+
return SUPPORTED_SYNC_PROVIDERS.includes(provider);
|
|
225
|
+
}
|
|
226
|
+
function formatSlashCommand(args) {
|
|
227
|
+
return args.join(" ");
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=slash.js.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type LocalAlertRecord } from "../../../packages/db/src/index.js";
|
|
2
|
+
import type { CliExecutionContext } from "./cli.js";
|
|
3
|
+
export interface SanitizedSummary {
|
|
4
|
+
generatedAt: string;
|
|
5
|
+
database: {
|
|
6
|
+
path: string;
|
|
7
|
+
available: boolean;
|
|
8
|
+
};
|
|
9
|
+
secretsReturned: false;
|
|
10
|
+
providerCount: number;
|
|
11
|
+
providers: SanitizedProviderSummary[];
|
|
12
|
+
totals: {
|
|
13
|
+
estimatedAmountMinorByCurrency: CurrencyTotal[];
|
|
14
|
+
alertCount: number;
|
|
15
|
+
health: "ok" | "degraded" | "down" | "unknown";
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export interface SanitizedProviderSummary {
|
|
19
|
+
key: string;
|
|
20
|
+
displayName: string;
|
|
21
|
+
lastCollectedAt: string | null;
|
|
22
|
+
usageSnapshotCount: number;
|
|
23
|
+
billingSnapshotCount: number;
|
|
24
|
+
healthSnapshotCount: number;
|
|
25
|
+
costEstimateCount: number;
|
|
26
|
+
alertCount: number;
|
|
27
|
+
health: "ok" | "degraded" | "down" | "unknown";
|
|
28
|
+
estimatedCost: {
|
|
29
|
+
amountMinor: number;
|
|
30
|
+
currency: string;
|
|
31
|
+
confidence: "low" | "medium" | "high";
|
|
32
|
+
} | null;
|
|
33
|
+
}
|
|
34
|
+
export interface NotificationDigest {
|
|
35
|
+
generatedAt: string;
|
|
36
|
+
secretsReturned: false;
|
|
37
|
+
providerCount: number;
|
|
38
|
+
estimatedAmountMinorByCurrency: CurrencyTotal[];
|
|
39
|
+
alertCount: number;
|
|
40
|
+
criticalAlertCount: number;
|
|
41
|
+
health: "ok" | "degraded" | "down" | "unknown";
|
|
42
|
+
}
|
|
43
|
+
interface CurrencyTotal {
|
|
44
|
+
currency: string;
|
|
45
|
+
amountMinor: number;
|
|
46
|
+
}
|
|
47
|
+
export declare function readSanitizedSummary(context: CliExecutionContext): Promise<SanitizedSummary>;
|
|
48
|
+
export declare function buildNotificationDigest(summary: SanitizedSummary, alerts?: readonly LocalAlertRecord[]): NotificationDigest;
|
|
49
|
+
export declare function readSanitizedNotificationDigest(context: CliExecutionContext): Promise<NotificationDigest>;
|
|
50
|
+
export {};
|
|
51
|
+
//# sourceMappingURL=summary-model.d.ts.map
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { stat } from "node:fs/promises";
|
|
2
|
+
import { readLocalStore, } from "../../../packages/db/src/index.js";
|
|
3
|
+
import { loadCliConfig, resolveDbPath } from "./commands/shared.js";
|
|
4
|
+
export async function readSanitizedSummary(context) {
|
|
5
|
+
const config = loadCliConfig(context.env);
|
|
6
|
+
const dbPath = resolveDbPath(context.cwd, config.dbPath);
|
|
7
|
+
const generatedAt = context.now().toISOString();
|
|
8
|
+
if (!await pathExists(dbPath)) {
|
|
9
|
+
return {
|
|
10
|
+
generatedAt,
|
|
11
|
+
database: {
|
|
12
|
+
path: config.dbPath,
|
|
13
|
+
available: false,
|
|
14
|
+
},
|
|
15
|
+
secretsReturned: false,
|
|
16
|
+
providerCount: 0,
|
|
17
|
+
providers: [],
|
|
18
|
+
totals: {
|
|
19
|
+
estimatedAmountMinorByCurrency: [],
|
|
20
|
+
alertCount: 0,
|
|
21
|
+
health: "unknown",
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const store = await readLocalStore({ dbPath });
|
|
26
|
+
const providers = store.providers.map((provider) => {
|
|
27
|
+
const usageSnapshots = store.usageSnapshots.filter((snapshot) => snapshot.providerKey === provider.key);
|
|
28
|
+
const billingSnapshots = store.billingSnapshots.filter((snapshot) => snapshot.providerKey === provider.key);
|
|
29
|
+
const healthSnapshots = store.serviceHealthSnapshots.filter((snapshot) => snapshot.providerKey === provider.key);
|
|
30
|
+
const costEstimates = store.costEstimates.filter((snapshot) => snapshot.providerKey === provider.key);
|
|
31
|
+
const alerts = store.alerts.filter((alert) => alert.providerKey === provider.key);
|
|
32
|
+
const latestCostEstimate = latestByCollectedAt(costEstimates);
|
|
33
|
+
return {
|
|
34
|
+
key: provider.key,
|
|
35
|
+
displayName: provider.displayName,
|
|
36
|
+
lastCollectedAt: latestTimestamp([
|
|
37
|
+
...usageSnapshots.map((snapshot) => snapshot.collectedAt),
|
|
38
|
+
...billingSnapshots.map((snapshot) => snapshot.collectedAt),
|
|
39
|
+
...healthSnapshots.map((snapshot) => snapshot.collectedAt),
|
|
40
|
+
...costEstimates.map((snapshot) => snapshot.collectedAt),
|
|
41
|
+
]),
|
|
42
|
+
usageSnapshotCount: usageSnapshots.length,
|
|
43
|
+
billingSnapshotCount: billingSnapshots.length,
|
|
44
|
+
healthSnapshotCount: healthSnapshots.length,
|
|
45
|
+
costEstimateCount: costEstimates.length,
|
|
46
|
+
alertCount: alerts.length,
|
|
47
|
+
health: summarizeHealth(healthSnapshots),
|
|
48
|
+
estimatedCost: latestCostEstimate === undefined
|
|
49
|
+
? null
|
|
50
|
+
: {
|
|
51
|
+
amountMinor: latestCostEstimate.estimatedAmountMinor,
|
|
52
|
+
currency: latestCostEstimate.currency,
|
|
53
|
+
confidence: latestCostEstimate.confidence,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
return {
|
|
58
|
+
generatedAt,
|
|
59
|
+
database: {
|
|
60
|
+
path: config.dbPath,
|
|
61
|
+
available: true,
|
|
62
|
+
},
|
|
63
|
+
secretsReturned: false,
|
|
64
|
+
providerCount: providers.length,
|
|
65
|
+
providers,
|
|
66
|
+
totals: {
|
|
67
|
+
estimatedAmountMinorByCurrency: summarizeCurrencyTotals(store.costEstimates),
|
|
68
|
+
alertCount: store.alerts.length,
|
|
69
|
+
health: summarizeProviderHealth(providers.map((provider) => provider.health)),
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export function buildNotificationDigest(summary, alerts = []) {
|
|
74
|
+
return {
|
|
75
|
+
generatedAt: summary.generatedAt,
|
|
76
|
+
secretsReturned: false,
|
|
77
|
+
providerCount: summary.providerCount,
|
|
78
|
+
estimatedAmountMinorByCurrency: summary.totals.estimatedAmountMinorByCurrency,
|
|
79
|
+
alertCount: summary.totals.alertCount,
|
|
80
|
+
criticalAlertCount: alerts.filter((alert) => alert.severity === "critical").length,
|
|
81
|
+
health: summary.totals.health,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export async function readSanitizedNotificationDigest(context) {
|
|
85
|
+
const config = loadCliConfig(context.env);
|
|
86
|
+
const dbPath = resolveDbPath(context.cwd, config.dbPath);
|
|
87
|
+
const summary = await readSanitizedSummary(context);
|
|
88
|
+
if (!summary.database.available) {
|
|
89
|
+
return buildNotificationDigest(summary);
|
|
90
|
+
}
|
|
91
|
+
const store = await readLocalStore({ dbPath });
|
|
92
|
+
return buildNotificationDigest(summary, store.alerts);
|
|
93
|
+
}
|
|
94
|
+
function summarizeCurrencyTotals(costEstimates) {
|
|
95
|
+
const totals = new Map();
|
|
96
|
+
for (const estimate of costEstimates) {
|
|
97
|
+
totals.set(estimate.currency, (totals.get(estimate.currency) ?? 0) + estimate.estimatedAmountMinor);
|
|
98
|
+
}
|
|
99
|
+
return [...totals.entries()]
|
|
100
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
101
|
+
.map(([currency, amountMinor]) => ({
|
|
102
|
+
currency,
|
|
103
|
+
amountMinor,
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
106
|
+
function summarizeHealth(snapshots) {
|
|
107
|
+
return summarizeProviderHealth(snapshots.map((snapshot) => snapshot.status));
|
|
108
|
+
}
|
|
109
|
+
function summarizeProviderHealth(statuses) {
|
|
110
|
+
if (statuses.includes("down")) {
|
|
111
|
+
return "down";
|
|
112
|
+
}
|
|
113
|
+
if (statuses.includes("degraded")) {
|
|
114
|
+
return "degraded";
|
|
115
|
+
}
|
|
116
|
+
if (statuses.includes("unknown") || statuses.length === 0) {
|
|
117
|
+
return "unknown";
|
|
118
|
+
}
|
|
119
|
+
return "ok";
|
|
120
|
+
}
|
|
121
|
+
function latestByCollectedAt(items) {
|
|
122
|
+
return [...items].sort((left, right) => right.collectedAt.localeCompare(left.collectedAt))[0];
|
|
123
|
+
}
|
|
124
|
+
function latestTimestamp(timestamps) {
|
|
125
|
+
return [...timestamps].sort((left, right) => right.localeCompare(left))[0] ?? null;
|
|
126
|
+
}
|
|
127
|
+
async function pathExists(path) {
|
|
128
|
+
try {
|
|
129
|
+
await stat(path);
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=summary-model.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface Theme {
|
|
2
|
+
readonly colorEnabled: boolean;
|
|
3
|
+
readonly source: string;
|
|
4
|
+
brand(text: string): string;
|
|
5
|
+
heading(text: string): string;
|
|
6
|
+
command(text: string): string;
|
|
7
|
+
muted(text: string): string;
|
|
8
|
+
warning(text: string): string;
|
|
9
|
+
}
|
|
10
|
+
interface ThemeOptions {
|
|
11
|
+
cwd?: string;
|
|
12
|
+
env: Record<string, string | undefined>;
|
|
13
|
+
stdoutIsTTY: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function createTheme(options: ThemeOptions): Theme;
|
|
16
|
+
export declare function shouldUseColor(options: ThemeOptions): boolean;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=theme.d.ts.map
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, join } from "node:path";
|
|
3
|
+
const DEFAULT_PALETTE = {
|
|
4
|
+
brand: "1;36",
|
|
5
|
+
heading: "1;37",
|
|
6
|
+
command: "32",
|
|
7
|
+
muted: "90",
|
|
8
|
+
warning: "33",
|
|
9
|
+
};
|
|
10
|
+
const IMAGE_DASHBOARD_PALETTE = {
|
|
11
|
+
brand: "1;38;5;30",
|
|
12
|
+
heading: "1;38;5;231",
|
|
13
|
+
command: "38;5;35",
|
|
14
|
+
muted: "38;5;244",
|
|
15
|
+
warning: "38;5;214",
|
|
16
|
+
};
|
|
17
|
+
export function createTheme(options) {
|
|
18
|
+
const colorEnabled = shouldUseColor(options);
|
|
19
|
+
const resolved = resolveThemePalette(options);
|
|
20
|
+
return {
|
|
21
|
+
colorEnabled,
|
|
22
|
+
source: resolved.source,
|
|
23
|
+
brand: style(colorEnabled, resolved.palette.brand),
|
|
24
|
+
heading: style(colorEnabled, resolved.palette.heading),
|
|
25
|
+
command: style(colorEnabled, resolved.palette.command),
|
|
26
|
+
muted: style(colorEnabled, resolved.palette.muted),
|
|
27
|
+
warning: style(colorEnabled, resolved.palette.warning),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function shouldUseColor(options) {
|
|
31
|
+
if (hasEnvValue(options.env.NO_COLOR)) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
const forcedColor = parseForceColor(options.env.FORCE_COLOR);
|
|
35
|
+
if (forcedColor !== undefined) {
|
|
36
|
+
return forcedColor;
|
|
37
|
+
}
|
|
38
|
+
if (options.env.TERM?.toLowerCase() === "dumb") {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return options.stdoutIsTTY;
|
|
42
|
+
}
|
|
43
|
+
function style(colorEnabled, code) {
|
|
44
|
+
return (text) => {
|
|
45
|
+
if (!colorEnabled) {
|
|
46
|
+
return text;
|
|
47
|
+
}
|
|
48
|
+
return `\x1b[${code}m${text}\x1b[0m`;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function resolveThemePalette(options) {
|
|
52
|
+
const themeFile = options.env.MONEYSIREN_CLI_THEME_FILE?.trim();
|
|
53
|
+
if (themeFile !== undefined && themeFile.length > 0) {
|
|
54
|
+
const loaded = loadThemeFile(themeFile, options.cwd ?? process.cwd());
|
|
55
|
+
if (loaded !== null) {
|
|
56
|
+
return loaded;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (options.env.MONEYSIREN_CLI_THEME?.trim().toLowerCase() === "image2-dashboard") {
|
|
60
|
+
return {
|
|
61
|
+
palette: IMAGE_DASHBOARD_PALETTE,
|
|
62
|
+
source: "image2-dashboard",
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
palette: DEFAULT_PALETTE,
|
|
67
|
+
source: "default",
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function loadThemeFile(path, cwd) {
|
|
71
|
+
try {
|
|
72
|
+
const absolutePath = isAbsolute(path) ? path : join(cwd, path);
|
|
73
|
+
const payload = JSON.parse(readFileSync(absolutePath, "utf8"));
|
|
74
|
+
return {
|
|
75
|
+
palette: {
|
|
76
|
+
...DEFAULT_PALETTE,
|
|
77
|
+
...sanitizePalette(payload.ansi ?? {}),
|
|
78
|
+
},
|
|
79
|
+
source: typeof payload.source === "string" && payload.source.trim().length > 0
|
|
80
|
+
? payload.source.trim().slice(0, 80)
|
|
81
|
+
: `file:${path}`,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function sanitizePalette(input) {
|
|
89
|
+
return Object.fromEntries(Object.entries(input)
|
|
90
|
+
.filter((entry) => isThemeRole(entry[0]) && isSafeAnsiCode(entry[1])));
|
|
91
|
+
}
|
|
92
|
+
function isThemeRole(value) {
|
|
93
|
+
return value === "brand" ||
|
|
94
|
+
value === "heading" ||
|
|
95
|
+
value === "command" ||
|
|
96
|
+
value === "muted" ||
|
|
97
|
+
value === "warning";
|
|
98
|
+
}
|
|
99
|
+
function isSafeAnsiCode(value) {
|
|
100
|
+
return typeof value === "string" && /^[0-9;]{1,32}$/.test(value);
|
|
101
|
+
}
|
|
102
|
+
function hasEnvValue(value) {
|
|
103
|
+
return value !== undefined && value.length > 0;
|
|
104
|
+
}
|
|
105
|
+
function parseForceColor(value) {
|
|
106
|
+
if (value === undefined) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
const normalized = value.trim().toLowerCase();
|
|
110
|
+
if (normalized.length === 0) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
if (normalized === "0" || normalized === "false" || normalized === "no") {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=theme.js.map
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { DEFAULT_DB_PATH, PROVIDER_ENV_KEYS, type ConfiguredProvider, type ProviderConfig, type ProviderConfigMap, type SlackConfig, type MoneySirenConfig, type MoneySirenEnv, } from "./schema.js";
|
|
2
|
+
export { loadMoneySirenConfig } from "./load.js";
|
|
3
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { DEFAULT_DB_PATH, PROVIDER_ENV_KEYS, } from "./schema.js";
|
|
2
|
+
const TELEMETRY_OPT_IN_VALUES = new Set(["1", "true", "yes", "on", "enabled"]);
|
|
3
|
+
export function loadMoneySirenConfig(env = process.env) {
|
|
4
|
+
const dbPath = loadDbPath(env);
|
|
5
|
+
const telemetryEnabled = loadTelemetryFlag(env);
|
|
6
|
+
return {
|
|
7
|
+
dbPath,
|
|
8
|
+
telemetryEnabled,
|
|
9
|
+
providers: loadProviders(env),
|
|
10
|
+
slack: {
|
|
11
|
+
webhookConfigured: isConfigured(env.SLACK_WEBHOOK_URL),
|
|
12
|
+
requiredEnvKey: "SLACK_WEBHOOK_URL",
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function loadDbPath(env) {
|
|
17
|
+
const rawPath = env.MONEYSIREN_DB_PATH;
|
|
18
|
+
if (rawPath === undefined) {
|
|
19
|
+
return DEFAULT_DB_PATH;
|
|
20
|
+
}
|
|
21
|
+
const trimmedPath = rawPath.trim();
|
|
22
|
+
if (trimmedPath.length === 0) {
|
|
23
|
+
throw new Error("MONEYSIREN_DB_PATH must not be blank.");
|
|
24
|
+
}
|
|
25
|
+
return trimmedPath;
|
|
26
|
+
}
|
|
27
|
+
function loadTelemetryFlag(env) {
|
|
28
|
+
const rawTelemetry = env.MONEYSIREN_TELEMETRY;
|
|
29
|
+
if (rawTelemetry === undefined || rawTelemetry.trim().length === 0) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
const normalizedTelemetry = rawTelemetry.trim().toLowerCase();
|
|
33
|
+
if (TELEMETRY_OPT_IN_VALUES.has(normalizedTelemetry)) {
|
|
34
|
+
throw new Error("Telemetry is not supported in MoneySiren v0.1.");
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
function loadProviders(env) {
|
|
39
|
+
return {
|
|
40
|
+
aws: loadProviderConfig("aws", env),
|
|
41
|
+
openai: loadProviderConfig("openai", env),
|
|
42
|
+
supabase: loadProviderConfig("supabase", env),
|
|
43
|
+
cloudflare: loadProviderConfig("cloudflare", env),
|
|
44
|
+
gcp: loadProviderConfig("gcp", env),
|
|
45
|
+
azure: loadProviderConfig("azure", env),
|
|
46
|
+
oracle: loadProviderConfig("oracle", env),
|
|
47
|
+
anthropic: loadProviderConfig("anthropic", env),
|
|
48
|
+
gemini: loadProviderConfig("gemini", env),
|
|
49
|
+
vercel: loadProviderConfig("vercel", env),
|
|
50
|
+
"github-actions": loadProviderConfig("github-actions", env),
|
|
51
|
+
railway: loadProviderConfig("railway", env),
|
|
52
|
+
fly: loadProviderConfig("fly", env),
|
|
53
|
+
netlify: loadProviderConfig("netlify", env),
|
|
54
|
+
render: loadProviderConfig("render", env),
|
|
55
|
+
neon: loadProviderConfig("neon", env),
|
|
56
|
+
"mongodb-atlas": loadProviderConfig("mongodb-atlas", env),
|
|
57
|
+
datadog: loadProviderConfig("datadog", env),
|
|
58
|
+
sentry: loadProviderConfig("sentry", env),
|
|
59
|
+
"codex-cli": loadProviderConfig("codex-cli", env),
|
|
60
|
+
"codex-app": loadProviderConfig("codex-app", env),
|
|
61
|
+
"claude-cli": loadProviderConfig("claude-cli", env),
|
|
62
|
+
"claude-app": loadProviderConfig("claude-app", env),
|
|
63
|
+
antigravity: loadProviderConfig("antigravity", env),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function loadProviderConfig(provider, env) {
|
|
67
|
+
const requiredEnvKeys = PROVIDER_ENV_KEYS[provider];
|
|
68
|
+
const configuredEnvKeys = requiredEnvKeys.filter((envKey) => isConfigured(env[envKey]));
|
|
69
|
+
const missingEnvKeys = requiredEnvKeys.filter((envKey) => !isConfigured(env[envKey]));
|
|
70
|
+
return {
|
|
71
|
+
configured: requiredEnvKeys.length > 0 && missingEnvKeys.length === 0,
|
|
72
|
+
requiredEnvKeys,
|
|
73
|
+
configuredEnvKeys,
|
|
74
|
+
missingEnvKeys,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function isConfigured(value) {
|
|
78
|
+
return value !== undefined && value.trim().length > 0;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=load.js.map
|