@moneysiren/cli 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +155 -0
  3. package/dist/apps/cli/src/cli.d.ts +56 -0
  4. package/dist/apps/cli/src/cli.js +182 -0
  5. package/dist/apps/cli/src/commands/dashboard.d.ts +3 -0
  6. package/dist/apps/cli/src/commands/dashboard.js +239 -0
  7. package/dist/apps/cli/src/commands/doctor.d.ts +3 -0
  8. package/dist/apps/cli/src/commands/doctor.js +25 -0
  9. package/dist/apps/cli/src/commands/init.d.ts +3 -0
  10. package/dist/apps/cli/src/commands/init.js +18 -0
  11. package/dist/apps/cli/src/commands/install.d.ts +3 -0
  12. package/dist/apps/cli/src/commands/install.js +116 -0
  13. package/dist/apps/cli/src/commands/modes.d.ts +3 -0
  14. package/dist/apps/cli/src/commands/modes.js +65 -0
  15. package/dist/apps/cli/src/commands/notify.d.ts +3 -0
  16. package/dist/apps/cli/src/commands/notify.js +430 -0
  17. package/dist/apps/cli/src/commands/report.d.ts +3 -0
  18. package/dist/apps/cli/src/commands/report.js +206 -0
  19. package/dist/apps/cli/src/commands/runtime.d.ts +5 -0
  20. package/dist/apps/cli/src/commands/runtime.js +133 -0
  21. package/dist/apps/cli/src/commands/shared.d.ts +9 -0
  22. package/dist/apps/cli/src/commands/shared.js +29 -0
  23. package/dist/apps/cli/src/commands/summary.d.ts +3 -0
  24. package/dist/apps/cli/src/commands/summary.js +15 -0
  25. package/dist/apps/cli/src/commands/sync.d.ts +3 -0
  26. package/dist/apps/cli/src/commands/sync.js +393 -0
  27. package/dist/apps/cli/src/commands/theme.d.ts +3 -0
  28. package/dist/apps/cli/src/commands/theme.js +181 -0
  29. package/dist/apps/cli/src/home.d.ts +7 -0
  30. package/dist/apps/cli/src/home.js +97 -0
  31. package/dist/apps/cli/src/index.d.ts +3 -0
  32. package/dist/apps/cli/src/index.js +14 -0
  33. package/dist/apps/cli/src/install-profile.d.ts +35 -0
  34. package/dist/apps/cli/src/install-profile.js +124 -0
  35. package/dist/apps/cli/src/install-selector.d.ts +10 -0
  36. package/dist/apps/cli/src/install-selector.js +66 -0
  37. package/dist/apps/cli/src/interactive.d.ts +3 -0
  38. package/dist/apps/cli/src/interactive.js +32 -0
  39. package/dist/apps/cli/src/postinstall.d.ts +3 -0
  40. package/dist/apps/cli/src/postinstall.js +42 -0
  41. package/dist/apps/cli/src/runtime-adapter.d.ts +24 -0
  42. package/dist/apps/cli/src/runtime-adapter.js +185 -0
  43. package/dist/apps/cli/src/slash.d.ts +15 -0
  44. package/dist/apps/cli/src/slash.js +202 -0
  45. package/dist/apps/cli/src/summary-model.d.ts +51 -0
  46. package/dist/apps/cli/src/summary-model.js +136 -0
  47. package/dist/apps/cli/src/theme.d.ts +18 -0
  48. package/dist/apps/cli/src/theme.js +118 -0
  49. package/dist/packages/config/src/index.d.ts +3 -0
  50. package/dist/packages/config/src/index.js +3 -0
  51. package/dist/packages/config/src/load.d.ts +3 -0
  52. package/dist/packages/config/src/load.js +77 -0
  53. package/dist/packages/config/src/schema.d.ts +46 -0
  54. package/dist/packages/config/src/schema.js +25 -0
  55. package/dist/packages/connectors/aws/src/cost-explorer.d.ts +34 -0
  56. package/dist/packages/connectors/aws/src/cost-explorer.js +43 -0
  57. package/dist/packages/connectors/aws/src/index.d.ts +35 -0
  58. package/dist/packages/connectors/aws/src/index.js +67 -0
  59. package/dist/packages/connectors/aws/src/normalize.d.ts +69 -0
  60. package/dist/packages/connectors/aws/src/normalize.js +141 -0
  61. package/dist/packages/connectors/aws/src/sdk-client.d.ts +6 -0
  62. package/dist/packages/connectors/aws/src/sdk-client.js +21 -0
  63. package/dist/packages/connectors/cloudflare/src/client.d.ts +23 -0
  64. package/dist/packages/connectors/cloudflare/src/client.js +107 -0
  65. package/dist/packages/connectors/cloudflare/src/index.d.ts +33 -0
  66. package/dist/packages/connectors/cloudflare/src/index.js +81 -0
  67. package/dist/packages/connectors/cloudflare/src/normalize.d.ts +113 -0
  68. package/dist/packages/connectors/cloudflare/src/normalize.js +288 -0
  69. package/dist/packages/connectors/mock/src/index.d.ts +58 -0
  70. package/dist/packages/connectors/mock/src/index.js +66 -0
  71. package/dist/packages/connectors/openai/src/index.d.ts +55 -0
  72. package/dist/packages/connectors/openai/src/index.js +169 -0
  73. package/dist/packages/connectors/openai/src/normalize.d.ts +91 -0
  74. package/dist/packages/connectors/openai/src/normalize.js +180 -0
  75. package/dist/packages/connectors/supabase/src/client.d.ts +22 -0
  76. package/dist/packages/connectors/supabase/src/client.js +132 -0
  77. package/dist/packages/connectors/supabase/src/index.d.ts +33 -0
  78. package/dist/packages/connectors/supabase/src/index.js +87 -0
  79. package/dist/packages/connectors/supabase/src/normalize.d.ts +106 -0
  80. package/dist/packages/connectors/supabase/src/normalize.js +266 -0
  81. package/dist/packages/core/src/collector.d.ts +12 -0
  82. package/dist/packages/core/src/collector.js +68 -0
  83. package/dist/packages/core/src/index.d.ts +5 -0
  84. package/dist/packages/core/src/index.js +4 -0
  85. package/dist/packages/core/src/provider.d.ts +18 -0
  86. package/dist/packages/core/src/provider.js +2 -0
  87. package/dist/packages/core/src/risk-engine.d.ts +9 -0
  88. package/dist/packages/core/src/risk-engine.js +4 -0
  89. package/dist/packages/core/src/snapshots.d.ts +49 -0
  90. package/dist/packages/core/src/snapshots.js +9 -0
  91. package/dist/packages/db/src/client.d.ts +11 -0
  92. package/dist/packages/db/src/client.js +14 -0
  93. package/dist/packages/db/src/index.d.ts +6 -0
  94. package/dist/packages/db/src/index.js +6 -0
  95. package/dist/packages/db/src/local-store.d.ts +161 -0
  96. package/dist/packages/db/src/local-store.js +623 -0
  97. package/dist/packages/db/src/migrate.d.ts +17 -0
  98. package/dist/packages/db/src/migrate.js +35 -0
  99. package/dist/packages/db/src/schema.d.ts +5 -0
  100. package/dist/packages/db/src/schema.js +120 -0
  101. package/dist/packages/db/src/sqlite-bin.d.ts +3 -0
  102. package/dist/packages/db/src/sqlite-bin.js +16 -0
  103. package/dist/packages/local-api/src/index.d.ts +2 -0
  104. package/dist/packages/local-api/src/index.js +2 -0
  105. package/dist/packages/local-api/src/server.d.ts +36 -0
  106. package/dist/packages/local-api/src/server.js +310 -0
  107. package/dist/packages/report/src/daily.d.ts +24 -0
  108. package/dist/packages/report/src/daily.js +9 -0
  109. package/dist/packages/report/src/index.d.ts +4 -0
  110. package/dist/packages/report/src/index.js +4 -0
  111. package/dist/packages/report/src/korean.d.ts +3 -0
  112. package/dist/packages/report/src/korean.js +62 -0
  113. package/dist/packages/report/src/slack.d.ts +34 -0
  114. package/dist/packages/report/src/slack.js +134 -0
  115. package/dist/packages/runtime/src/index.d.ts +2 -0
  116. package/dist/packages/runtime/src/index.js +2 -0
  117. package/dist/packages/runtime/src/runtime.d.ts +26 -0
  118. package/dist/packages/runtime/src/runtime.js +182 -0
  119. package/dist/packages/view-model/src/index.d.ts +3 -0
  120. package/dist/packages/view-model/src/index.js +3 -0
  121. package/dist/packages/view-model/src/notification-preferences-model.d.ts +47 -0
  122. package/dist/packages/view-model/src/notification-preferences-model.js +218 -0
  123. package/dist/packages/view-model/src/notification-preferences.d.ts +6 -0
  124. package/dist/packages/view-model/src/notification-preferences.js +36 -0
  125. package/dist/packages/view-model/src/view-model.d.ts +193 -0
  126. package/dist/packages/view-model/src/view-model.js +684 -0
  127. package/package.json +49 -0
  128. package/scripts/postinstall.mjs +11 -0
