@moneysiren/app 0.1.0-alpha.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +45 -0
  3. package/dist/apps/cli/src/cli.d.ts +59 -0
  4. package/dist/apps/cli/src/cli.js +199 -0
  5. package/dist/apps/cli/src/commands/dashboard.d.ts +3 -0
  6. package/dist/apps/cli/src/commands/dashboard.js +239 -0
  7. package/dist/apps/cli/src/commands/doctor.d.ts +3 -0
  8. package/dist/apps/cli/src/commands/doctor.js +25 -0
  9. package/dist/apps/cli/src/commands/init.d.ts +3 -0
  10. package/dist/apps/cli/src/commands/init.js +18 -0
  11. package/dist/apps/cli/src/commands/install.d.ts +3 -0
  12. package/dist/apps/cli/src/commands/install.js +244 -0
  13. package/dist/apps/cli/src/commands/modes.d.ts +3 -0
  14. package/dist/apps/cli/src/commands/modes.js +73 -0
  15. package/dist/apps/cli/src/commands/notify.d.ts +3 -0
  16. package/dist/apps/cli/src/commands/notify.js +430 -0
  17. package/dist/apps/cli/src/commands/report.d.ts +3 -0
  18. package/dist/apps/cli/src/commands/report.js +206 -0
  19. package/dist/apps/cli/src/commands/runtime.d.ts +10 -0
  20. package/dist/apps/cli/src/commands/runtime.js +499 -0
  21. package/dist/apps/cli/src/commands/shared.d.ts +9 -0
  22. package/dist/apps/cli/src/commands/shared.js +29 -0
  23. package/dist/apps/cli/src/commands/summary.d.ts +3 -0
  24. package/dist/apps/cli/src/commands/summary.js +15 -0
  25. package/dist/apps/cli/src/commands/sync.d.ts +3 -0
  26. package/dist/apps/cli/src/commands/sync.js +393 -0
  27. package/dist/apps/cli/src/commands/theme.d.ts +3 -0
  28. package/dist/apps/cli/src/commands/theme.js +181 -0
  29. package/dist/apps/cli/src/desktop-runtime.d.ts +54 -0
  30. package/dist/apps/cli/src/desktop-runtime.js +720 -0
  31. package/dist/apps/cli/src/home.d.ts +7 -0
  32. package/dist/apps/cli/src/home.js +124 -0
  33. package/dist/apps/cli/src/index.d.ts +3 -0
  34. package/dist/apps/cli/src/index.js +14 -0
  35. package/dist/apps/cli/src/install-profile.d.ts +35 -0
  36. package/dist/apps/cli/src/install-profile.js +124 -0
  37. package/dist/apps/cli/src/install-selector.d.ts +10 -0
  38. package/dist/apps/cli/src/install-selector.js +66 -0
  39. package/dist/apps/cli/src/interactive.d.ts +3 -0
  40. package/dist/apps/cli/src/interactive.js +32 -0
  41. package/dist/apps/cli/src/postinstall.d.ts +3 -0
  42. package/dist/apps/cli/src/postinstall.js +42 -0
  43. package/dist/apps/cli/src/release-installer.d.ts +57 -0
  44. package/dist/apps/cli/src/release-installer.js +432 -0
  45. package/dist/apps/cli/src/runtime-adapter.d.ts +24 -0
  46. package/dist/apps/cli/src/runtime-adapter.js +185 -0
  47. package/dist/apps/cli/src/slash.d.ts +15 -0
  48. package/dist/apps/cli/src/slash.js +229 -0
  49. package/dist/apps/cli/src/summary-model.d.ts +51 -0
  50. package/dist/apps/cli/src/summary-model.js +136 -0
  51. package/dist/apps/cli/src/theme.d.ts +18 -0
  52. package/dist/apps/cli/src/theme.js +118 -0
  53. package/dist/apps/cli/src/version.d.ts +2 -0
  54. package/dist/apps/cli/src/version.js +2 -0
  55. package/dist/packages/config/src/index.d.ts +3 -0
  56. package/dist/packages/config/src/index.js +3 -0
  57. package/dist/packages/config/src/load.d.ts +3 -0
  58. package/dist/packages/config/src/load.js +80 -0
  59. package/dist/packages/config/src/schema.d.ts +49 -0
  60. package/dist/packages/config/src/schema.js +28 -0
  61. package/dist/packages/connectors/aws/src/cost-explorer.d.ts +34 -0
  62. package/dist/packages/connectors/aws/src/cost-explorer.js +43 -0
  63. package/dist/packages/connectors/aws/src/index.d.ts +35 -0
  64. package/dist/packages/connectors/aws/src/index.js +67 -0
  65. package/dist/packages/connectors/aws/src/normalize.d.ts +69 -0
  66. package/dist/packages/connectors/aws/src/normalize.js +141 -0
  67. package/dist/packages/connectors/aws/src/sdk-client.d.ts +6 -0
  68. package/dist/packages/connectors/aws/src/sdk-client.js +21 -0
  69. package/dist/packages/connectors/cloudflare/src/client.d.ts +23 -0
  70. package/dist/packages/connectors/cloudflare/src/client.js +107 -0
  71. package/dist/packages/connectors/cloudflare/src/index.d.ts +33 -0
  72. package/dist/packages/connectors/cloudflare/src/index.js +81 -0
  73. package/dist/packages/connectors/cloudflare/src/normalize.d.ts +113 -0
  74. package/dist/packages/connectors/cloudflare/src/normalize.js +288 -0
  75. package/dist/packages/connectors/mock/src/index.d.ts +58 -0
  76. package/dist/packages/connectors/mock/src/index.js +66 -0
  77. package/dist/packages/connectors/openai/src/index.d.ts +55 -0
  78. package/dist/packages/connectors/openai/src/index.js +169 -0
  79. package/dist/packages/connectors/openai/src/normalize.d.ts +91 -0
  80. package/dist/packages/connectors/openai/src/normalize.js +180 -0
  81. package/dist/packages/connectors/supabase/src/client.d.ts +22 -0
  82. package/dist/packages/connectors/supabase/src/client.js +132 -0
  83. package/dist/packages/connectors/supabase/src/index.d.ts +33 -0
  84. package/dist/packages/connectors/supabase/src/index.js +87 -0
  85. package/dist/packages/connectors/supabase/src/normalize.d.ts +106 -0
  86. package/dist/packages/connectors/supabase/src/normalize.js +266 -0
  87. package/dist/packages/core/src/collector.d.ts +12 -0
  88. package/dist/packages/core/src/collector.js +68 -0
  89. package/dist/packages/core/src/index.d.ts +5 -0
  90. package/dist/packages/core/src/index.js +4 -0
  91. package/dist/packages/core/src/provider.d.ts +18 -0
  92. package/dist/packages/core/src/provider.js +2 -0
  93. package/dist/packages/core/src/risk-engine.d.ts +9 -0
  94. package/dist/packages/core/src/risk-engine.js +4 -0
  95. package/dist/packages/core/src/snapshots.d.ts +49 -0
  96. package/dist/packages/core/src/snapshots.js +9 -0
  97. package/dist/packages/db/src/client.d.ts +11 -0
  98. package/dist/packages/db/src/client.js +14 -0
  99. package/dist/packages/db/src/index.d.ts +6 -0
  100. package/dist/packages/db/src/index.js +6 -0
  101. package/dist/packages/db/src/local-store.d.ts +161 -0
  102. package/dist/packages/db/src/local-store.js +623 -0
  103. package/dist/packages/db/src/migrate.d.ts +17 -0
  104. package/dist/packages/db/src/migrate.js +35 -0
  105. package/dist/packages/db/src/schema.d.ts +5 -0
  106. package/dist/packages/db/src/schema.js +120 -0
  107. package/dist/packages/db/src/sqlite-bin.d.ts +3 -0
  108. package/dist/packages/db/src/sqlite-bin.js +16 -0
  109. package/dist/packages/local-api/src/index.d.ts +2 -0
  110. package/dist/packages/local-api/src/index.js +2 -0
  111. package/dist/packages/local-api/src/server.d.ts +36 -0
  112. package/dist/packages/local-api/src/server.js +310 -0
  113. package/dist/packages/report/src/daily.d.ts +24 -0
  114. package/dist/packages/report/src/daily.js +9 -0
  115. package/dist/packages/report/src/index.d.ts +4 -0
  116. package/dist/packages/report/src/index.js +4 -0
  117. package/dist/packages/report/src/korean.d.ts +3 -0
  118. package/dist/packages/report/src/korean.js +62 -0
  119. package/dist/packages/report/src/slack.d.ts +34 -0
  120. package/dist/packages/report/src/slack.js +134 -0
  121. package/dist/packages/runtime/src/index.d.ts +2 -0
  122. package/dist/packages/runtime/src/index.js +2 -0
  123. package/dist/packages/runtime/src/runtime.d.ts +26 -0
  124. package/dist/packages/runtime/src/runtime.js +182 -0
  125. package/dist/packages/view-model/src/hud-model.d.ts +74 -0
  126. package/dist/packages/view-model/src/hud-model.js +295 -0
  127. package/dist/packages/view-model/src/index.d.ts +6 -0
  128. package/dist/packages/view-model/src/index.js +6 -0
  129. package/dist/packages/view-model/src/notification-preferences-model.d.ts +75 -0
  130. package/dist/packages/view-model/src/notification-preferences-model.js +400 -0
  131. package/dist/packages/view-model/src/notification-preferences.d.ts +6 -0
  132. package/dist/packages/view-model/src/notification-preferences.js +36 -0
  133. package/dist/packages/view-model/src/sync-state.d.ts +47 -0
  134. package/dist/packages/view-model/src/sync-state.js +140 -0
  135. package/dist/packages/view-model/src/usage-progress.d.ts +22 -0
  136. package/dist/packages/view-model/src/usage-progress.js +57 -0
  137. package/dist/packages/view-model/src/view-model.d.ts +215 -0
  138. package/dist/packages/view-model/src/view-model.js +826 -0
  139. package/package.json +40 -0
  140. package/scripts/postinstall.mjs +69 -0
