@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
@@ -0,0 +1,202 @@
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 === "/init") {
38
+ return noExtraArgs(command, rest, ["init"]);
39
+ }
40
+ if (command === "/dashboard") {
41
+ return resolveDashboardSlash(rest);
42
+ }
43
+ if (command === "/summary") {
44
+ return resolveSummarySlash(rest);
45
+ }
46
+ if (command === "/notify") {
47
+ return resolveNotifySlash(rest);
48
+ }
49
+ if (command === "/desktop") {
50
+ return resolveDesktopSlash(rest);
51
+ }
52
+ if (command === "/sync") {
53
+ return resolveSyncSlash(rest);
54
+ }
55
+ if (command === "/report") {
56
+ return resolveReportSlash(rest);
57
+ }
58
+ if (command === "/theme") {
59
+ return resolveThemeSlash(rest);
60
+ }
61
+ if (command === "/quit") {
62
+ if (rest.length === 0) {
63
+ return {
64
+ kind: "quit",
65
+ };
66
+ }
67
+ return invalidUsage(command, "Usage: moneysiren /quit");
68
+ }
69
+ return {
70
+ kind: "error",
71
+ message: `Unknown slash command: ${formatSlashCommand(args)}`,
72
+ usage: "Run `moneysiren /help` for slash usage.",
73
+ };
74
+ }
75
+ function noExtraArgs(command, rest, mappedArgs) {
76
+ if (rest.length > 0) {
77
+ return invalidUsage(command, `Usage: moneysiren ${command}`);
78
+ }
79
+ return {
80
+ kind: "dispatch",
81
+ args: mappedArgs,
82
+ };
83
+ }
84
+ function resolveDashboardSlash(rest) {
85
+ if (rest.length === 0) {
86
+ return {
87
+ kind: "dispatch",
88
+ args: ["dashboard", "check"],
89
+ };
90
+ }
91
+ const [subcommand, ...subcommandRest] = rest;
92
+ if (subcommand === "check") {
93
+ return {
94
+ kind: "dispatch",
95
+ args: ["dashboard", "check", ...subcommandRest],
96
+ };
97
+ }
98
+ return invalidUsage("/dashboard", "Usage: moneysiren /dashboard [check]");
99
+ }
100
+ function resolveInstallSlash(rest) {
101
+ if (rest.length === 0) {
102
+ return {
103
+ kind: "dispatch",
104
+ args: ["install"],
105
+ };
106
+ }
107
+ if (rest.length === 1 && rest[0] === "status") {
108
+ return {
109
+ kind: "dispatch",
110
+ args: ["install", "--status"],
111
+ };
112
+ }
113
+ return {
114
+ kind: "dispatch",
115
+ args: ["install", ...rest],
116
+ };
117
+ }
118
+ function resolveSyncSlash(rest) {
119
+ const [provider, ...extra] = rest;
120
+ if (provider === undefined || extra.length > 0 || !isSupportedSyncProvider(provider)) {
121
+ return invalidUsage("/sync", "Usage: moneysiren /sync <mock|aws|openai|supabase|cloudflare>");
122
+ }
123
+ return {
124
+ kind: "dispatch",
125
+ args: ["sync", "--provider", provider],
126
+ };
127
+ }
128
+ function resolveSummarySlash(rest) {
129
+ if (rest.length !== 1 || rest[0] !== "json") {
130
+ return invalidUsage("/summary", "Usage: moneysiren /summary json");
131
+ }
132
+ return {
133
+ kind: "dispatch",
134
+ args: ["summary", "--json"],
135
+ };
136
+ }
137
+ function resolveNotifySlash(rest) {
138
+ if (rest.length === 1 && rest[0] === "dry-run") {
139
+ return {
140
+ kind: "dispatch",
141
+ args: ["notify", "once", "--dry-run"],
142
+ };
143
+ }
144
+ if (rest.length === 1 && rest[0] === "prefs") {
145
+ return {
146
+ kind: "dispatch",
147
+ args: ["notify", "prefs", "list"],
148
+ };
149
+ }
150
+ return invalidUsage("/notify", "Usage: moneysiren /notify <dry-run|prefs>");
151
+ }
152
+ function resolveDesktopSlash(rest) {
153
+ if (rest.length !== 1 || rest[0] !== "status") {
154
+ return invalidUsage("/desktop", "Usage: moneysiren /desktop status");
155
+ }
156
+ return {
157
+ kind: "dispatch",
158
+ args: ["desktop", "status"],
159
+ };
160
+ }
161
+ function resolveReportSlash(rest) {
162
+ if (rest.length !== 1 || rest[0] !== "ko") {
163
+ return invalidUsage("/report", "Usage: moneysiren /report ko");
164
+ }
165
+ return {
166
+ kind: "dispatch",
167
+ args: ["report", "daily", "--lang", "ko"],
168
+ };
169
+ }
170
+ function resolveThemeSlash(rest) {
171
+ const [subcommand, ...extra] = rest;
172
+ if (subcommand === "preview" || subcommand === "image-prompt") {
173
+ if (extra.length > 0) {
174
+ return invalidUsage("/theme", "Usage: moneysiren /theme <preview|image-prompt|image-generate>");
175
+ }
176
+ return {
177
+ kind: "dispatch",
178
+ args: ["theme", subcommand],
179
+ };
180
+ }
181
+ if (subcommand === "image-generate") {
182
+ return {
183
+ kind: "dispatch",
184
+ args: ["theme", subcommand, ...extra],
185
+ };
186
+ }
187
+ return invalidUsage("/theme", "Usage: moneysiren /theme <preview|image-prompt|image-generate>");
188
+ }
189
+ function invalidUsage(command, usage) {
190
+ return {
191
+ kind: "error",
192
+ message: `Invalid slash command usage: ${command}`,
193
+ usage,
194
+ };
195
+ }
196
+ function isSupportedSyncProvider(provider) {
197
+ return SUPPORTED_SYNC_PROVIDERS.includes(provider);
198
+ }
199
+ function formatSlashCommand(args) {
200
+ return args.join(" ");
201
+ }
202
+ //# 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,3 @@
1
+ export { DEFAULT_DB_PATH, PROVIDER_ENV_KEYS, } from "./schema.js";
2
+ export { loadMoneySirenConfig } from "./load.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,3 @@
1
+ import { type MoneySirenConfig, type MoneySirenEnv } from "./schema.js";
2
+ export declare function loadMoneySirenConfig(env?: MoneySirenEnv): MoneySirenConfig;
3
+ //# sourceMappingURL=load.d.ts.map
@@ -0,0 +1,77 @@
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
+ "claude-cli": loadProviderConfig("claude-cli", env),
61
+ };
62
+ }
63
+ function loadProviderConfig(provider, env) {
64
+ const requiredEnvKeys = PROVIDER_ENV_KEYS[provider];
65
+ const configuredEnvKeys = requiredEnvKeys.filter((envKey) => isConfigured(env[envKey]));
66
+ const missingEnvKeys = requiredEnvKeys.filter((envKey) => !isConfigured(env[envKey]));
67
+ return {
68
+ configured: requiredEnvKeys.length > 0 && missingEnvKeys.length === 0,
69
+ requiredEnvKeys,
70
+ configuredEnvKeys,
71
+ missingEnvKeys,
72
+ };
73
+ }
74
+ function isConfigured(value) {
75
+ return value !== undefined && value.trim().length > 0;
76
+ }
77
+ //# sourceMappingURL=load.js.map
@@ -0,0 +1,46 @@
1
+ export declare const DEFAULT_DB_PATH = ".moneysiren/moneysiren.sqlite";
2
+ export declare const PROVIDER_ENV_KEYS: {
3
+ readonly aws: readonly ["AWS_PROFILE"];
4
+ readonly openai: readonly ["OPENAI_ADMIN_KEY"];
5
+ readonly supabase: readonly ["SUPABASE_ACCESS_TOKEN"];
6
+ readonly cloudflare: readonly ["CLOUDFLARE_API_TOKEN", "CLOUDFLARE_ACCOUNT_IDS"];
7
+ readonly gcp: readonly ["GOOGLE_APPLICATION_CREDENTIALS", "GOOGLE_CLOUD_PROJECT"];
8
+ readonly azure: readonly ["AZURE_TENANT_ID", "AZURE_CLIENT_ID", "AZURE_CLIENT_SECRET", "AZURE_SUBSCRIPTION_ID"];
9
+ readonly oracle: readonly ["OCI_CONFIG_FILE", "OCI_PROFILE"];
10
+ readonly anthropic: readonly ["ANTHROPIC_ADMIN_KEY"];
11
+ readonly gemini: readonly ["GEMINI_API_KEY", "GOOGLE_APPLICATION_CREDENTIALS", "GOOGLE_CLOUD_PROJECT"];
12
+ readonly vercel: readonly ["VERCEL_API_TOKEN"];
13
+ readonly "github-actions": readonly ["GITHUB_TOKEN"];
14
+ readonly railway: readonly ["RAILWAY_API_TOKEN"];
15
+ readonly fly: readonly ["FLY_ACCESS_TOKEN"];
16
+ readonly netlify: readonly ["NETLIFY_AUTH_TOKEN"];
17
+ readonly render: readonly ["RENDER_API_KEY"];
18
+ readonly neon: readonly ["NEON_API_KEY"];
19
+ readonly "mongodb-atlas": readonly ["MONGODB_ATLAS_PUBLIC_KEY", "MONGODB_ATLAS_PRIVATE_KEY", "MONGODB_ATLAS_ORG_ID"];
20
+ readonly datadog: readonly ["DATADOG_API_KEY", "DATADOG_APP_KEY", "DATADOG_SITE"];
21
+ readonly sentry: readonly ["SENTRY_AUTH_TOKEN", "SENTRY_ORG"];
22
+ readonly "codex-cli": readonly [];
23
+ readonly "claude-cli": readonly [];
24
+ };
25
+ export type ConfiguredProvider = keyof typeof PROVIDER_ENV_KEYS;
26
+ export interface ProviderConfig {
27
+ configured: boolean;
28
+ requiredEnvKeys: readonly string[];
29
+ configuredEnvKeys: readonly string[];
30
+ missingEnvKeys: readonly string[];
31
+ }
32
+ export type ProviderConfigMap = {
33
+ readonly [Provider in ConfiguredProvider]: ProviderConfig;
34
+ };
35
+ export interface SlackConfig {
36
+ webhookConfigured: boolean;
37
+ requiredEnvKey: "SLACK_WEBHOOK_URL";
38
+ }
39
+ export interface MoneySirenConfig {
40
+ dbPath: string;
41
+ telemetryEnabled: false;
42
+ providers: ProviderConfigMap;
43
+ slack: SlackConfig;
44
+ }
45
+ export type MoneySirenEnv = Record<string, string | undefined>;
46
+ //# sourceMappingURL=schema.d.ts.map