@moneysiren/cli 0.1.0-alpha.1 → 0.1.0-alpha.2

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 (34) hide show
  1. package/README.md +2 -2
  2. package/dist/apps/cli/src/cli.d.ts +3 -0
  3. package/dist/apps/cli/src/cli.js +9 -2
  4. package/dist/apps/cli/src/commands/modes.js +13 -11
  5. package/dist/apps/cli/src/commands/runtime.d.ts +2 -0
  6. package/dist/apps/cli/src/commands/runtime.js +167 -0
  7. package/dist/apps/cli/src/desktop-runtime.d.ts +31 -0
  8. package/dist/apps/cli/src/desktop-runtime.js +441 -0
  9. package/dist/apps/cli/src/home.js +16 -0
  10. package/dist/apps/cli/src/postinstall.js +1 -1
  11. package/dist/apps/cli/src/release-installer.d.ts +1 -1
  12. package/dist/apps/cli/src/release-installer.js +2 -2
  13. package/dist/apps/cli/src/slash.js +12 -0
  14. package/dist/apps/cli/src/version.d.ts +1 -1
  15. package/dist/apps/cli/src/version.js +1 -1
  16. package/dist/packages/config/src/load.js +3 -0
  17. package/dist/packages/config/src/schema.d.ts +3 -0
  18. package/dist/packages/config/src/schema.js +3 -0
  19. package/dist/packages/local-api/src/server.js +1 -1
  20. package/dist/packages/view-model/src/hud-model.d.ts +74 -0
  21. package/dist/packages/view-model/src/hud-model.js +295 -0
  22. package/dist/packages/view-model/src/index.d.ts +5 -2
  23. package/dist/packages/view-model/src/index.js +4 -1
  24. package/dist/packages/view-model/src/notification-preferences-model.d.ts +30 -2
  25. package/dist/packages/view-model/src/notification-preferences-model.js +183 -1
  26. package/dist/packages/view-model/src/notification-preferences.d.ts +1 -1
  27. package/dist/packages/view-model/src/notification-preferences.js +1 -1
  28. package/dist/packages/view-model/src/sync-state.d.ts +47 -0
  29. package/dist/packages/view-model/src/sync-state.js +140 -0
  30. package/dist/packages/view-model/src/usage-progress.d.ts +22 -0
  31. package/dist/packages/view-model/src/usage-progress.js +57 -0
  32. package/dist/packages/view-model/src/view-model.d.ts +22 -0
  33. package/dist/packages/view-model/src/view-model.js +142 -0
  34. package/package.json +3 -2