@@ -0,0 +1,400 @@
1
+ export const NOTIFICATION_WIDGET_KEYS = [
2
+ "month_forecast",
3
+ "today_live_cost",
4
+ "risk_high_count",
5
+ "stale_connection_count",
6
+ "aws_month_forecast",
7
+ "openai_today_cost",
8
+ "openai_today_tokens",
9
+ "claude_five_hour_percent",
10
+ "claude_weekly_percent",
11
+ "codex_five_hour_percent",
12
+ "codex_weekly_percent",
13
+ "codex_reset_credit_count",
14
+ "codex_reset_credit_expiry",
15
+ "supabase_usage_health",
16
+ "cloudflare_month_to_date",
17
+ ];
18
+ export const LOCAL_CLI_DASHBOARD_METRIC_KEYS = [
19
+ "context_percent",
20
+ "last_request_tokens",
21
+ "total_tokens",
22
+ "five_hour_limit_percent",
23
+ "weekly_limit_percent",
24
+ "five_hour_remaining_tokens",
25
+ "weekly_remaining_tokens",
26
+ "usage_reset_credits",
27
+ "usage_reset_credit_estimate",
28
+ "context_tokens",
29
+ "input_tokens",
30
+ "output_tokens",
31
+ "cache_tokens",
32
+ "reasoning_tokens",
33
+ "sessions",
34
+ "turns",
35
+ "tool_calls",
36
+ "log_files",
37
+ ];
38
+ export const DASHBOARD_VIEW_KEYS = ["overview", "today", "forecast", "risks"];
39
+ export const DASHBOARD_WIDGET_SIZES = ["compact", "normal", "wide", "full"];
40
+ export const DASHBOARD_WIDGET_KEYS_BY_VIEW = {
41
+ overview: [
42
+ "overview_meta",
43
+ "overview_metrics",
44
+ "overview_trend",
45
+ "overview_grouping",
46
+ "overview_services",
47
+ "overview_insights",
48
+ ],
49
+ today: [
50
+ "today_main",
51
+ "today_rail",
52
+ ],
53
+ forecast: [
54
+ "forecast_metrics",
55
+ "forecast_table",
56
+ "forecast_breakdown",
57
+ ],
58
+ risks: [
59
+ "risks_summary",
60
+ "risks_table",
61
+ ],
62
+ };
63
+ export const DEFAULT_SELECTED_NOTIFICATION_WIDGET_KEYS = [
64
+ "month_forecast",
65
+ "today_live_cost",
66
+ "risk_high_count",
67
+ "stale_connection_count",
68
+ "openai_today_tokens",
69
+ "codex_five_hour_percent",
70
+ "codex_reset_credit_count",
71
+ "codex_reset_credit_expiry",
72
+ ];
73
+ const LEGACY_SELECTED_NOTIFICATION_WIDGET_KEY_SETS = [
74
+ [
75
+ "month_forecast",
76
+ "today_live_cost",
77
+ "risk_high_count",
78
+ "stale_connection_count",
79
+ "openai_today_tokens",
80
+ "codex_five_hour_percent",
81
+ ],
82
+ [
83
+ "month_forecast",
84
+ "today_live_cost",
85
+ "risk_high_count",
86
+ "stale_connection_count",
87
+ "openai_today_tokens",
88
+ "codex_five_hour_percent",
89
+ "codex_reset_credit_expiry",
90
+ ],
91
+ ];
92
+ export const DEFAULT_LOCAL_CLI_DASHBOARD_METRIC_KEYS = [
93
+ "context_percent",
94
+ "last_request_tokens",
95
+ "total_tokens",
96
+ "usage_reset_credits",
97
+ ];
98
+ export const DEFAULT_DASHBOARD_WIDGET_LAYOUTS = {
99
+ overview: defaultDashboardWidgetLayout("overview"),
100
+ today: defaultDashboardWidgetLayout("today"),
101
+ forecast: defaultDashboardWidgetLayout("forecast"),
102
+ risks: defaultDashboardWidgetLayout("risks"),
103
+ };
104
+ export const DEFAULT_NOTIFICATION_THRESHOLD_RULES = [
105
+ {
106
+ widgetKey: "risk_high_count",
107
+ operator: "gte",
108
+ value: 1,
109
+ cooldownMinutes: 60,
110
+ },
111
+ {
112
+ widgetKey: "today_live_cost",
113
+ operator: "gte",
114
+ value: 10,
115
+ cooldownMinutes: 180,
116
+ },
117
+ {
118
+ widgetKey: "codex_weekly_percent",
119
+ operator: "gte",
120
+ value: 90,
121
+ cooldownMinutes: 360,
122
+ },
123
+ ];
124
+ export const DEFAULT_NOTIFICATION_PREFERENCES = {
125
+ enabled: true,
126
+ digestEnabled: true,
127
+ digestInterval: "daily",
128
+ quietHours: {
129
+ start: "22:00",
130
+ end: "08:00",
131
+ },
132
+ selectedWidgets: DEFAULT_SELECTED_NOTIFICATION_WIDGET_KEYS,
133
+ thresholdRules: DEFAULT_NOTIFICATION_THRESHOLD_RULES,
134
+ desktopEnabled: false,
135
+ dashboard: {
136
+ localCliMetricKeys: DEFAULT_LOCAL_CLI_DASHBOARD_METRIC_KEYS,
137
+ widgetLayouts: DEFAULT_DASHBOARD_WIDGET_LAYOUTS,
138
+ budget: {
139
+ monthlyBudgetMinor: null,
140
+ currency: "USD",
141
+ warningPercent: 80,
142
+ criticalPercent: 100,
143
+ },
144
+ },
145
+ hud: {
146
+ alwaysOnTop: true,
147
+ fontScale: 0.95,
148
+ opacity: 0.94,
149
+ selectedWidgets: DEFAULT_SELECTED_NOTIFICATION_WIDGET_KEYS,
150
+ },
151
+ };
152
+ export function parseNotificationPreferences(value) {
153
+ if (!isRecord(value)) {
154
+ return cloneNotificationPreferences(DEFAULT_NOTIFICATION_PREFERENCES);
155
+ }
156
+ const selectedWidgets = parseSelectedWidgets(value.selectedWidgets);
157
+ return {
158
+ enabled: typeof value.enabled === "boolean" ? value.enabled : DEFAULT_NOTIFICATION_PREFERENCES.enabled,
159
+ digestEnabled: typeof value.digestEnabled === "boolean"
160
+ ? value.digestEnabled
161
+ : DEFAULT_NOTIFICATION_PREFERENCES.digestEnabled,
162
+ digestInterval: parseDigestInterval(value.digestInterval),
163
+ quietHours: parseQuietHours(value.quietHours),
164
+ selectedWidgets,
165
+ thresholdRules: parseThresholdRules(value.thresholdRules),
166
+ desktopEnabled: typeof value.desktopEnabled === "boolean"
167
+ ? value.desktopEnabled
168
+ : DEFAULT_NOTIFICATION_PREFERENCES.desktopEnabled,
169
+ dashboard: parseDashboardDisplayPreferences(value.dashboard),
170
+ hud: parseHudPreferences(value.hud, selectedWidgets),
171
+ };
172
+ }
173
+ export function cloneNotificationPreferences(preferences) {
174
+ return {
175
+ enabled: preferences.enabled,
176
+ digestEnabled: preferences.digestEnabled,
177
+ digestInterval: preferences.digestInterval,
178
+ quietHours: {
179
+ ...preferences.quietHours,
180
+ },
181
+ selectedWidgets: [...preferences.selectedWidgets],
182
+ thresholdRules: preferences.thresholdRules.map((rule) => ({ ...rule })),
183
+ desktopEnabled: preferences.desktopEnabled,
184
+ dashboard: parseDashboardDisplayPreferences(preferences.dashboard),
185
+ hud: parseHudPreferences(preferences.hud),
186
+ };
187
+ }
188
+ function parseDashboardDisplayPreferences(value) {
189
+ const record = isRecord(value) ? value : {};
190
+ return {
191
+ localCliMetricKeys: parseLocalCliDashboardMetricKeys(record.localCliMetricKeys),
192
+ budget: parseDashboardBudgetPreferences(record.budget),
193
+ widgetLayouts: parseDashboardWidgetLayouts(record.widgetLayouts),
194
+ };
195
+ }
196
+ function parseDashboardWidgetLayouts(value) {
197
+ const record = isRecord(value) ? value : {};
198
+ return {
199
+ overview: parseDashboardWidgetLayout("overview", record.overview),
200
+ today: parseDashboardWidgetLayout("today", record.today),
201
+ forecast: parseDashboardWidgetLayout("forecast", record.forecast),
202
+ risks: parseDashboardWidgetLayout("risks", record.risks),
203
+ };
204
+ }
205
+ function parseDashboardWidgetLayout(viewKey, value) {
206
+ const validWidgetKeys = new Set(DASHBOARD_WIDGET_KEYS_BY_VIEW[viewKey]);
207
+ const defaults = defaultDashboardWidgetLayout(viewKey);
208
+ const defaultByKey = new Map(defaults.map((item) => [item.widgetKey, item]));
209
+ const parsed = Array.isArray(value)
210
+ ? value
211
+ .map((item) => {
212
+ if (!isRecord(item) || typeof item.widgetKey !== "string" || !validWidgetKeys.has(item.widgetKey)) {
213
+ return null;
214
+ }
215
+ const widgetKey = item.widgetKey;
216
+ const fallback = defaultByKey.get(widgetKey);
217
+ return {
218
+ widgetKey,
219
+ visible: typeof item.visible === "boolean" ? item.visible : fallback?.visible ?? true,
220
+ size: parseDashboardWidgetSize(item.size, fallback?.size ?? "normal"),
221
+ };
222
+ })
223
+ .filter((item) => item !== null)
224
+ : [];
225
+ const seen = new Set();
226
+ const normalized = parsed.filter((item) => {
227
+ if (seen.has(item.widgetKey)) {
228
+ return false;
229
+ }
230
+ seen.add(item.widgetKey);
231
+ return true;
232
+ });
233
+ const missing = defaults.filter((item) => !seen.has(item.widgetKey));
234
+ return [...normalized, ...missing];
235
+ }
236
+ function defaultDashboardWidgetLayout(viewKey) {
237
+ return DASHBOARD_WIDGET_KEYS_BY_VIEW[viewKey].map((widgetKey) => ({
238
+ widgetKey,
239
+ visible: true,
240
+ size: defaultDashboardWidgetSize(widgetKey),
241
+ }));
242
+ }
243
+ function defaultDashboardWidgetSize(widgetKey) {
244
+ if (widgetKey === "overview_metrics" || widgetKey === "overview_services" || widgetKey === "risks_table") {
245
+ return "full";
246
+ }
247
+ if (widgetKey === "today_main" || widgetKey === "forecast_table") {
248
+ return "wide";
249
+ }
250
+ if (widgetKey === "forecast_breakdown" || widgetKey === "today_rail") {
251
+ return "compact";
252
+ }
253
+ return "normal";
254
+ }
255
+ function parseDashboardWidgetSize(value, fallback) {
256
+ return typeof value === "string" && DASHBOARD_WIDGET_SIZES.includes(value)
257
+ ? value
258
+ : fallback;
259
+ }
260
+ function parseDashboardBudgetPreferences(value) {
261
+ const record = isRecord(value) ? value : {};
262
+ const monthlyBudgetMinor = parseOptionalPositiveInteger(record.monthlyBudgetMinor);
263
+ const warningPercent = clampNumber(record.warningPercent, 1, 999, DEFAULT_NOTIFICATION_PREFERENCES.dashboard.budget.warningPercent);
264
+ const criticalPercent = clampNumber(record.criticalPercent, warningPercent, 999, DEFAULT_NOTIFICATION_PREFERENCES.dashboard.budget.criticalPercent);
265
+ return {
266
+ monthlyBudgetMinor,
267
+ currency: parseCurrency(record.currency, DEFAULT_NOTIFICATION_PREFERENCES.dashboard.budget.currency),
268
+ warningPercent,
269
+ criticalPercent,
270
+ };
271
+ }
272
+ function parseHudPreferences(value, fallbackSelectedWidgets = DEFAULT_NOTIFICATION_PREFERENCES.hud.selectedWidgets) {
273
+ const record = isRecord(value) ? value : {};
274
+ return {
275
+ alwaysOnTop: typeof record.alwaysOnTop === "boolean"
276
+ ? record.alwaysOnTop
277
+ : DEFAULT_NOTIFICATION_PREFERENCES.hud.alwaysOnTop,
278
+ fontScale: clampNumber(record.fontScale, 0.8, 1.3, DEFAULT_NOTIFICATION_PREFERENCES.hud.fontScale),
279
+ opacity: clampNumber(record.opacity, 0, 1, DEFAULT_NOTIFICATION_PREFERENCES.hud.opacity),
280
+ selectedWidgets: parseSelectedWidgets(record.selectedWidgets, fallbackSelectedWidgets),
281
+ };
282
+ }
283
+ function parseLocalCliDashboardMetricKeys(value, fallbackMetricKeys = DEFAULT_LOCAL_CLI_DASHBOARD_METRIC_KEYS) {
284
+ const metricKeys = new Set(LOCAL_CLI_DASHBOARD_METRIC_KEYS);
285
+ const selected = Array.isArray(value)
286
+ ? value.filter((item) => typeof item === "string" && metricKeys.has(item))
287
+ : [...fallbackMetricKeys];
288
+ return selected.length === 0 ? [...fallbackMetricKeys] : [...new Set(selected)];
289
+ }
290
+ function parseDigestInterval(value) {
291
+ return value === "six-hours" || value === "daily" || value === "weekly"
292
+ ? value
293
+ : DEFAULT_NOTIFICATION_PREFERENCES.digestInterval;
294
+ }
295
+ function parseQuietHours(value) {
296
+ const record = isRecord(value) ? value : {};
297
+ return {
298
+ start: parseTime(record.start, DEFAULT_NOTIFICATION_PREFERENCES.quietHours.start),
299
+ end: parseTime(record.end, DEFAULT_NOTIFICATION_PREFERENCES.quietHours.end),
300
+ };
301
+ }
302
+ function parseSelectedWidgets(value, fallbackSelectedWidgets = DEFAULT_SELECTED_NOTIFICATION_WIDGET_KEYS) {
303
+ const widgetKeys = new Set(NOTIFICATION_WIDGET_KEYS);
304
+ const selected = Array.isArray(value)
305
+ ? value.filter((item) => typeof item === "string" && widgetKeys.has(item))
306
+ : [...fallbackSelectedWidgets];
307
+ if (selected.length === 0) {
308
+ return [...fallbackSelectedWidgets];
309
+ }
310
+ const uniqueSelected = [...new Set(selected)];
311
+ return isLegacySelectedWidgetDefault(uniqueSelected)
312
+ ? [...DEFAULT_SELECTED_NOTIFICATION_WIDGET_KEYS]
313
+ : migrateSelectedWidgets(uniqueSelected);
314
+ }
315
+ function migrateSelectedWidgets(selectedWidgets) {
316
+ if (!selectedWidgets.includes("codex_reset_credit_expiry") ||
317
+ selectedWidgets.includes("codex_reset_credit_count")) {
318
+ return [...selectedWidgets];
319
+ }
320
+ const migrated = [];
321
+ for (const widgetKey of selectedWidgets) {
322
+ if (widgetKey === "codex_reset_credit_expiry") {
323
+ migrated.push("codex_reset_credit_count");
324
+ }
325
+ migrated.push(widgetKey);
326
+ }
327
+ return migrated;
328
+ }
329
+ function isLegacySelectedWidgetDefault(selectedWidgets) {
330
+ return LEGACY_SELECTED_NOTIFICATION_WIDGET_KEY_SETS.some((legacySet) => legacySet.length === selectedWidgets.length &&
331
+ legacySet.every((widgetKey, index) => selectedWidgets[index] === widgetKey));
332
+ }
333
+ function parseThresholdRules(value) {
334
+ if (!Array.isArray(value)) {
335
+ return DEFAULT_NOTIFICATION_THRESHOLD_RULES.map((rule) => ({ ...rule }));
336
+ }
337
+ const rules = value
338
+ .map((item) => isRecord(item) ? item : null)
339
+ .filter((item) => item !== null)
340
+ .map((item) => {
341
+ const widgetKey = parseWidgetKey(item.widgetKey);
342
+ const operator = parseOperator(item.operator);
343
+ const numericValue = parseNonNegativeNumber(item.value);
344
+ const cooldownMinutes = parseNonNegativeNumber(item.cooldownMinutes);
345
+ if (widgetKey === null || operator === null || numericValue === null || cooldownMinutes === null) {
346
+ return null;
347
+ }
348
+ return {
349
+ widgetKey,
350
+ operator,
351
+ value: numericValue,
352
+ cooldownMinutes,
353
+ };
354
+ })
355
+ .filter((item) => item !== null);
356
+ return rules.length === 0 ? DEFAULT_NOTIFICATION_THRESHOLD_RULES.map((rule) => ({ ...rule })) : rules;
357
+ }
358
+ function parseWidgetKey(value) {
359
+ return typeof value === "string" && NOTIFICATION_WIDGET_KEYS.includes(value)
360
+ ? value
361
+ : null;
362
+ }
363
+ function parseOperator(value) {
364
+ return value === "gte" || value === "lte" || value === "eq" ? value : null;
365
+ }
366
+ function parseNonNegativeNumber(value) {
367
+ if (typeof value !== "number" || !Number.isFinite(value)) {
368
+ return null;
369
+ }
370
+ return Math.max(0, value);
371
+ }
372
+ function parseOptionalPositiveInteger(value) {
373
+ if (value === null || value === undefined || value === "") {
374
+ return null;
375
+ }
376
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
377
+ return null;
378
+ }
379
+ return Math.round(value);
380
+ }
381
+ function parseCurrency(value, fallback) {
382
+ if (typeof value !== "string") {
383
+ return fallback;
384
+ }
385
+ const normalized = value.trim().toUpperCase();
386
+ return /^[A-Z]{3}$/.test(normalized) ? normalized : fallback;
387
+ }
388
+ function clampNumber(value, min, max, fallback) {
389
+ if (typeof value !== "number" || !Number.isFinite(value)) {
390
+ return fallback;
391
+ }
392
+ return Math.min(max, Math.max(min, Math.round(value * 100) / 100));
393
+ }
394
+ function parseTime(value, fallback) {
395
+ return typeof value === "string" && /^\d{2}:\d{2}$/.test(value) ? value : fallback;
396
+ }
397
+ function isRecord(value) {
398
+ return typeof value === "object" && value !== null && !Array.isArray(value);
399
+ }
400
+ //# sourceMappingURL=notification-preferences-model.js.map
@@ -0,0 +1,6 @@
1
+ import { type NotificationPreferenceFileOptions, type NotificationPreferences } from "./notification-preferences-model.js";
2
+ export { cloneNotificationPreferences, DEFAULT_NOTIFICATION_PREFERENCES, DEFAULT_NOTIFICATION_THRESHOLD_RULES, DEFAULT_LOCAL_CLI_DASHBOARD_METRIC_KEYS, DEFAULT_DASHBOARD_WIDGET_LAYOUTS, DEFAULT_SELECTED_NOTIFICATION_WIDGET_KEYS, DASHBOARD_VIEW_KEYS, DASHBOARD_WIDGET_KEYS_BY_VIEW, DASHBOARD_WIDGET_SIZES, LOCAL_CLI_DASHBOARD_METRIC_KEYS, NOTIFICATION_WIDGET_KEYS, parseNotificationPreferences, type DashboardBudgetPreferences, type DashboardDisplayPreferences, type DashboardViewKey, type DashboardWidgetKey, type DashboardWidgetLayoutItem, type DashboardWidgetLayoutPreferences, type DashboardWidgetSize, type DigestInterval, type LocalCliDashboardMetricKey, type NotificationPreferenceFileOptions, type NotificationPreferences, type NotificationThresholdRule, type NotificationWidgetKey, type ThresholdOperator, } from "./notification-preferences-model.js";
3
+ export declare function readNotificationPreferencesFile(options?: NotificationPreferenceFileOptions): Promise<NotificationPreferences>;
4
+ export declare function writeNotificationPreferencesFile(preferences: NotificationPreferences, options?: NotificationPreferenceFileOptions): Promise<NotificationPreferences>;
5
+ export declare function resolveNotificationPreferencesPath(options?: NotificationPreferenceFileOptions): string;
6
+ //# sourceMappingURL=notification-preferences.d.ts.map
@@ -0,0 +1,36 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { dirname, isAbsolute, join } from "node:path";
3
+ import { cloneNotificationPreferences, DEFAULT_NOTIFICATION_PREFERENCES, parseNotificationPreferences, } from "./notification-preferences-model.js";
4
+ export { cloneNotificationPreferences, DEFAULT_NOTIFICATION_PREFERENCES, DEFAULT_NOTIFICATION_THRESHOLD_RULES, DEFAULT_LOCAL_CLI_DASHBOARD_METRIC_KEYS, DEFAULT_DASHBOARD_WIDGET_LAYOUTS, DEFAULT_SELECTED_NOTIFICATION_WIDGET_KEYS, DASHBOARD_VIEW_KEYS, DASHBOARD_WIDGET_KEYS_BY_VIEW, DASHBOARD_WIDGET_SIZES, LOCAL_CLI_DASHBOARD_METRIC_KEYS, NOTIFICATION_WIDGET_KEYS, parseNotificationPreferences, } from "./notification-preferences-model.js";
5
+ const PREFERENCES_PATH_ENV = "MONEYSIREN_NOTIFICATION_PREFS_PATH";
6
+ const DEFAULT_PREFERENCES_PATH = ".moneysiren/notification-preferences.json";
7
+ export async function readNotificationPreferencesFile(options = {}) {
8
+ try {
9
+ const parsed = JSON.parse(await readFile(resolveNotificationPreferencesPath(options), "utf8"));
10
+ return parseNotificationPreferences(parsed);
11
+ }
12
+ catch {
13
+ return cloneNotificationPreferences(DEFAULT_NOTIFICATION_PREFERENCES);
14
+ }
15
+ }
16
+ export async function writeNotificationPreferencesFile(preferences, options = {}) {
17
+ const normalized = parseNotificationPreferences(preferences);
18
+ const path = resolveNotificationPreferencesPath(options);
19
+ await mkdir(dirname(path), { recursive: true });
20
+ await writeFile(path, `${JSON.stringify(normalized, null, 2)}\n`, "utf8");
21
+ return normalized;
22
+ }
23
+ export function resolveNotificationPreferencesPath(options = {}) {
24
+ if (options.path !== undefined && options.path.trim().length > 0) {
25
+ return resolveLocalPath(options.path, options.cwd);
26
+ }
27
+ const configuredPath = options.env?.[PREFERENCES_PATH_ENV]?.trim();
28
+ if (configuredPath !== undefined && configuredPath.length > 0) {
29
+ return resolveLocalPath(configuredPath, options.cwd);
30
+ }
31
+ return resolveLocalPath(DEFAULT_PREFERENCES_PATH, options.cwd);
32
+ }
33
+ function resolveLocalPath(path, cwd = process.cwd()) {
34
+ return isAbsolute(path) ? path : join(cwd, path);
35
+ }
36
+ //# sourceMappingURL=notification-preferences.js.map
@@ -0,0 +1,47 @@
1
+ export type AggregateSyncStatus = "ok" | "partial" | "stale" | "error" | "empty";
2
+ export type RiskSeverity = "info" | "warning" | "critical";
3
+ export type SyncStateValue = "fresh" | "stale" | "error" | "not_configured" | "unavailable" | "locked";
4
+ export type SyncErrorCode = "not_configured" | "auth_expired" | "permission_denied" | "rate_limited" | "timeout" | "network" | "upstream_unavailable" | "schema_changed" | "invalid_data" | "local_source_missing" | "local_io" | "internal";
5
+ export interface SyncErrorView {
6
+ code: SyncErrorCode;
7
+ retryable: boolean;
8
+ userActionRequired: boolean;
9
+ message: string;
10
+ }
11
+ export interface ItemSyncView {
12
+ state: SyncStateValue;
13
+ observedAt: string | null;
14
+ lastAttemptAt: string | null;
15
+ lastSuccessAt: string | null;
16
+ freshUntil: string | null;
17
+ staleUntil: string | null;
18
+ ageSeconds: number | null;
19
+ source: string;
20
+ error: SyncErrorView | null;
21
+ lastRefreshFailed: boolean;
22
+ }
23
+ export declare function createSyncError(code: SyncErrorCode, message?: string): SyncErrorView;
24
+ export declare function syncViewFromFreshness(options: {
25
+ freshness: "live" | "stale" | "error" | "unavailable" | "not_configured" | "locked";
26
+ checkedAt: string | null;
27
+ generatedAt: string;
28
+ ttlSeconds?: number | null;
29
+ source: string;
30
+ message?: string | null;
31
+ lastAttemptAt?: string | null;
32
+ lastSuccessAt?: string | null;
33
+ freshUntil?: string | null;
34
+ staleUntil?: string | null;
35
+ lastRefreshFailed?: boolean;
36
+ }): ItemSyncView;
37
+ export declare function summarizeAggregateSync(items: readonly ItemSyncView[]): {
38
+ status: AggregateSyncStatus;
39
+ freshCount: number;
40
+ staleCount: number;
41
+ errorCount: number;
42
+ neutralCount: number;
43
+ lastSuccessAt: string | null;
44
+ };
45
+ export declare function syncStateFromFreshness(freshness: "live" | "stale" | "error" | "unavailable" | "not_configured" | "locked"): SyncStateValue;
46
+ export declare function isNeutralSyncState(state: SyncStateValue): boolean;
47
+ //# sourceMappingURL=sync-state.d.ts.map
@@ -0,0 +1,140 @@
1
+ export function createSyncError(code, message) {
2
+ return {
3
+ code,
4
+ retryable: isRetryableSyncError(code),
5
+ userActionRequired: isUserActionRequiredSyncError(code),
6
+ message: sanitizeSyncMessage(message ?? defaultSyncErrorMessage(code)),
7
+ };
8
+ }
9
+ export function syncViewFromFreshness(options) {
10
+ const state = syncStateFromFreshness(options.freshness);
11
+ const observedAt = options.checkedAt;
12
+ const ageSeconds = observedAt === null
13
+ ? null
14
+ : Math.max(0, Math.floor((Date.parse(options.generatedAt) - Date.parse(observedAt)) / 1000));
15
+ const defaultFreshUntil = observedAt === null || options.ttlSeconds === null || options.ttlSeconds === undefined
16
+ ? null
17
+ : new Date(Date.parse(observedAt) + options.ttlSeconds * 1000).toISOString();
18
+ return {
19
+ state,
20
+ observedAt,
21
+ lastAttemptAt: options.lastAttemptAt ?? observedAt,
22
+ lastSuccessAt: options.lastSuccessAt ?? (state === "error" ? null : observedAt),
23
+ freshUntil: options.freshUntil ?? defaultFreshUntil,
24
+ staleUntil: options.staleUntil ?? null,
25
+ ageSeconds: Number.isFinite(ageSeconds) ? ageSeconds : null,
26
+ source: options.source,
27
+ error: state === "error" || state === "locked" || state === "not_configured"
28
+ ? createSyncError(errorCodeFromState(state, options.message), options.message ?? undefined)
29
+ : null,
30
+ lastRefreshFailed: options.lastRefreshFailed ?? state === "error",
31
+ };
32
+ }
33
+ export function summarizeAggregateSync(items) {
34
+ const actionable = items.filter((item) => !isNeutralSyncState(item.state));
35
+ const freshCount = actionable.filter((item) => item.state === "fresh").length;
36
+ const staleCount = actionable.filter((item) => item.state === "stale").length;
37
+ const errorCount = actionable.filter((item) => item.state === "error" || item.state === "locked").length;
38
+ const neutralCount = items.length - actionable.length;
39
+ const lastSuccessAt = latestIso(items.map((item) => item.lastSuccessAt).filter((value) => value !== null));
40
+ const status = actionable.length === 0
41
+ ? "empty"
42
+ : errorCount > 0 && (freshCount > 0 || staleCount > 0)
43
+ ? "partial"
44
+ : errorCount === actionable.length
45
+ ? "error"
46
+ : freshCount > 0 && staleCount > 0
47
+ ? "partial"
48
+ : staleCount === actionable.length
49
+ ? "stale"
50
+ : "ok";
51
+ return {
52
+ status,
53
+ freshCount,
54
+ staleCount,
55
+ errorCount,
56
+ neutralCount,
57
+ lastSuccessAt,
58
+ };
59
+ }
60
+ export function syncStateFromFreshness(freshness) {
61
+ if (freshness === "live") {
62
+ return "fresh";
63
+ }
64
+ if (freshness === "not_configured" || freshness === "unavailable" || freshness === "locked") {
65
+ return freshness;
66
+ }
67
+ return freshness;
68
+ }
69
+ export function isNeutralSyncState(state) {
70
+ return state === "not_configured" || state === "unavailable";
71
+ }
72
+ function errorCodeFromState(state, message) {
73
+ if (state === "not_configured") {
74
+ return "not_configured";
75
+ }
76
+ if (state === "locked") {
77
+ return "local_io";
78
+ }
79
+ if (message !== undefined && message !== null) {
80
+ const normalized = message.toLowerCase();
81
+ if (normalized.includes("permission") || normalized.includes("403")) {
82
+ return "permission_denied";
83
+ }
84
+ if (normalized.includes("401") || normalized.includes("expired")) {
85
+ return "auth_expired";
86
+ }
87
+ if (normalized.includes("429") || normalized.includes("rate limit")) {
88
+ return "rate_limited";
89
+ }
90
+ if (normalized.includes("timeout")) {
91
+ return "timeout";
92
+ }
93
+ if (normalized.includes("network") || normalized.includes("enotfound") || normalized.includes("econn")) {
94
+ return "network";
95
+ }
96
+ }
97
+ return "internal";
98
+ }
99
+ function isRetryableSyncError(code) {
100
+ return code === "rate_limited" || code === "timeout" || code === "network" || code === "upstream_unavailable";
101
+ }
102
+ function isUserActionRequiredSyncError(code) {
103
+ return code === "not_configured" ||
104
+ code === "auth_expired" ||
105
+ code === "permission_denied" ||
106
+ code === "local_source_missing" ||
107
+ code === "local_io";
108
+ }
109
+ function defaultSyncErrorMessage(code) {
110
+ if (code === "auth_expired") {
111
+ return "Login may have expired. Sign in again and retry.";
112
+ }
113
+ if (code === "permission_denied") {
114
+ return "Permission is not sufficient for this read-only check.";
115
+ }
116
+ if (code === "not_configured" || code === "local_source_missing") {
117
+ return "Local source or credential is not configured.";
118
+ }
119
+ if (code === "rate_limited") {
120
+ return "Provider request limit was reached. Retry later.";
121
+ }
122
+ if (code === "schema_changed") {
123
+ return "Provider response shape changed.";
124
+ }
125
+ return "Live sync failed.";
126
+ }
127
+ function sanitizeSyncMessage(value) {
128
+ return value
129
+ .replace(/https:\/\/hooks\.slack\.com\/services\/[A-Za-z0-9/_-]+/g, "[redacted]")
130
+ .replace(/\b(?:sk|sbp|xox[baprs])[-_][A-Za-z0-9_-]+\b/gi, "[redacted]")
131
+ .replace(/\bacct[_-][A-Za-z0-9_-]+\b/gi, "[redacted]")
132
+ .replace(/\b(?:proj|project|invoice)[_-][A-Za-z0-9_-]+\b/gi, "[redacted]")
133
+ .replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi, "[redacted]");
134
+ }
135
+ function latestIso(values) {
136
+ return values.length === 0
137
+ ? null
138
+ : [...values].sort((first, second) => second.localeCompare(first))[0] ?? null;
139
+ }
140
+ //# sourceMappingURL=sync-state.js.map
@@ -0,0 +1,22 @@
1
+ export interface UsageProgressView {
2
+ usedPercent: number | null;
3
+ remainingPercent: number | null;
4
+ warningAtPercent: number;
5
+ criticalAtPercent: number;
6
+ }
7
+ export type UsageProgressSeverity = "info" | "warning" | "critical";
8
+ export declare function usageProgressFromUsedPercent(value: number | null | undefined, thresholds?: {
9
+ warningAtPercent?: number;
10
+ criticalAtPercent?: number;
11
+ }): UsageProgressView;
12
+ export declare function usageProgressFromRemainingPercent(value: number | null | undefined, thresholds?: {
13
+ warningAtPercent?: number;
14
+ criticalAtPercent?: number;
15
+ }): UsageProgressView;
16
+ export declare function usageProgressFromTokens(usedTokens: number | null | undefined, remainingTokens: number | null | undefined, thresholds?: {
17
+ warningAtPercent?: number;
18
+ criticalAtPercent?: number;
19
+ }): UsageProgressView;
20
+ export declare function usageProgressSeverity(progress: UsageProgressView): UsageProgressSeverity;
21
+ export declare function normalizePercent(value: number | null | undefined): number | null;
22
+ //# sourceMappingURL=usage-progress.d.ts.map