@moneysiren/app 0.1.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,81 @@
1
+ import { normalizeCloudflareBillingUsage, } from "./normalize.js";
2
+ export { createCloudflareBillingUsageClient, createStaticCloudflareBillingUsageClient, } from "./client.js";
3
+ export { cloudflareAmountToMinorUnits, normalizeCloudflareBillingUsage, redactedCloudflareAccountId, } from "./normalize.js";
4
+ const EMPTY_CLOUDFLARE_SNAPSHOTS = {
5
+ usage: [],
6
+ billing: [],
7
+ serviceHealth: [],
8
+ costEstimates: [],
9
+ };
10
+ export function createCloudflareBillingUsageConnector(options) {
11
+ return {
12
+ kind: "cloudflare",
13
+ displayName: "Cloudflare Billing/Usage Experimental",
14
+ access: "read-only",
15
+ async collect(context) {
16
+ const collectedAt = context.now().toISOString();
17
+ try {
18
+ const payload = await options.client.fetchBillingUsage();
19
+ const alerts = unavailableSurfaceAlerts(payload.unavailable ?? [], collectedAt);
20
+ return {
21
+ collectedAt,
22
+ status: alerts.length === 0 ? "ok" : "partial",
23
+ snapshots: normalizeCloudflareBillingUsage({
24
+ payload,
25
+ collectedAt,
26
+ }),
27
+ alerts,
28
+ ...(alerts.length === 0 ? {} : { errors: alerts.map((alert) => alert.message) }),
29
+ };
30
+ }
31
+ catch {
32
+ return {
33
+ collectedAt,
34
+ status: "error",
35
+ snapshots: EMPTY_CLOUDFLARE_SNAPSHOTS,
36
+ alerts: [
37
+ {
38
+ provider: "cloudflare",
39
+ createdAt: collectedAt,
40
+ severity: "warning",
41
+ category: "provider-sync",
42
+ title: "Cloudflare billing/usage sync failed",
43
+ message: "Cloudflare billing/usage request failed before normalized snapshots were collected.",
44
+ },
45
+ ],
46
+ errors: ["Cloudflare billing/usage request failed."],
47
+ };
48
+ }
49
+ },
50
+ };
51
+ }
52
+ function unavailableSurfaceAlerts(surfaces, collectedAt) {
53
+ const uniqueSurfaces = new Set(surfaces.map((surface) => surface.surface));
54
+ return [...uniqueSurfaces].sort().map((surface) => ({
55
+ provider: "cloudflare",
56
+ createdAt: collectedAt,
57
+ severity: "warning",
58
+ category: "provider-sync",
59
+ title: unavailableSurfaceTitle(surface),
60
+ message: unavailableSurfaceMessage(surface),
61
+ }));
62
+ }
63
+ function unavailableSurfaceTitle(surface) {
64
+ if (surface === "billable-usage") {
65
+ return "Cloudflare billable usage surface unavailable";
66
+ }
67
+ if (surface === "paygo-usage") {
68
+ return "Cloudflare PayGo usage surface unavailable";
69
+ }
70
+ return "Cloudflare subscriptions surface unavailable";
71
+ }
72
+ function unavailableSurfaceMessage(surface) {
73
+ if (surface === "billable-usage") {
74
+ return "Cloudflare billable usage API was restricted or unavailable; normalized sync continued with available data.";
75
+ }
76
+ if (surface === "paygo-usage") {
77
+ return "Cloudflare PayGo usage API was restricted or unavailable; normalized sync continued with available data.";
78
+ }
79
+ return "Cloudflare subscriptions API was restricted or unavailable; normalized sync continued with available data.";
80
+ }
81
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,113 @@
1
+ declare const CLOUDFLARE_PROVIDER = "cloudflare";
2
+ export interface CloudflareBillingUsagePayload {
3
+ accounts?: readonly CloudflareAccount[];
4
+ billableUsage?: readonly CloudflareBillableUsageRecord[];
5
+ paygoUsage?: readonly CloudflarePaygoUsageRecord[];
6
+ status?: readonly CloudflareStatusSignal[];
7
+ unavailable?: readonly CloudflareUnavailableSurface[];
8
+ }
9
+ export interface CloudflareAccount {
10
+ id?: string;
11
+ name?: string;
12
+ }
13
+ export interface CloudflareBillableUsageRecord {
14
+ BillingAccountId?: string;
15
+ BillingAccountName?: string;
16
+ BillingCurrency?: string;
17
+ BillingPeriodStart?: string;
18
+ BillingPeriodEnd?: string;
19
+ ChargePeriodStart?: string;
20
+ ChargePeriodEnd?: string;
21
+ BilledCost?: number | string;
22
+ Cost?: number | string;
23
+ ConsumedQuantity?: number | string;
24
+ ConsumedUnit?: string;
25
+ PricingQuantity?: number | string;
26
+ PricingUnit?: string;
27
+ ServiceName?: string;
28
+ x_ProductFamilyName?: string;
29
+ x_BillableMetricId?: string;
30
+ x_ZoneId?: string;
31
+ x_ZoneName?: string;
32
+ SubAccountId?: string;
33
+ SubAccountName?: string;
34
+ ChargeCategory?: string;
35
+ ChargeDescription?: string;
36
+ ChargeFrequency?: string;
37
+ }
38
+ export interface CloudflarePaygoUsageRecord {
39
+ BillingAccountId?: string;
40
+ BillingCurrency?: string;
41
+ BillingPeriodStart?: string;
42
+ BillingPeriodEnd?: string;
43
+ ChargePeriodStart?: string;
44
+ ChargePeriodEnd?: string;
45
+ ContractedCost?: number | string;
46
+ CumulatedContractedCost?: number | string;
47
+ ConsumedQuantity?: number | string;
48
+ ConsumedUnit?: string;
49
+ ServiceName?: string;
50
+ ServiceFamilyName?: string;
51
+ }
52
+ export interface CloudflareStatusSignal {
53
+ accountId?: string;
54
+ service?: string;
55
+ status?: string;
56
+ message?: string;
57
+ }
58
+ export interface CloudflareUnavailableSurface {
59
+ surface: "billable-usage" | "paygo-usage" | "subscriptions";
60
+ accountId?: string;
61
+ reason?: string;
62
+ }
63
+ export interface CloudflareNormalizedSnapshotBundle {
64
+ usage: readonly CloudflareUsageSnapshot[];
65
+ billing: readonly CloudflareBillingSnapshot[];
66
+ serviceHealth: readonly CloudflareServiceHealthSnapshot[];
67
+ costEstimates: readonly CloudflareCostEstimate[];
68
+ }
69
+ export interface CloudflareUsageSnapshot {
70
+ provider: typeof CLOUDFLARE_PROVIDER;
71
+ collectedAt: string;
72
+ providerAccountRef: string;
73
+ service: string;
74
+ metric: "billable_quantity";
75
+ unit: string;
76
+ value: number;
77
+ }
78
+ export interface CloudflareBillingSnapshot {
79
+ provider: typeof CLOUDFLARE_PROVIDER;
80
+ collectedAt: string;
81
+ providerAccountRef: string;
82
+ periodStart: string;
83
+ periodEnd: string;
84
+ amountMinor: number;
85
+ currency: string;
86
+ status: "estimated";
87
+ }
88
+ export interface CloudflareServiceHealthSnapshot {
89
+ provider: typeof CLOUDFLARE_PROVIDER;
90
+ collectedAt: string;
91
+ service: string;
92
+ status: "ok" | "degraded" | "down" | "unknown";
93
+ message?: string;
94
+ }
95
+ export interface CloudflareCostEstimate {
96
+ provider: typeof CLOUDFLARE_PROVIDER;
97
+ collectedAt: string;
98
+ providerAccountRef: string;
99
+ periodStart: string;
100
+ periodEnd: string;
101
+ estimatedAmountMinor: number;
102
+ currency: string;
103
+ confidence: "low";
104
+ }
105
+ export interface NormalizeCloudflareBillingUsageInput {
106
+ payload: CloudflareBillingUsagePayload;
107
+ collectedAt: string;
108
+ }
109
+ export declare function normalizeCloudflareBillingUsage(input: NormalizeCloudflareBillingUsageInput): CloudflareNormalizedSnapshotBundle;
110
+ export declare function redactedCloudflareAccountId(accountId: string): string;
111
+ export declare function cloudflareAmountToMinorUnits(amount: number | string): number;
112
+ export {};
113
+ //# sourceMappingURL=normalize.d.ts.map
@@ -0,0 +1,288 @@
1
+ import { createHash } from "node:crypto";
2
+ const CLOUDFLARE_PROVIDER = "cloudflare";
3
+ const ACCOUNT_ID_HASH_PREFIX = "cloudflare-account";
4
+ export function normalizeCloudflareBillingUsage(input) {
5
+ const chargeRecords = normalizeChargeRecords(input.payload);
6
+ return {
7
+ usage: chargeRecords.map((record) => ({
8
+ provider: CLOUDFLARE_PROVIDER,
9
+ collectedAt: input.collectedAt,
10
+ providerAccountRef: record.providerAccountRef,
11
+ service: `${record.service}:${record.providerAccountRef}`,
12
+ metric: "billable_quantity",
13
+ unit: record.unit,
14
+ value: record.quantity,
15
+ })),
16
+ billing: normalizeBilling(chargeRecords, input.collectedAt),
17
+ serviceHealth: normalizeHealth(input.payload, input.collectedAt),
18
+ costEstimates: normalizeCostEstimates(chargeRecords, input.collectedAt),
19
+ };
20
+ }
21
+ export function redactedCloudflareAccountId(accountId) {
22
+ const trimmedAccountId = requireNonBlankString(accountId, "Cloudflare account ID");
23
+ const digest = createHash("sha256").update(`cloudflare:${trimmedAccountId}`).digest("hex").slice(0, 16);
24
+ return `${ACCOUNT_ID_HASH_PREFIX}:${digest}`;
25
+ }
26
+ export function cloudflareAmountToMinorUnits(amount) {
27
+ const rawAmount = typeof amount === "number" ? String(requireFiniteNumber(amount, "Cloudflare cost amount")) : amount;
28
+ const trimmed = rawAmount.trim();
29
+ const sign = trimmed.startsWith("-") ? -1n : 1n;
30
+ const unsigned = sign === -1n ? trimmed.slice(1) : trimmed;
31
+ if (!/^\d+(\.\d+)?$/.test(unsigned)) {
32
+ throw new Error(`Cloudflare currency amount must be a decimal string or finite number: ${rawAmount}`);
33
+ }
34
+ const [wholePart = "0", fractionPart = ""] = unsigned.split(".");
35
+ const minorDigits = `${fractionPart}00`.slice(0, 2);
36
+ const roundingDigit = Number(`${fractionPart}000`.slice(2, 3));
37
+ let minorUnits = BigInt(wholePart) * 100n + BigInt(minorDigits);
38
+ if (roundingDigit >= 5) {
39
+ minorUnits += 1n;
40
+ }
41
+ const signedMinorUnits = minorUnits * sign;
42
+ const asNumber = Number(signedMinorUnits);
43
+ if (!Number.isSafeInteger(asNumber)) {
44
+ throw new Error(`Cloudflare currency amount is outside safe integer range: ${rawAmount}`);
45
+ }
46
+ return asNumber;
47
+ }
48
+ function normalizeChargeRecords(payload) {
49
+ const billableUsage = payload.billableUsage ?? [];
50
+ const paygoUsage = payload.paygoUsage ?? [];
51
+ if (billableUsage.length === 0) {
52
+ return paygoUsage.map(normalizePaygoUsageRecord);
53
+ }
54
+ const billableAccountIds = new Set(billableUsage
55
+ .map((record) => readOptionalNonBlankString(record.BillingAccountId))
56
+ .filter((accountId) => accountId !== undefined));
57
+ const paygoFallbackUsage = paygoUsage.filter((record) => {
58
+ const accountId = readOptionalNonBlankString(record.BillingAccountId);
59
+ return accountId === undefined || !billableAccountIds.has(accountId);
60
+ });
61
+ return [
62
+ ...billableUsage.map(normalizeBillableUsageRecord),
63
+ ...paygoFallbackUsage.map(normalizePaygoUsageRecord),
64
+ ];
65
+ }
66
+ function normalizeBillableUsageRecord(record) {
67
+ const accountRef = redactedCloudflareAccountId(requireNonBlankString(record.BillingAccountId, "BillingAccountId"));
68
+ const service = normalizeServiceName(readOptionalNonBlankString(record.x_ProductFamilyName) ??
69
+ readOptionalNonBlankString(record.ServiceName) ??
70
+ "Cloudflare usage");
71
+ const unit = readOptionalNonBlankString(record.PricingUnit) ??
72
+ readOptionalNonBlankString(record.ConsumedUnit) ??
73
+ "units";
74
+ const currency = readOptionalNonBlankString(record.BillingCurrency)?.toUpperCase();
75
+ const amountMinor = optionalAmountToMinorUnits(record.BilledCost ?? record.Cost);
76
+ return {
77
+ providerAccountRef: accountRef,
78
+ service,
79
+ unit,
80
+ quantity: readOptionalFiniteNumber(record.PricingQuantity, `${service} PricingQuantity`) ??
81
+ readOptionalFiniteNumber(record.ConsumedQuantity, `${service} ConsumedQuantity`) ??
82
+ 0,
83
+ periodStart: normalizePeriodDate(readOptionalNonBlankString(record.ChargePeriodStart) ??
84
+ readOptionalNonBlankString(record.BillingPeriodStart), "Cloudflare billable usage period start"),
85
+ periodEnd: normalizePeriodDate(readOptionalNonBlankString(record.ChargePeriodEnd) ??
86
+ readOptionalNonBlankString(record.BillingPeriodEnd), "Cloudflare billable usage period end"),
87
+ ...(currency === undefined ? {} : { currency }),
88
+ ...(amountMinor === undefined ? {} : { amountMinor }),
89
+ };
90
+ }
91
+ function normalizePaygoUsageRecord(record) {
92
+ const accountRef = redactedCloudflareAccountId(requireNonBlankString(record.BillingAccountId, "BillingAccountId"));
93
+ const service = normalizeServiceName(readOptionalNonBlankString(record.ServiceName) ??
94
+ readOptionalNonBlankString(record.ServiceFamilyName) ??
95
+ "Cloudflare PayGo usage");
96
+ const unit = readOptionalNonBlankString(record.ConsumedUnit) ?? "units";
97
+ const currency = readOptionalNonBlankString(record.BillingCurrency)?.toUpperCase();
98
+ const amountMinor = optionalAmountToMinorUnits(record.ContractedCost);
99
+ return {
100
+ providerAccountRef: accountRef,
101
+ service,
102
+ unit,
103
+ quantity: readOptionalFiniteNumber(record.ConsumedQuantity, `${service} ConsumedQuantity`) ?? 0,
104
+ periodStart: normalizePeriodDate(readOptionalNonBlankString(record.ChargePeriodStart) ??
105
+ readOptionalNonBlankString(record.BillingPeriodStart), "Cloudflare PayGo usage period start"),
106
+ periodEnd: normalizePeriodDate(readOptionalNonBlankString(record.ChargePeriodEnd) ??
107
+ readOptionalNonBlankString(record.BillingPeriodEnd), "Cloudflare PayGo usage period end"),
108
+ ...(currency === undefined ? {} : { currency }),
109
+ ...(amountMinor === undefined ? {} : { amountMinor }),
110
+ };
111
+ }
112
+ function normalizeBilling(records, collectedAt) {
113
+ return aggregateBillingRecords(records).map((record) => ({
114
+ provider: CLOUDFLARE_PROVIDER,
115
+ collectedAt,
116
+ providerAccountRef: record.providerAccountRef,
117
+ periodStart: record.periodStart,
118
+ periodEnd: record.periodEnd,
119
+ amountMinor: record.amountMinor,
120
+ currency: record.currency,
121
+ status: "estimated",
122
+ }));
123
+ }
124
+ function normalizeCostEstimates(records, collectedAt) {
125
+ return aggregateBillingRecords(records).map((record) => ({
126
+ provider: CLOUDFLARE_PROVIDER,
127
+ collectedAt,
128
+ providerAccountRef: record.providerAccountRef,
129
+ periodStart: record.periodStart,
130
+ periodEnd: record.periodEnd,
131
+ estimatedAmountMinor: record.amountMinor,
132
+ currency: record.currency,
133
+ confidence: "low",
134
+ }));
135
+ }
136
+ function aggregateBillingRecords(records) {
137
+ const accumulators = new Map();
138
+ for (const record of records) {
139
+ if (record.amountMinor === undefined || record.currency === undefined) {
140
+ continue;
141
+ }
142
+ const key = [
143
+ record.providerAccountRef,
144
+ record.periodStart,
145
+ record.periodEnd,
146
+ record.currency,
147
+ ].join("|");
148
+ const existing = accumulators.get(key);
149
+ if (existing === undefined) {
150
+ accumulators.set(key, {
151
+ providerAccountRef: record.providerAccountRef,
152
+ periodStart: record.periodStart,
153
+ periodEnd: record.periodEnd,
154
+ currency: record.currency,
155
+ amountMinor: record.amountMinor,
156
+ });
157
+ continue;
158
+ }
159
+ existing.amountMinor += record.amountMinor;
160
+ }
161
+ return [...accumulators.values()].sort((left, right) => `${left.periodStart}:${left.providerAccountRef}:${left.currency}`.localeCompare(`${right.periodStart}:${right.providerAccountRef}:${right.currency}`));
162
+ }
163
+ function normalizeHealth(payload, collectedAt) {
164
+ const snapshots = [];
165
+ const seen = new Set();
166
+ for (const signal of payload.status ?? []) {
167
+ const accountId = readOptionalNonBlankString(signal.accountId);
168
+ const service = normalizeHealthServiceName(readOptionalNonBlankString(signal.service));
169
+ if (accountId === undefined) {
170
+ continue;
171
+ }
172
+ pushHealthSnapshot(snapshots, seen, {
173
+ provider: CLOUDFLARE_PROVIDER,
174
+ collectedAt,
175
+ service: `${service}:${redactedCloudflareAccountId(accountId)}`,
176
+ status: mapCloudflareStatus(readOptionalNonBlankString(signal.status)),
177
+ message: safeStatusMessage(readOptionalNonBlankString(signal.status)),
178
+ });
179
+ }
180
+ for (const unavailable of payload.unavailable ?? []) {
181
+ const accountId = readOptionalNonBlankString(unavailable.accountId);
182
+ if (accountId === undefined) {
183
+ continue;
184
+ }
185
+ pushHealthSnapshot(snapshots, seen, {
186
+ provider: CLOUDFLARE_PROVIDER,
187
+ collectedAt,
188
+ service: `${surfaceServiceName(unavailable.surface)}:${redactedCloudflareAccountId(accountId)}`,
189
+ status: "degraded",
190
+ message: "Cloudflare billing usage API unavailable for this account.",
191
+ });
192
+ }
193
+ return snapshots;
194
+ }
195
+ function pushHealthSnapshot(snapshots, seen, snapshot) {
196
+ if (seen.has(snapshot.service)) {
197
+ return;
198
+ }
199
+ seen.add(snapshot.service);
200
+ snapshots.push(snapshot);
201
+ }
202
+ function normalizeServiceName(serviceName) {
203
+ return serviceName.replaceAll(/\s+/g, " ").trim();
204
+ }
205
+ function normalizeHealthServiceName(serviceName) {
206
+ return serviceName?.replaceAll(/[^a-z0-9]+/gi, "_").replaceAll(/^_+|_+$/g, "").toLowerCase() ??
207
+ "billing_usage_api";
208
+ }
209
+ function surfaceServiceName(surface) {
210
+ if (surface === "billable-usage") {
211
+ return "billable_usage_api";
212
+ }
213
+ if (surface === "paygo-usage") {
214
+ return "paygo_usage_api";
215
+ }
216
+ return "subscriptions_api";
217
+ }
218
+ function mapCloudflareStatus(status) {
219
+ const normalized = status?.trim().toLowerCase();
220
+ if (normalized === "ok" || normalized === "available" || normalized === "healthy" || normalized === "active") {
221
+ return "ok";
222
+ }
223
+ if (normalized === "restricted" ||
224
+ normalized === "unavailable" ||
225
+ normalized === "degraded" ||
226
+ normalized === "limited") {
227
+ return "degraded";
228
+ }
229
+ if (normalized === "down" || normalized === "error" || normalized === "failed") {
230
+ return "down";
231
+ }
232
+ return "unknown";
233
+ }
234
+ function safeStatusMessage(status) {
235
+ const mappedStatus = mapCloudflareStatus(status);
236
+ if (mappedStatus === "ok") {
237
+ return "Cloudflare billing usage API available.";
238
+ }
239
+ if (mappedStatus === "unknown") {
240
+ return "Cloudflare billing usage API status unknown.";
241
+ }
242
+ return "Cloudflare billing usage API unavailable for this account.";
243
+ }
244
+ function normalizePeriodDate(value, context) {
245
+ const trimmed = requireNonBlankString(value, context);
246
+ const isoDate = /^\d{4}-\d{2}-\d{2}/.exec(trimmed)?.[0];
247
+ if (isoDate === undefined) {
248
+ throw new Error(`${context} must start with an ISO date.`);
249
+ }
250
+ return isoDate;
251
+ }
252
+ function optionalAmountToMinorUnits(amount) {
253
+ if (amount === undefined) {
254
+ return undefined;
255
+ }
256
+ return cloudflareAmountToMinorUnits(amount);
257
+ }
258
+ function readOptionalFiniteNumber(value, context) {
259
+ if (value === undefined) {
260
+ return undefined;
261
+ }
262
+ if (typeof value === "number") {
263
+ return requireFiniteNumber(value, context);
264
+ }
265
+ const trimmed = value.trim();
266
+ if (trimmed.length === 0) {
267
+ return undefined;
268
+ }
269
+ return requireFiniteNumber(Number(trimmed), context);
270
+ }
271
+ function requireFiniteNumber(value, context) {
272
+ if (!Number.isFinite(value)) {
273
+ throw new Error(`${context} must be finite.`);
274
+ }
275
+ return value;
276
+ }
277
+ function requireNonBlankString(value, context) {
278
+ const trimmed = value?.trim();
279
+ if (trimmed === undefined || trimmed.length === 0) {
280
+ throw new Error(`${context} must not be blank.`);
281
+ }
282
+ return trimmed;
283
+ }
284
+ function readOptionalNonBlankString(value) {
285
+ const trimmed = value?.trim();
286
+ return trimmed === undefined || trimmed.length === 0 ? undefined : trimmed;
287
+ }
288
+ //# sourceMappingURL=normalize.js.map
@@ -0,0 +1,58 @@
1
+ export type MockProviderSnapshot = {
2
+ provider: "mock";
3
+ collectedAt: string;
4
+ status: "ok";
5
+ };
6
+ export interface MockProviderCollectionContext {
7
+ now(): Date;
8
+ }
9
+ export interface MockProviderConnector {
10
+ kind: "mock";
11
+ displayName: "Mock Provider";
12
+ access: "read-only";
13
+ collect(context: MockProviderCollectionContext): Promise<MockProviderCollectionResult>;
14
+ }
15
+ export interface MockProviderCollectionResult {
16
+ collectedAt: string;
17
+ status: "ok";
18
+ snapshots: {
19
+ usage: readonly MockUsageSnapshot[];
20
+ billing: readonly MockBillingSnapshot[];
21
+ serviceHealth: readonly MockServiceHealthSnapshot[];
22
+ costEstimates: readonly MockCostEstimate[];
23
+ };
24
+ alerts: readonly [];
25
+ }
26
+ export interface MockSnapshotBase {
27
+ provider: "mock";
28
+ collectedAt: string;
29
+ service?: string;
30
+ }
31
+ export interface MockUsageSnapshot extends MockSnapshotBase {
32
+ service: "mock-api";
33
+ metric: "requests";
34
+ unit: "count";
35
+ value: 1200;
36
+ }
37
+ export interface MockBillingSnapshot extends MockSnapshotBase {
38
+ periodStart: string;
39
+ periodEnd: string;
40
+ amountMinor: 1234;
41
+ currency: "USD";
42
+ status: "estimated";
43
+ }
44
+ export interface MockServiceHealthSnapshot extends MockSnapshotBase {
45
+ service: "mock-api";
46
+ region: "local";
47
+ status: "ok";
48
+ }
49
+ export interface MockCostEstimate extends MockSnapshotBase {
50
+ periodStart: string;
51
+ periodEnd: string;
52
+ estimatedAmountMinor: 1500;
53
+ currency: "USD";
54
+ confidence: "high";
55
+ }
56
+ export declare function createMockSnapshot(collectedAt?: string): MockProviderSnapshot;
57
+ export declare function createMockProviderConnector(): MockProviderConnector;
58
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,66 @@
1
+ export function createMockSnapshot(collectedAt = new Date().toISOString()) {
2
+ return {
3
+ provider: "mock",
4
+ collectedAt,
5
+ status: "ok",
6
+ };
7
+ }
8
+ export function createMockProviderConnector() {
9
+ return {
10
+ kind: "mock",
11
+ displayName: "Mock Provider",
12
+ access: "read-only",
13
+ async collect(context) {
14
+ const collectedAt = context.now().toISOString();
15
+ return {
16
+ collectedAt,
17
+ status: "ok",
18
+ snapshots: {
19
+ usage: [
20
+ {
21
+ provider: "mock",
22
+ collectedAt,
23
+ service: "mock-api",
24
+ metric: "requests",
25
+ unit: "count",
26
+ value: 1200,
27
+ },
28
+ ],
29
+ billing: [
30
+ {
31
+ provider: "mock",
32
+ collectedAt,
33
+ periodStart: "2026-06-01",
34
+ periodEnd: "2026-06-30",
35
+ amountMinor: 1234,
36
+ currency: "USD",
37
+ status: "estimated",
38
+ },
39
+ ],
40
+ serviceHealth: [
41
+ {
42
+ provider: "mock",
43
+ collectedAt,
44
+ service: "mock-api",
45
+ region: "local",
46
+ status: "ok",
47
+ },
48
+ ],
49
+ costEstimates: [
50
+ {
51
+ provider: "mock",
52
+ collectedAt,
53
+ periodStart: "2026-06-01",
54
+ periodEnd: "2026-06-30",
55
+ estimatedAmountMinor: 1500,
56
+ currency: "USD",
57
+ confidence: "high",
58
+ },
59
+ ],
60
+ },
61
+ alerts: [],
62
+ };
63
+ },
64
+ };
65
+ }
66
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,55 @@
1
+ import { type OpenAiNormalizedSnapshotBundle, type OpenAiUsageCostsPayload } from "./normalize.js";
2
+ export { normalizeOpenAiUsageCosts, openAiAmountToMinorUnits, type OpenAiBillingSnapshot, type OpenAiCostAmount, type OpenAiCostEstimate, type OpenAiCostsBucket, type OpenAiCostsPage, type OpenAiCostsResult, type OpenAiNormalizedSnapshotBundle, type OpenAiServiceHealthSnapshot, type OpenAiUsageBucket, type OpenAiUsageCostsPayload, type OpenAiUsagePage, type OpenAiUsageResult, type OpenAiUsageSnapshot, } from "./normalize.js";
3
+ export interface OpenAiUsageCostsPeriod {
4
+ startTime: number;
5
+ endTime: number;
6
+ }
7
+ export interface OpenAiUsageCostsClient {
8
+ fetchUsageCosts(period: OpenAiUsageCostsPeriod): Promise<OpenAiUsageCostsPayload>;
9
+ }
10
+ export interface OpenAiUsageCostsRequest {
11
+ path: "/v1/organization/usage/completions" | "/v1/organization/costs";
12
+ query: Record<string, string | readonly string[]>;
13
+ headers: {
14
+ Authorization: string;
15
+ };
16
+ }
17
+ export interface OpenAiUsageCostsTransport {
18
+ getJson(request: OpenAiUsageCostsRequest): Promise<unknown>;
19
+ }
20
+ export interface CreateOpenAiUsageCostsClientOptions {
21
+ adminKey: string;
22
+ transport?: OpenAiUsageCostsTransport;
23
+ }
24
+ export interface OpenAiProviderCollectionContext {
25
+ now(): Date;
26
+ }
27
+ export interface OpenAiProviderConnector {
28
+ kind: "openai";
29
+ displayName: "OpenAI Usage/Costs";
30
+ access: "read-only";
31
+ collect(context: OpenAiProviderCollectionContext): Promise<OpenAiProviderCollectionResult>;
32
+ }
33
+ export interface OpenAiProviderCollectionResult {
34
+ collectedAt: string;
35
+ status: "ok" | "error";
36
+ snapshots: OpenAiNormalizedSnapshotBundle;
37
+ alerts: readonly OpenAiProviderAlert[];
38
+ errors?: readonly string[];
39
+ }
40
+ export interface OpenAiProviderAlert {
41
+ provider: "openai";
42
+ createdAt: string;
43
+ severity: "warning";
44
+ category: "provider-sync";
45
+ title: "OpenAI Usage/Costs sync failed";
46
+ message: "OpenAI Usage/Costs request failed before normalized snapshots were collected.";
47
+ }
48
+ export interface OpenAiUsageCostsConnectorOptions {
49
+ client: OpenAiUsageCostsClient;
50
+ }
51
+ export declare function createOpenAiUsageCostsClient(options: CreateOpenAiUsageCostsClientOptions): OpenAiUsageCostsClient;
52
+ export declare function createStaticOpenAiUsageCostsClient(payload: OpenAiUsageCostsPayload): OpenAiUsageCostsClient;
53
+ export declare function createOpenAiUsageCostsConnector(options: OpenAiUsageCostsConnectorOptions): OpenAiProviderConnector;
54
+ export declare function createCurrentOpenAiUsageCostsPeriod(now: Date): OpenAiUsageCostsPeriod;
55
+ //# sourceMappingURL=index.d.ts.map