@@ -0,0 +1,295 @@
1
+ import { summarizeAggregateSync, syncViewFromFreshness, } from "./sync-state.js";
2
+ import { usageProgressFromTokens, usageProgressFromUsedPercent, usageProgressSeverity, } from "./usage-progress.js";
3
+ export const CODEX_APP_PROVIDER_KEY = "codex-app";
4
+ export const CODEX_CLI_PROVIDER_KEY = "codex-cli";
5
+ const CODEX_PROVIDER_KEYS = [CODEX_APP_PROVIDER_KEY, CODEX_CLI_PROVIDER_KEY];
6
+ const CREDIT_WARNING_MS = 7 * 24 * 60 * 60 * 1000;
7
+ const CREDIT_CRITICAL_MS = 24 * 60 * 60 * 1000;
8
+ export function buildHudViewModel(todayLive) {
9
+ const items = todayLive.providers.flatMap((provider) => hudItemsForProvider(provider, todayLive.generatedAt));
10
+ const sync = summarizeAggregateSync(items.map((item) => item.sync));
11
+ const warningCount = items.filter((item) => item.riskSeverity === "warning").length;
12
+ const criticalCount = items.filter((item) => item.riskSeverity === "critical").length;
13
+ return {
14
+ generatedAt: todayLive.generatedAt,
15
+ localOnly: true,
16
+ secretsReturned: false,
17
+ dataRevision: dataRevisionFor(todayLive),
18
+ sync,
19
+ risk: {
20
+ severity: criticalCount > 0 ? "critical" : warningCount > 0 ? "warning" : "info",
21
+ warningCount,
22
+ criticalCount,
23
+ },
24
+ items,
25
+ };
26
+ }
27
+ export function filterHudViewModelByWidgets(model, selectedWidgets) {
28
+ const itemsByWidget = new Map();
29
+ for (const item of model.items) {
30
+ for (const widgetKey of hudWidgetKeysForItem(item)) {
31
+ itemsByWidget.set(widgetKey, [...(itemsByWidget.get(widgetKey) ?? []), item]);
32
+ }
33
+ }
34
+ const items = selectedWidgets.flatMap((widgetKey) => itemsByWidget.get(widgetKey) ?? []);
35
+ const sync = summarizeAggregateSync(items.map((item) => item.sync));
36
+ const warningCount = items.filter((item) => item.riskSeverity === "warning").length;
37
+ const criticalCount = items.filter((item) => item.riskSeverity === "critical").length;
38
+ return {
39
+ ...model,
40
+ dataRevision: `${model.dataRevision}#widgets:${selectedWidgets.join(",")}`,
41
+ sync,
42
+ risk: {
43
+ severity: criticalCount > 0 ? "critical" : warningCount > 0 ? "warning" : "info",
44
+ warningCount,
45
+ criticalCount,
46
+ },
47
+ items,
48
+ };
49
+ }
50
+ export function buildCreditPoolFromProvider(provider, generatedAt) {
51
+ return buildCreditPoolsFromProvider(provider, generatedAt)[0] ?? null;
52
+ }
53
+ export function buildCreditPoolsFromProvider(provider, generatedAt) {
54
+ if (!isCodexProviderKey(provider.providerKey)) {
55
+ return [];
56
+ }
57
+ const metrics = provider.metrics;
58
+ const explicitCount = firstMetric(metrics, "usage_reset_credits")?.value ?? null;
59
+ const exactMetrics = metrics.filter((metric) => metric.key === "usage_reset_credit");
60
+ const estimatedMetrics = metrics.filter((metric) => metric.key === "usage_reset_credit_estimate");
61
+ const selectedMetrics = exactMetrics.length > 0 ? exactMetrics : estimatedMetrics;
62
+ const credits = selectedMetrics
63
+ .map((metric, index) => creditItemFromMetric(metric, index, generatedAt))
64
+ .sort(compareCreditItems);
65
+ const activeCreditCount = credits.filter((credit) => credit.status !== "expired").length;
66
+ const availableCount = normalizeCount(explicitCount) ?? activeCreditCount;
67
+ const unresolvedCount = Math.max(0, availableCount - credits.length);
68
+ if (availableCount === 0 && credits.length === 0) {
69
+ return [];
70
+ }
71
+ const nearestExpiryAt = credits
72
+ .map((credit) => credit.expiresAt ?? credit.estimatedEarliestAt)
73
+ .filter((value) => value !== null)
74
+ .sort((left, right) => Date.parse(left) - Date.parse(right))[0] ?? null;
75
+ const accuracy = summarizeCreditAccuracy(credits, selectedMetrics.length > 0 ? selectedMetrics : metrics);
76
+ const sync = syncViewForProvider(provider, generatedAt);
77
+ const invalidKnownCount = explicitCount !== null && activeCreditCount > explicitCount;
78
+ const basePool = {
79
+ kind: "credit_pool",
80
+ providerKey: provider.providerKey,
81
+ availableCount,
82
+ totalEarnedCount: firstMetric(metrics, "usage_reset_credit_total_earned")?.value ?? null,
83
+ credits,
84
+ unresolvedCount,
85
+ nearestExpiryAt,
86
+ accuracy,
87
+ sync: invalidKnownCount
88
+ ? {
89
+ ...sync,
90
+ state: "error",
91
+ error: {
92
+ code: "invalid_data",
93
+ retryable: false,
94
+ userActionRequired: false,
95
+ message: "Reset credit count is lower than the known active credit list.",
96
+ },
97
+ lastRefreshFailed: true,
98
+ }
99
+ : sync,
100
+ riskSeverity: riskFromNearestExpiry(nearestExpiryAt, generatedAt),
101
+ target: {
102
+ type: "service",
103
+ providerKey: provider.providerKey,
104
+ },
105
+ };
106
+ return [
107
+ {
108
+ ...basePool,
109
+ id: `${provider.providerKey}:credit-pool:count`,
110
+ variant: "count",
111
+ },
112
+ {
113
+ ...basePool,
114
+ id: `${provider.providerKey}:credit-pool:expiry`,
115
+ variant: "expiry",
116
+ },
117
+ ];
118
+ }
119
+ function hudItemsForProvider(provider, generatedAt) {
120
+ const quotaItems = [
121
+ quotaItemForProvider(provider, generatedAt, "five_hour", "five_hour_limit_percent", "five_hour_tokens", "five_hour_remaining_tokens"),
122
+ quotaItemForProvider(provider, generatedAt, "weekly", "weekly_limit_percent", "weekly_tokens", "weekly_remaining_tokens"),
123
+ quotaItemForProvider(provider, generatedAt, "context", "context_percent", "context_tokens", null),
124
+ ].filter((item) => item !== null);
125
+ const creditPools = buildCreditPoolsFromProvider(provider, generatedAt);
126
+ return [...quotaItems, ...creditPools];
127
+ }
128
+ function quotaItemForProvider(provider, generatedAt, window, percentMetricKey, usedTokensMetricKey, remainingTokensMetricKey) {
129
+ const usedPercentMetric = firstMetric(provider.metrics, percentMetricKey);
130
+ const usedTokensMetric = firstMetric(provider.metrics, usedTokensMetricKey);
131
+ const remainingTokensMetric = remainingTokensMetricKey === null ? undefined : firstMetric(provider.metrics, remainingTokensMetricKey);
132
+ const progress = usedPercentMetric === undefined
133
+ ? remainingTokensMetric === undefined
134
+ ? null
135
+ : usageProgressFromTokens(usedTokensMetric?.value ?? null, remainingTokensMetric.value)
136
+ : usageProgressFromUsedPercent(usedPercentMetric.value);
137
+ if (progress === null || progress.usedPercent === null) {
138
+ return null;
139
+ }
140
+ const resetAt = remainingTokensMetric?.resetAt ?? usedPercentMetric?.resetAt ?? null;
141
+ const riskSeverity = usageProgressSeverity(progress);
142
+ return {
143
+ kind: "quota",
144
+ id: `${provider.providerKey}:${window}`,
145
+ providerKey: provider.providerKey,
146
+ window,
147
+ progress,
148
+ resetAt,
149
+ sync: syncViewForProvider(provider, generatedAt),
150
+ riskSeverity,
151
+ target: {
152
+ type: "service",
153
+ providerKey: provider.providerKey,
154
+ },
155
+ };
156
+ }
157
+ function hudWidgetKeysForItem(item) {
158
+ if (item.kind === "credit_pool") {
159
+ return item.variant === "count"
160
+ ? ["codex_reset_credit_count"]
161
+ : ["codex_reset_credit_expiry"];
162
+ }
163
+ if (item.window === "five_hour") {
164
+ if (item.providerKey.startsWith("codex-")) {
165
+ return ["codex_five_hour_percent"];
166
+ }
167
+ if (item.providerKey.startsWith("claude-")) {
168
+ return ["claude_five_hour_percent"];
169
+ }
170
+ }
171
+ if (item.window === "weekly") {
172
+ if (item.providerKey.startsWith("codex-")) {
173
+ return ["codex_weekly_percent"];
174
+ }
175
+ if (item.providerKey.startsWith("claude-")) {
176
+ return ["claude_weekly_percent"];
177
+ }
178
+ }
179
+ return [];
180
+ }
181
+ function creditItemFromMetric(metric, index, generatedAt) {
182
+ const accuracy = normalizeCreditAccuracy(metric.accuracy, metric.key);
183
+ const expiresAt = metric.key === "usage_reset_credit" ? metric.resetAt ?? null : null;
184
+ const estimatedEarliestAt = metric.key === "usage_reset_credit_estimate" ? metric.resetAt ?? null : null;
185
+ const estimatedLatestAt = metric.resetAtLatest ?? null;
186
+ const riskTime = expiresAt ?? estimatedEarliestAt;
187
+ return {
188
+ itemKey: metric.itemKey ?? `${metric.key}:${index + 1}`,
189
+ expiresAt,
190
+ estimatedEarliestAt,
191
+ estimatedLatestAt,
192
+ accuracy,
193
+ status: creditStatus(riskTime, generatedAt),
194
+ };
195
+ }
196
+ function syncViewForProvider(provider, generatedAt) {
197
+ return syncViewFromFreshness({
198
+ freshness: provider.freshness,
199
+ checkedAt: provider.checkedAt,
200
+ generatedAt,
201
+ source: provider.providerKey,
202
+ ...(provider.ttlSeconds === undefined ? {} : { ttlSeconds: provider.ttlSeconds }),
203
+ ...(provider.message === undefined ? {} : { message: provider.message }),
204
+ ...(provider.lastAttemptAt === undefined ? {} : { lastAttemptAt: provider.lastAttemptAt }),
205
+ ...(provider.lastSuccessAt === undefined ? {} : { lastSuccessAt: provider.lastSuccessAt }),
206
+ ...(provider.freshUntil === undefined ? {} : { freshUntil: provider.freshUntil }),
207
+ ...(provider.staleUntil === undefined ? {} : { staleUntil: provider.staleUntil }),
208
+ ...(provider.lastRefreshFailed === undefined ? {} : { lastRefreshFailed: provider.lastRefreshFailed }),
209
+ });
210
+ }
211
+ function riskFromNearestExpiry(value, generatedAt) {
212
+ if (value === null) {
213
+ return "info";
214
+ }
215
+ const remainingMs = Date.parse(value) - Date.parse(generatedAt);
216
+ if (!Number.isFinite(remainingMs) || remainingMs <= CREDIT_CRITICAL_MS) {
217
+ return "critical";
218
+ }
219
+ return remainingMs <= CREDIT_WARNING_MS ? "warning" : "info";
220
+ }
221
+ function creditStatus(expiresAt, generatedAt) {
222
+ if (expiresAt === null) {
223
+ return "unknown";
224
+ }
225
+ const remainingMs = Date.parse(expiresAt) - Date.parse(generatedAt);
226
+ if (!Number.isFinite(remainingMs)) {
227
+ return "unknown";
228
+ }
229
+ if (remainingMs <= 0) {
230
+ return "expired";
231
+ }
232
+ return remainingMs <= CREDIT_WARNING_MS ? "expiring_soon" : "active";
233
+ }
234
+ function firstMetric(metrics, key) {
235
+ return metrics.find((metric) => metric.key === key);
236
+ }
237
+ function normalizeCount(value) {
238
+ return value === null || !Number.isFinite(value) || value < 0 ? null : Math.floor(value);
239
+ }
240
+ function summarizeCreditAccuracy(credits, metrics) {
241
+ if (credits.length === 0 && metrics.length === 0) {
242
+ return "unknown";
243
+ }
244
+ if (credits.length > 0 && credits.every((credit) => credit.accuracy === "exact")) {
245
+ return "exact";
246
+ }
247
+ if (credits.some((credit) => credit.accuracy === "bounded")) {
248
+ return "bounded";
249
+ }
250
+ if (credits.some((credit) => credit.accuracy === "estimated")) {
251
+ return "estimated";
252
+ }
253
+ return "unknown";
254
+ }
255
+ function normalizeCreditAccuracy(value, key) {
256
+ if (value === "exact" || value === "estimated" || value === "bounded" || value === "unknown") {
257
+ return value;
258
+ }
259
+ return key === "usage_reset_credit"
260
+ ? "exact"
261
+ : key === "usage_reset_credit_estimate"
262
+ ? "bounded"
263
+ : "unknown";
264
+ }
265
+ function compareCreditItems(left, right) {
266
+ return compareNullableIso(left.expiresAt ?? left.estimatedEarliestAt, right.expiresAt ?? right.estimatedEarliestAt) ||
267
+ left.itemKey.localeCompare(right.itemKey);
268
+ }
269
+ function compareNullableIso(left, right) {
270
+ if (left === null && right === null) {
271
+ return 0;
272
+ }
273
+ if (left === null) {
274
+ return 1;
275
+ }
276
+ if (right === null) {
277
+ return -1;
278
+ }
279
+ return Date.parse(left) - Date.parse(right);
280
+ }
281
+ function dataRevisionFor(todayLive) {
282
+ return [
283
+ todayLive.generatedAt,
284
+ todayLive.providers.map((provider) => [
285
+ provider.providerKey,
286
+ provider.checkedAt ?? "",
287
+ provider.lastSuccessAt ?? "",
288
+ provider.revision ?? "",
289
+ ].join(":")).join("|"),
290
+ ].join("#");
291
+ }
292
+ function isCodexProviderKey(value) {
293
+ return CODEX_PROVIDER_KEYS.includes(value);
294
+ }
295
+ //# sourceMappingURL=hud-model.js.map
@@ -1,3 +1,6 @@
1
- export { buildNotificationDigest, buildOperationsOverview, buildTodayLiveView, buildTrayMenuModel, readNotificationDigest, readOperationsOverview, readTodayLiveView, readTrayMenuModel, type LocalSafeEnvelope, type NotificationDigest, type NotificationDigestItem, type OperationsOverview, type OperationsOverviewAlert, type OperationsOverviewProvider, type ReadNotificationDigestOptions, type ReadOperationsOverviewOptions, type ReadTodayLiveViewOptions, type ReadTrayMenuModelOptions, type TodayLiveProviderInput, type TodayLiveProviderView, type TodayLiveView, type TrayMenuItem, type TrayMenuModel, type ViewModelAlertRecord, type ViewModelBillingSnapshotRecord, type ViewModelCostEstimateRecord, type ViewModelHealthStatus, type ViewModelProviderRecord, type ViewModelReadStore, type ViewModelRiskSeverity, type ViewModelServiceHealthSnapshotRecord, type ViewModelStore, type ViewModelUsageSnapshotRecord, } from "./view-model.js";
2
- export { cloneNotificationPreferences, DEFAULT_LOCAL_CLI_DASHBOARD_METRIC_KEYS, DEFAULT_NOTIFICATION_PREFERENCES, DEFAULT_NOTIFICATION_THRESHOLD_RULES, DEFAULT_SELECTED_NOTIFICATION_WIDGET_KEYS, LOCAL_CLI_DASHBOARD_METRIC_KEYS, NOTIFICATION_WIDGET_KEYS, parseNotificationPreferences, readNotificationPreferencesFile, resolveNotificationPreferencesPath, writeNotificationPreferencesFile, type DashboardDisplayPreferences, type DigestInterval, type LocalCliDashboardMetricKey, type NotificationPreferenceFileOptions, type NotificationPreferences, type NotificationThresholdRule, type NotificationWidgetKey, type ThresholdOperator, } from "./notification-preferences.js";
1
+ export { buildNotificationDigest, buildOperationsOverview, buildTodayLiveView, buildTrayMenuModel, readNotificationDigest, readOperationsOverview, readTodayLiveView, readTrayMenuModel, type LocalSafeEnvelope, type NotificationDigest, type NotificationDigestItem, type OperationsOverview, type OperationsOverviewAlert, type OperationsOverviewProvider, type ReadNotificationDigestOptions, type ReadOperationsOverviewOptions, type ReadTodayLiveViewOptions, type ReadTrayMenuModelOptions, type TodayLiveProviderInput, type TodayLiveProviderView, type TodayLiveMetric, type TodayLiveView, type TrayMenuItem, type TrayMenuModel, type ViewModelAlertRecord, type ViewModelBillingSnapshotRecord, type ViewModelCostEstimateRecord, type ViewModelHealthStatus, type ViewModelProviderRecord, type ViewModelReadStore, type ViewModelRiskSeverity, type ViewModelServiceHealthSnapshotRecord, type ViewModelStore, type ViewModelUsageSnapshotRecord, } from "./view-model.js";
2
+ export { CODEX_APP_PROVIDER_KEY, CODEX_CLI_PROVIDER_KEY, buildCreditPoolFromProvider, buildCreditPoolsFromProvider, buildHudViewModel, filterHudViewModelByWidgets, type CreditAccuracy, type CreditItemView, type CreditPoolView, type HudItemView, type HudViewModel, type QuotaItemView, } from "./hud-model.js";
3
+ export { createSyncError, isNeutralSyncState, summarizeAggregateSync, syncStateFromFreshness, syncViewFromFreshness, type AggregateSyncStatus, type ItemSyncView, type RiskSeverity, type SyncErrorCode, type SyncErrorView, type SyncStateValue, } from "./sync-state.js";
4
+ export { normalizePercent, usageProgressFromRemainingPercent, usageProgressFromTokens, usageProgressFromUsedPercent, usageProgressSeverity, type UsageProgressSeverity, type UsageProgressView, } from "./usage-progress.js";
5
+ export { cloneNotificationPreferences, DEFAULT_LOCAL_CLI_DASHBOARD_METRIC_KEYS, DEFAULT_DASHBOARD_WIDGET_LAYOUTS, DEFAULT_NOTIFICATION_PREFERENCES, DEFAULT_NOTIFICATION_THRESHOLD_RULES, 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, readNotificationPreferencesFile, resolveNotificationPreferencesPath, writeNotificationPreferencesFile, 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.js";
3
6
  //# sourceMappingURL=index.d.ts.map
@@ -1,3 +1,6 @@
1
1
  export { buildNotificationDigest, buildOperationsOverview, buildTodayLiveView, buildTrayMenuModel, readNotificationDigest, readOperationsOverview, readTodayLiveView, readTrayMenuModel, } from "./view-model.js";
2
- export { cloneNotificationPreferences, DEFAULT_LOCAL_CLI_DASHBOARD_METRIC_KEYS, DEFAULT_NOTIFICATION_PREFERENCES, DEFAULT_NOTIFICATION_THRESHOLD_RULES, DEFAULT_SELECTED_NOTIFICATION_WIDGET_KEYS, LOCAL_CLI_DASHBOARD_METRIC_KEYS, NOTIFICATION_WIDGET_KEYS, parseNotificationPreferences, readNotificationPreferencesFile, resolveNotificationPreferencesPath, writeNotificationPreferencesFile, } from "./notification-preferences.js";
2
+ export { CODEX_APP_PROVIDER_KEY, CODEX_CLI_PROVIDER_KEY, buildCreditPoolFromProvider, buildCreditPoolsFromProvider, buildHudViewModel, filterHudViewModelByWidgets, } from "./hud-model.js";
3
+ export { createSyncError, isNeutralSyncState, summarizeAggregateSync, syncStateFromFreshness, syncViewFromFreshness, } from "./sync-state.js";
4
+ export { normalizePercent, usageProgressFromRemainingPercent, usageProgressFromTokens, usageProgressFromUsedPercent, usageProgressSeverity, } from "./usage-progress.js";
5
+ export { cloneNotificationPreferences, DEFAULT_LOCAL_CLI_DASHBOARD_METRIC_KEYS, DEFAULT_DASHBOARD_WIDGET_LAYOUTS, DEFAULT_NOTIFICATION_PREFERENCES, DEFAULT_NOTIFICATION_THRESHOLD_RULES, 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, readNotificationPreferencesFile, resolveNotificationPreferencesPath, writeNotificationPreferencesFile, } from "./notification-preferences.js";
3
6
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,18 @@
1
- export declare const NOTIFICATION_WIDGET_KEYS: readonly ["month_forecast", "today_live_cost", "risk_high_count", "stale_connection_count", "aws_month_forecast", "openai_today_cost", "openai_today_tokens", "claude_five_hour_percent", "claude_weekly_percent", "codex_five_hour_percent", "codex_weekly_percent", "supabase_usage_health", "cloudflare_month_to_date"];
2
- export declare const LOCAL_CLI_DASHBOARD_METRIC_KEYS: readonly ["context_percent", "last_request_tokens", "total_tokens", "five_hour_limit_percent", "weekly_limit_percent", "five_hour_remaining_tokens", "weekly_remaining_tokens", "context_tokens", "input_tokens", "output_tokens", "cache_tokens", "reasoning_tokens", "sessions", "turns", "tool_calls", "log_files"];
1
+ export declare const NOTIFICATION_WIDGET_KEYS: readonly ["month_forecast", "today_live_cost", "risk_high_count", "stale_connection_count", "aws_month_forecast", "openai_today_cost", "openai_today_tokens", "claude_five_hour_percent", "claude_weekly_percent", "codex_five_hour_percent", "codex_weekly_percent", "codex_reset_credit_count", "codex_reset_credit_expiry", "supabase_usage_health", "cloudflare_month_to_date"];
2
+ export declare const LOCAL_CLI_DASHBOARD_METRIC_KEYS: readonly ["context_percent", "last_request_tokens", "total_tokens", "five_hour_limit_percent", "weekly_limit_percent", "five_hour_remaining_tokens", "weekly_remaining_tokens", "usage_reset_credits", "usage_reset_credit_estimate", "context_tokens", "input_tokens", "output_tokens", "cache_tokens", "reasoning_tokens", "sessions", "turns", "tool_calls", "log_files"];
3
+ export declare const DASHBOARD_VIEW_KEYS: readonly ["overview", "today", "forecast", "risks"];
4
+ export declare const DASHBOARD_WIDGET_SIZES: readonly ["compact", "normal", "wide", "full"];
5
+ export declare const DASHBOARD_WIDGET_KEYS_BY_VIEW: {
6
+ readonly overview: readonly ["overview_meta", "overview_metrics", "overview_trend", "overview_grouping", "overview_services", "overview_insights"];
7
+ readonly today: readonly ["today_main", "today_rail"];
8
+ readonly forecast: readonly ["forecast_metrics", "forecast_table", "forecast_breakdown"];
9
+ readonly risks: readonly ["risks_summary", "risks_table"];
10
+ };
3
11
  export type NotificationWidgetKey = (typeof NOTIFICATION_WIDGET_KEYS)[number];
4
12
  export type LocalCliDashboardMetricKey = (typeof LOCAL_CLI_DASHBOARD_METRIC_KEYS)[number];
13
+ export type DashboardViewKey = (typeof DASHBOARD_VIEW_KEYS)[number];
14
+ export type DashboardWidgetSize = (typeof DASHBOARD_WIDGET_SIZES)[number];
15
+ export type DashboardWidgetKey = (typeof DASHBOARD_WIDGET_KEYS_BY_VIEW)[DashboardViewKey][number];
5
16
  export type ThresholdOperator = "gte" | "lte" | "eq";
6
17
  export type DigestInterval = "six-hours" | "daily" | "weekly";
7
18
  export interface NotificationThresholdRule {
@@ -24,8 +35,24 @@ export interface NotificationPreferences {
24
35
  dashboard: DashboardDisplayPreferences;
25
36
  hud: HudPreferences;
26
37
  }
38
+ export interface DashboardBudgetPreferences {
39
+ monthlyBudgetMinor: number | null;
40
+ currency: string;
41
+ warningPercent: number;
42
+ criticalPercent: number;
43
+ }
27
44
  export interface DashboardDisplayPreferences {
28
45
  localCliMetricKeys: readonly LocalCliDashboardMetricKey[];
46
+ budget: DashboardBudgetPreferences;
47
+ widgetLayouts: DashboardWidgetLayoutPreferences;
48
+ }
49
+ export type DashboardWidgetLayoutPreferences = {
50
+ readonly [ViewKey in DashboardViewKey]: readonly DashboardWidgetLayoutItem[];
51
+ };
52
+ export interface DashboardWidgetLayoutItem {
53
+ widgetKey: DashboardWidgetKey;
54
+ visible: boolean;
55
+ size: DashboardWidgetSize;
29
56
  }
30
57
  export interface HudPreferences {
31
58
  alwaysOnTop: boolean;
@@ -40,6 +67,7 @@ export interface NotificationPreferenceFileOptions {
40
67
  }
41
68
  export declare const DEFAULT_SELECTED_NOTIFICATION_WIDGET_KEYS: readonly NotificationWidgetKey[];
42
69
  export declare const DEFAULT_LOCAL_CLI_DASHBOARD_METRIC_KEYS: readonly LocalCliDashboardMetricKey[];
70
+ export declare const DEFAULT_DASHBOARD_WIDGET_LAYOUTS: DashboardWidgetLayoutPreferences;
43
71
  export declare const DEFAULT_NOTIFICATION_THRESHOLD_RULES: readonly NotificationThresholdRule[];
44
72
  export declare const DEFAULT_NOTIFICATION_PREFERENCES: NotificationPreferences;
45
73
  export declare function parseNotificationPreferences(value: unknown): NotificationPreferences;
@@ -10,6 +10,8 @@ export const NOTIFICATION_WIDGET_KEYS = [
10
10
  "claude_weekly_percent",
11
11
  "codex_five_hour_percent",
12
12
  "codex_weekly_percent",
13
+ "codex_reset_credit_count",
14
+ "codex_reset_credit_expiry",
13
15
  "supabase_usage_health",
14
16
  "cloudflare_month_to_date",
15
17
  ];
@@ -21,6 +23,8 @@ export const LOCAL_CLI_DASHBOARD_METRIC_KEYS = [
21
23
  "weekly_limit_percent",
22
24
  "five_hour_remaining_tokens",
23
25
  "weekly_remaining_tokens",
26
+ "usage_reset_credits",
27
+ "usage_reset_credit_estimate",
24
28
  "context_tokens",
25
29
  "input_tokens",
26
30
  "output_tokens",
@@ -31,6 +35,31 @@ export const LOCAL_CLI_DASHBOARD_METRIC_KEYS = [
31
35
  "tool_calls",
32
36
  "log_files",
33
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
+ };
34
63
  export const DEFAULT_SELECTED_NOTIFICATION_WIDGET_KEYS = [
35
64
  "month_forecast",
36
65
  "today_live_cost",
@@ -38,12 +67,40 @@ export const DEFAULT_SELECTED_NOTIFICATION_WIDGET_KEYS = [
38
67
  "stale_connection_count",
39
68
  "openai_today_tokens",
40
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
+ ],
41
91
  ];
42
92
  export const DEFAULT_LOCAL_CLI_DASHBOARD_METRIC_KEYS = [
43
93
  "context_percent",
44
94
  "last_request_tokens",
45
95
  "total_tokens",
96
+ "usage_reset_credits",
46
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
+ };
47
104
  export const DEFAULT_NOTIFICATION_THRESHOLD_RULES = [
48
105
  {
49
106
  widgetKey: "risk_high_count",
@@ -77,6 +134,13 @@ export const DEFAULT_NOTIFICATION_PREFERENCES = {
77
134
  desktopEnabled: false,
78
135
  dashboard: {
79
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
+ },
80
144
  },
81
145
  hud: {
82
146
  alwaysOnTop: true,
@@ -125,6 +189,84 @@ function parseDashboardDisplayPreferences(value) {
125
189
  const record = isRecord(value) ? value : {};
126
190
  return {
127
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,
128
270
  };
129
271
  }
130
272
  function parseHudPreferences(value, fallbackSelectedWidgets = DEFAULT_NOTIFICATION_PREFERENCES.hud.selectedWidgets) {
@@ -162,7 +304,31 @@ function parseSelectedWidgets(value, fallbackSelectedWidgets = DEFAULT_SELECTED_
162
304
  const selected = Array.isArray(value)
163
305
  ? value.filter((item) => typeof item === "string" && widgetKeys.has(item))
164
306
  : [...fallbackSelectedWidgets];
165
- return selected.length === 0 ? [...fallbackSelectedWidgets] : [...new Set(selected)];
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));
166
332
  }
167
333
  function parseThresholdRules(value) {
168
334
  if (!Array.isArray(value)) {
@@ -203,6 +369,22 @@ function parseNonNegativeNumber(value) {
203
369
  }
204
370
  return Math.max(0, value);
205
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
+ }
206
388
  function clampNumber(value, min, max, fallback) {
207
389
  if (typeof value !== "number" || !Number.isFinite(value)) {
208
390
  return fallback;
@@ -1,5 +1,5 @@
1
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_SELECTED_NOTIFICATION_WIDGET_KEYS, LOCAL_CLI_DASHBOARD_METRIC_KEYS, NOTIFICATION_WIDGET_KEYS, parseNotificationPreferences, type DigestInterval, type DashboardDisplayPreferences, type LocalCliDashboardMetricKey, type NotificationPreferenceFileOptions, type NotificationPreferences, type NotificationThresholdRule, type NotificationWidgetKey, type ThresholdOperator, } 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
3
  export declare function readNotificationPreferencesFile(options?: NotificationPreferenceFileOptions): Promise<NotificationPreferences>;
4
4
  export declare function writeNotificationPreferencesFile(preferences: NotificationPreferences, options?: NotificationPreferenceFileOptions): Promise<NotificationPreferences>;
5
5
  export declare function resolveNotificationPreferencesPath(options?: NotificationPreferenceFileOptions): string;
@@ -1,7 +1,7 @@
1
1
  import { mkdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { dirname, isAbsolute, join } from "node:path";
3
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_SELECTED_NOTIFICATION_WIDGET_KEYS, LOCAL_CLI_DASHBOARD_METRIC_KEYS, NOTIFICATION_WIDGET_KEYS, 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
5
  const PREFERENCES_PATH_ENV = "MONEYSIREN_NOTIFICATION_PREFS_PATH";
6
6
  const DEFAULT_PREFERENCES_PATH = ".moneysiren/notification-preferences.json";
7
7
  export async function readNotificationPreferencesFile(options = {}) {