@@ -0,0 +1,169 @@
1
+ import { normalizeOpenAiUsageCosts, } from "./normalize.js";
2
+ export { normalizeOpenAiUsageCosts, openAiAmountToMinorUnits, } from "./normalize.js";
3
+ const OPENAI_API_BASE_URL = "https://api.openai.com";
4
+ const EMPTY_OPENAI_SNAPSHOTS = {
5
+ usage: [],
6
+ billing: [],
7
+ serviceHealth: [],
8
+ costEstimates: [],
9
+ };
10
+ const defaultOpenAiUsageCostsTransport = {
11
+ async getJson(request) {
12
+ const url = new URL(request.path, OPENAI_API_BASE_URL);
13
+ for (const [key, value] of Object.entries(request.query)) {
14
+ if (isReadonlyStringArray(value)) {
15
+ for (const nestedValue of value) {
16
+ url.searchParams.append(key, nestedValue);
17
+ }
18
+ continue;
19
+ }
20
+ url.searchParams.set(key, value);
21
+ }
22
+ const response = await fetch(url, {
23
+ method: "GET",
24
+ headers: request.headers,
25
+ });
26
+ if (!response.ok) {
27
+ throw new Error(`OpenAI Usage/Costs request failed with status ${response.status}.`);
28
+ }
29
+ return response.json();
30
+ },
31
+ };
32
+ export function createOpenAiUsageCostsClient(options) {
33
+ const adminKey = options.adminKey.trim();
34
+ if (adminKey.length === 0) {
35
+ throw new Error("OPENAI_ADMIN_KEY must not be blank.");
36
+ }
37
+ const transport = options.transport ?? defaultOpenAiUsageCostsTransport;
38
+ return {
39
+ async fetchUsageCosts(period) {
40
+ const headers = {
41
+ Authorization: `Bearer ${adminKey}`,
42
+ };
43
+ const usage = await fetchOpenAiPaginatedPage(transport, {
44
+ path: "/v1/organization/usage/completions",
45
+ query: {
46
+ start_time: String(period.startTime),
47
+ end_time: String(period.endTime),
48
+ bucket_width: "1d",
49
+ group_by: ["model"],
50
+ },
51
+ headers,
52
+ });
53
+ const costs = await fetchOpenAiPaginatedPage(transport, {
54
+ path: "/v1/organization/costs",
55
+ query: {
56
+ start_time: String(period.startTime),
57
+ end_time: String(period.endTime),
58
+ bucket_width: "1d",
59
+ group_by: ["line_item"],
60
+ },
61
+ headers,
62
+ });
63
+ return {
64
+ usage: usage,
65
+ costs: costs,
66
+ };
67
+ },
68
+ };
69
+ }
70
+ async function fetchOpenAiPaginatedPage(transport, request) {
71
+ const data = [];
72
+ const seenPageTokens = new Set();
73
+ let currentRequest = request;
74
+ let lastPage;
75
+ while (true) {
76
+ const page = await transport.getJson(currentRequest);
77
+ data.push(...(page.data ?? []));
78
+ lastPage = page;
79
+ if (page.has_more !== true) {
80
+ break;
81
+ }
82
+ const nextPage = requireOpenAiNextPageToken(page.next_page, request.path);
83
+ if (seenPageTokens.has(nextPage)) {
84
+ throw new Error(`OpenAI Usage/Costs ${request.path} pagination returned a repeated next_page token.`);
85
+ }
86
+ seenPageTokens.add(nextPage);
87
+ currentRequest = {
88
+ ...request,
89
+ query: {
90
+ ...request.query,
91
+ page: nextPage,
92
+ },
93
+ };
94
+ }
95
+ if (lastPage === undefined) {
96
+ throw new Error(`OpenAI Usage/Costs ${request.path} pagination did not return a page.`);
97
+ }
98
+ return {
99
+ ...lastPage,
100
+ data,
101
+ };
102
+ }
103
+ function requireOpenAiNextPageToken(value, path) {
104
+ const nextPage = value?.trim();
105
+ if (nextPage === undefined || nextPage.length === 0) {
106
+ throw new Error(`OpenAI Usage/Costs ${path} response has has_more=true without next_page.`);
107
+ }
108
+ return nextPage;
109
+ }
110
+ export function createStaticOpenAiUsageCostsClient(payload) {
111
+ return {
112
+ async fetchUsageCosts() {
113
+ return payload;
114
+ },
115
+ };
116
+ }
117
+ export function createOpenAiUsageCostsConnector(options) {
118
+ return {
119
+ kind: "openai",
120
+ displayName: "OpenAI Usage/Costs",
121
+ access: "read-only",
122
+ async collect(context) {
123
+ const collectedAt = context.now().toISOString();
124
+ const period = createCurrentOpenAiUsageCostsPeriod(context.now());
125
+ try {
126
+ const payload = await options.client.fetchUsageCosts(period);
127
+ return {
128
+ collectedAt,
129
+ status: "ok",
130
+ snapshots: normalizeOpenAiUsageCosts({
131
+ payload,
132
+ collectedAt,
133
+ }),
134
+ alerts: [],
135
+ };
136
+ }
137
+ catch {
138
+ return {
139
+ collectedAt,
140
+ status: "error",
141
+ snapshots: EMPTY_OPENAI_SNAPSHOTS,
142
+ alerts: [
143
+ {
144
+ provider: "openai",
145
+ createdAt: collectedAt,
146
+ severity: "warning",
147
+ category: "provider-sync",
148
+ title: "OpenAI Usage/Costs sync failed",
149
+ message: "OpenAI Usage/Costs request failed before normalized snapshots were collected.",
150
+ },
151
+ ],
152
+ errors: ["OpenAI Usage/Costs request failed."],
153
+ };
154
+ }
155
+ },
156
+ };
157
+ }
158
+ export function createCurrentOpenAiUsageCostsPeriod(now) {
159
+ const start = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1) / 1000;
160
+ const end = Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 1) / 1000;
161
+ return {
162
+ startTime: start,
163
+ endTime: end,
164
+ };
165
+ }
166
+ function isReadonlyStringArray(value) {
167
+ return Array.isArray(value);
168
+ }
169
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,91 @@
1
+ declare const OPENAI_PROVIDER = "openai";
2
+ export interface OpenAiUsageCostsPayload {
3
+ usage: OpenAiUsagePage;
4
+ costs: OpenAiCostsPage;
5
+ }
6
+ export interface OpenAiUsagePage {
7
+ object?: string;
8
+ data?: readonly OpenAiUsageBucket[];
9
+ has_more?: boolean;
10
+ next_page?: string | null;
11
+ }
12
+ export interface OpenAiUsageBucket {
13
+ object?: string;
14
+ start_time?: number;
15
+ end_time?: number;
16
+ results?: readonly OpenAiUsageResult[];
17
+ }
18
+ export interface OpenAiUsageResult {
19
+ object?: string;
20
+ model?: string;
21
+ input_tokens?: number;
22
+ output_tokens?: number;
23
+ num_model_requests?: number;
24
+ }
25
+ export interface OpenAiCostsPage {
26
+ object?: string;
27
+ data?: readonly OpenAiCostsBucket[];
28
+ has_more?: boolean;
29
+ next_page?: string | null;
30
+ }
31
+ export interface OpenAiCostsBucket {
32
+ object?: string;
33
+ start_time?: number;
34
+ end_time?: number;
35
+ results?: readonly OpenAiCostsResult[];
36
+ }
37
+ export interface OpenAiCostsResult {
38
+ object?: string;
39
+ amount?: OpenAiCostAmount;
40
+ line_item?: string;
41
+ }
42
+ export interface OpenAiCostAmount {
43
+ currency?: string;
44
+ value?: number | string;
45
+ }
46
+ export interface OpenAiNormalizedSnapshotBundle {
47
+ usage: readonly OpenAiUsageSnapshot[];
48
+ billing: readonly OpenAiBillingSnapshot[];
49
+ serviceHealth: readonly OpenAiServiceHealthSnapshot[];
50
+ costEstimates: readonly OpenAiCostEstimate[];
51
+ }
52
+ export interface OpenAiUsageSnapshot {
53
+ provider: typeof OPENAI_PROVIDER;
54
+ collectedAt: string;
55
+ service: string;
56
+ metric: "input_tokens" | "output_tokens" | "model_requests";
57
+ unit: "tokens" | "requests";
58
+ value: number;
59
+ }
60
+ export interface OpenAiBillingSnapshot {
61
+ provider: typeof OPENAI_PROVIDER;
62
+ collectedAt: string;
63
+ periodStart: string;
64
+ periodEnd: string;
65
+ amountMinor: number;
66
+ currency: string;
67
+ status: "estimated";
68
+ }
69
+ export interface OpenAiServiceHealthSnapshot {
70
+ provider: typeof OPENAI_PROVIDER;
71
+ collectedAt: string;
72
+ service: string;
73
+ status: "ok" | "degraded" | "down" | "unknown";
74
+ }
75
+ export interface OpenAiCostEstimate {
76
+ provider: typeof OPENAI_PROVIDER;
77
+ collectedAt: string;
78
+ periodStart: string;
79
+ periodEnd: string;
80
+ estimatedAmountMinor: number;
81
+ currency: string;
82
+ confidence: "medium";
83
+ }
84
+ export interface NormalizeOpenAiUsageCostsInput {
85
+ payload: OpenAiUsageCostsPayload;
86
+ collectedAt: string;
87
+ }
88
+ export declare function normalizeOpenAiUsageCosts(input: NormalizeOpenAiUsageCostsInput): OpenAiNormalizedSnapshotBundle;
89
+ export declare function openAiAmountToMinorUnits(amount: number | string): number;
90
+ export {};
91
+ //# sourceMappingURL=normalize.d.ts.map
@@ -0,0 +1,180 @@
1
+ const OPENAI_PROVIDER = "openai";
2
+ export function normalizeOpenAiUsageCosts(input) {
3
+ return {
4
+ usage: normalizeUsage(input.payload.usage, input.collectedAt),
5
+ billing: normalizeBilling(input.payload.costs, input.collectedAt),
6
+ serviceHealth: [],
7
+ costEstimates: normalizeCostEstimates(input.payload.costs, input.collectedAt),
8
+ };
9
+ }
10
+ export function openAiAmountToMinorUnits(amount) {
11
+ const rawAmount = typeof amount === "number" ? String(requireFiniteNumber(amount, "OpenAI cost amount")) : amount;
12
+ const trimmed = rawAmount.trim();
13
+ const sign = trimmed.startsWith("-") ? -1n : 1n;
14
+ const unsigned = sign === -1n ? trimmed.slice(1) : trimmed;
15
+ if (!/^\d+(\.\d+)?$/.test(unsigned)) {
16
+ throw new Error(`OpenAI currency amount must be a decimal string or finite number: ${rawAmount}`);
17
+ }
18
+ const [wholePart = "0", fractionPart = ""] = unsigned.split(".");
19
+ const minorDigits = `${fractionPart}00`.slice(0, 2);
20
+ const roundingDigit = Number(`${fractionPart}000`.slice(2, 3));
21
+ let minorUnits = BigInt(wholePart) * 100n + BigInt(minorDigits);
22
+ if (roundingDigit >= 5) {
23
+ minorUnits += 1n;
24
+ }
25
+ const signedMinorUnits = minorUnits * sign;
26
+ const asNumber = Number(signedMinorUnits);
27
+ if (!Number.isSafeInteger(asNumber)) {
28
+ throw new Error(`OpenAI currency amount is outside safe integer range: ${rawAmount}`);
29
+ }
30
+ return asNumber;
31
+ }
32
+ function normalizeUsage(page, collectedAt) {
33
+ const usage = [];
34
+ for (const bucket of page.data ?? []) {
35
+ for (const result of bucket.results ?? []) {
36
+ const service = `${usageServicePrefix(result)}:${requireModel(result)}`;
37
+ const inputTokens = readOptionalFiniteNumber(result.input_tokens, `${service} input_tokens`);
38
+ const outputTokens = readOptionalFiniteNumber(result.output_tokens, `${service} output_tokens`);
39
+ const modelRequests = readOptionalFiniteNumber(result.num_model_requests, `${service} num_model_requests`);
40
+ if (inputTokens !== undefined) {
41
+ usage.push({
42
+ provider: OPENAI_PROVIDER,
43
+ collectedAt,
44
+ service,
45
+ metric: "input_tokens",
46
+ unit: "tokens",
47
+ value: inputTokens,
48
+ });
49
+ }
50
+ if (outputTokens !== undefined) {
51
+ usage.push({
52
+ provider: OPENAI_PROVIDER,
53
+ collectedAt,
54
+ service,
55
+ metric: "output_tokens",
56
+ unit: "tokens",
57
+ value: outputTokens,
58
+ });
59
+ }
60
+ if (modelRequests !== undefined) {
61
+ usage.push({
62
+ provider: OPENAI_PROVIDER,
63
+ collectedAt,
64
+ service,
65
+ metric: "model_requests",
66
+ unit: "requests",
67
+ value: modelRequests,
68
+ });
69
+ }
70
+ }
71
+ }
72
+ return usage;
73
+ }
74
+ function normalizeBilling(page, collectedAt) {
75
+ return normalizeCostBuckets(page, collectedAt).map((bucket) => ({
76
+ provider: OPENAI_PROVIDER,
77
+ collectedAt,
78
+ periodStart: bucket.periodStart,
79
+ periodEnd: bucket.periodEnd,
80
+ amountMinor: bucket.amountMinor,
81
+ currency: bucket.currency,
82
+ status: "estimated",
83
+ }));
84
+ }
85
+ function normalizeCostEstimates(page, collectedAt) {
86
+ return normalizeCostBuckets(page, collectedAt).map((bucket) => ({
87
+ provider: OPENAI_PROVIDER,
88
+ collectedAt,
89
+ periodStart: bucket.periodStart,
90
+ periodEnd: bucket.periodEnd,
91
+ estimatedAmountMinor: bucket.amountMinor,
92
+ currency: bucket.currency,
93
+ confidence: "medium",
94
+ }));
95
+ }
96
+ function normalizeCostBuckets(page, collectedAt) {
97
+ const buckets = [];
98
+ for (const bucket of page.data ?? []) {
99
+ const results = bucket.results ?? [];
100
+ if (results.length === 0) {
101
+ continue;
102
+ }
103
+ let amountMinor = 0;
104
+ let currency;
105
+ for (const result of results) {
106
+ const amount = requireAmount(result);
107
+ const resultCurrency = requireCurrency(amount);
108
+ if (currency !== undefined && currency !== resultCurrency) {
109
+ throw new Error("OpenAI costs bucket contains multiple currencies.");
110
+ }
111
+ currency = resultCurrency;
112
+ amountMinor += openAiAmountToMinorUnits(requireAmountValue(amount));
113
+ }
114
+ if (currency === undefined) {
115
+ continue;
116
+ }
117
+ buckets.push({
118
+ periodStart: unixSecondsToDateString(requireUnixSeconds(bucket.start_time, "OpenAI costs bucket start_time")),
119
+ periodEnd: unixSecondsToDateString(requireUnixSeconds(bucket.end_time, "OpenAI costs bucket end_time")),
120
+ amountMinor,
121
+ currency,
122
+ });
123
+ }
124
+ return buckets;
125
+ }
126
+ function usageServicePrefix(result) {
127
+ const objectName = result.object ?? "";
128
+ if (objectName.includes(".embeddings.")) {
129
+ return "embeddings";
130
+ }
131
+ return "completions";
132
+ }
133
+ function requireModel(result) {
134
+ const model = result.model?.trim();
135
+ if (model === undefined || model.length === 0) {
136
+ throw new Error("OpenAI usage result is missing model.");
137
+ }
138
+ return model;
139
+ }
140
+ function requireAmount(result) {
141
+ if (result.amount === undefined) {
142
+ throw new Error("OpenAI costs result is missing amount.");
143
+ }
144
+ return result.amount;
145
+ }
146
+ function requireCurrency(amount) {
147
+ const currency = amount.currency?.trim();
148
+ if (currency === undefined || currency.length === 0) {
149
+ throw new Error("OpenAI costs amount is missing currency.");
150
+ }
151
+ return currency.toUpperCase();
152
+ }
153
+ function requireAmountValue(amount) {
154
+ if (amount.value === undefined) {
155
+ throw new Error("OpenAI costs amount is missing value.");
156
+ }
157
+ return amount.value;
158
+ }
159
+ function readOptionalFiniteNumber(value, context) {
160
+ if (value === undefined) {
161
+ return undefined;
162
+ }
163
+ return requireFiniteNumber(value, context);
164
+ }
165
+ function requireFiniteNumber(value, context) {
166
+ if (!Number.isFinite(value)) {
167
+ throw new Error(`${context} must be a finite number.`);
168
+ }
169
+ return value;
170
+ }
171
+ function requireUnixSeconds(value, context) {
172
+ if (value === undefined || !Number.isFinite(value)) {
173
+ throw new Error(`${context} must be a finite Unix timestamp in seconds.`);
174
+ }
175
+ return value;
176
+ }
177
+ function unixSecondsToDateString(unixSeconds) {
178
+ return new Date(unixSeconds * 1000).toISOString().slice(0, 10);
179
+ }
180
+ //# sourceMappingURL=normalize.js.map
@@ -0,0 +1,22 @@
1
+ import type { SupabaseUsageHealthPayload } from "./normalize.js";
2
+ export interface SupabaseManagementClient {
3
+ fetchUsageHealth(): Promise<SupabaseUsageHealthPayload>;
4
+ }
5
+ export type SupabaseManagementPath = "/v1/projects" | `/v1/projects/${string}/analytics/endpoints/usage.api-counts` | `/v1/projects/${string}/analytics/endpoints/usage.api-requests-count` | `/v1/projects/${string}/health`;
6
+ export interface SupabaseManagementRequest {
7
+ path: SupabaseManagementPath;
8
+ query: Record<string, string | readonly string[]>;
9
+ headers: {
10
+ Authorization: string;
11
+ };
12
+ }
13
+ export interface SupabaseManagementTransport {
14
+ getJson(request: SupabaseManagementRequest): Promise<unknown>;
15
+ }
16
+ export interface CreateSupabaseManagementClientOptions {
17
+ accessToken: string;
18
+ transport?: SupabaseManagementTransport;
19
+ }
20
+ export declare function createSupabaseManagementClient(options: CreateSupabaseManagementClientOptions): SupabaseManagementClient;
21
+ export declare function createStaticSupabaseUsageHealthClient(payload: SupabaseUsageHealthPayload): SupabaseManagementClient;
22
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1,132 @@
1
+ const SUPABASE_API_BASE_URL = "https://api.supabase.com";
2
+ const defaultSupabaseManagementTransport = {
3
+ async getJson(request) {
4
+ const url = new URL(request.path, SUPABASE_API_BASE_URL);
5
+ for (const [key, value] of Object.entries(request.query)) {
6
+ if (isReadonlyStringArray(value)) {
7
+ for (const nestedValue of value) {
8
+ url.searchParams.append(key, nestedValue);
9
+ }
10
+ continue;
11
+ }
12
+ url.searchParams.set(key, value);
13
+ }
14
+ const response = await fetch(url, {
15
+ method: "GET",
16
+ headers: request.headers,
17
+ });
18
+ if (!response.ok) {
19
+ throw new Error(`Supabase Management API request failed with status ${response.status}.`);
20
+ }
21
+ return response.json();
22
+ },
23
+ };
24
+ export function createSupabaseManagementClient(options) {
25
+ const accessToken = options.accessToken.trim();
26
+ if (accessToken.length === 0) {
27
+ throw new Error("SUPABASE_ACCESS_TOKEN must not be blank.");
28
+ }
29
+ const transport = options.transport ?? defaultSupabaseManagementTransport;
30
+ return {
31
+ async fetchUsageHealth() {
32
+ const headers = {
33
+ Authorization: `Bearer ${accessToken}`,
34
+ };
35
+ const projects = coerceProjectList(await transport.getJson({
36
+ path: "/v1/projects",
37
+ query: {},
38
+ headers,
39
+ }));
40
+ const usage = [];
41
+ const health = [];
42
+ const unavailable = [];
43
+ for (const project of projects) {
44
+ const ref = readProjectRef(project);
45
+ if (ref === undefined) {
46
+ continue;
47
+ }
48
+ const encodedRef = encodeURIComponent(ref);
49
+ const usageEntry = {
50
+ ref,
51
+ };
52
+ const apiCounts = await tryFetchSurface(transport, {
53
+ path: `/v1/projects/${encodedRef}/analytics/endpoints/usage.api-counts`,
54
+ query: {},
55
+ headers,
56
+ }, unavailable, "usage.api-counts", ref);
57
+ if (apiCounts !== undefined) {
58
+ usageEntry.apiCounts = apiCounts;
59
+ }
60
+ const apiRequestsCount = await tryFetchSurface(transport, {
61
+ path: `/v1/projects/${encodedRef}/analytics/endpoints/usage.api-requests-count`,
62
+ query: {},
63
+ headers,
64
+ }, unavailable, "usage.api-requests-count", ref);
65
+ if (apiRequestsCount !== undefined) {
66
+ usageEntry.apiRequestsCount = apiRequestsCount;
67
+ }
68
+ if (usageEntry.apiCounts !== undefined || usageEntry.apiRequestsCount !== undefined) {
69
+ usage.push(usageEntry);
70
+ }
71
+ const healthPayload = await tryFetchSurface(transport, {
72
+ path: `/v1/projects/${encodedRef}/health`,
73
+ query: {},
74
+ headers,
75
+ }, unavailable, "health", ref);
76
+ if (healthPayload !== undefined) {
77
+ health.push({
78
+ ref,
79
+ ...healthPayload,
80
+ });
81
+ }
82
+ }
83
+ return {
84
+ projects,
85
+ usage,
86
+ health,
87
+ unavailable,
88
+ };
89
+ },
90
+ };
91
+ }
92
+ export function createStaticSupabaseUsageHealthClient(payload) {
93
+ return {
94
+ async fetchUsageHealth() {
95
+ return payload;
96
+ },
97
+ };
98
+ }
99
+ async function tryFetchSurface(transport, request, unavailable, surface, ref) {
100
+ try {
101
+ return (await transport.getJson(request));
102
+ }
103
+ catch {
104
+ unavailable.push({
105
+ surface,
106
+ ref,
107
+ });
108
+ return undefined;
109
+ }
110
+ }
111
+ function coerceProjectList(value) {
112
+ if (Array.isArray(value)) {
113
+ return value;
114
+ }
115
+ if (isRecord(value) && Array.isArray(value.projects)) {
116
+ return value.projects;
117
+ }
118
+ return [];
119
+ }
120
+ function readProjectRef(project) {
121
+ return readOptionalNonBlankString(project.id) ?? readOptionalNonBlankString(project.ref);
122
+ }
123
+ function readOptionalNonBlankString(value) {
124
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
125
+ }
126
+ function isReadonlyStringArray(value) {
127
+ return Array.isArray(value);
128
+ }
129
+ function isRecord(value) {
130
+ return typeof value === "object" && value !== null && !Array.isArray(value);
131
+ }
132
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1,33 @@
1
+ import { type SupabaseNormalizedSnapshotBundle } from "./normalize.js";
2
+ export { createStaticSupabaseUsageHealthClient, createSupabaseManagementClient, type CreateSupabaseManagementClientOptions, type SupabaseManagementClient, type SupabaseManagementPath, type SupabaseManagementRequest, type SupabaseManagementTransport, } from "./client.js";
3
+ export { normalizeSupabaseUsageHealth, redactedSupabaseProjectRef, type NormalizeSupabaseUsageHealthInput, type SupabaseApiCountsResponse, type SupabaseApiCountsRow, type SupabaseApiRequestsCountResponse, type SupabaseApiRequestsCountRow, type SupabaseBillingSnapshot, type SupabaseCostEstimate, type SupabaseNormalizedSnapshotBundle, type SupabaseProject, type SupabaseProjectHealth, type SupabaseProjectHealthService, type SupabaseProjectUsage, type SupabaseServiceHealthSnapshot, type SupabaseUnavailableSurface, type SupabaseUsageHealthPayload, type SupabaseUsageSnapshot, } from "./normalize.js";
4
+ import type { SupabaseManagementClient } from "./client.js";
5
+ export interface SupabaseProviderCollectionContext {
6
+ now(): Date;
7
+ }
8
+ export interface SupabaseProviderConnector {
9
+ kind: "supabase";
10
+ displayName: "Supabase Usage/Health";
11
+ access: "read-only";
12
+ collect(context: SupabaseProviderCollectionContext): Promise<SupabaseProviderCollectionResult>;
13
+ }
14
+ export interface SupabaseProviderCollectionResult {
15
+ collectedAt: string;
16
+ status: "ok" | "partial" | "error";
17
+ snapshots: SupabaseNormalizedSnapshotBundle;
18
+ alerts: readonly SupabaseProviderAlert[];
19
+ errors?: readonly string[];
20
+ }
21
+ export interface SupabaseProviderAlert {
22
+ provider: "supabase";
23
+ createdAt: string;
24
+ severity: "warning";
25
+ category: "provider-sync";
26
+ title: "Supabase usage/health sync failed" | "Supabase projects surface unavailable" | "Supabase usage.api-counts surface unavailable" | "Supabase usage.api-requests-count surface unavailable" | "Supabase health surface unavailable";
27
+ message: "Supabase usage/health request failed before normalized snapshots were collected." | "Supabase projects request failed before normalized snapshots were collected." | "Supabase usage.api-counts request failed before normalized snapshots were collected." | "Supabase usage.api-requests-count request failed before normalized snapshots were collected." | "Supabase health request failed before normalized snapshots were collected.";
28
+ }
29
+ export interface SupabaseUsageHealthConnectorOptions {
30
+ client: SupabaseManagementClient;
31
+ }
32
+ export declare function createSupabaseUsageHealthConnector(options: SupabaseUsageHealthConnectorOptions): SupabaseProviderConnector;
33
+ //# sourceMappingURL=index.d.ts.map