@aigne/afs-github-cost 1.11.0-beta.12

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.
package/LICENSE.md ADDED
@@ -0,0 +1,26 @@
1
+ # Proprietary License
2
+
3
+ Copyright (c) 2024-2025 ArcBlock, Inc. All Rights Reserved.
4
+
5
+ This software and associated documentation files (the "Software") are proprietary
6
+ and confidential. Unauthorized copying, modification, distribution, or use of
7
+ this Software, via any medium, is strictly prohibited.
8
+
9
+ The Software is provided for internal use only within ArcBlock, Inc. and its
10
+ authorized affiliates.
11
+
12
+ ## No License Granted
13
+
14
+ No license, express or implied, is granted to any party for any purpose.
15
+ All rights are reserved by ArcBlock, Inc.
16
+
17
+ ## Public Artifact Distribution
18
+
19
+ Portions of this Software may be released publicly under separate open-source
20
+ licenses (such as MIT License) through designated public repositories. Such
21
+ public releases are governed by their respective licenses and do not affect
22
+ the proprietary nature of this repository.
23
+
24
+ ## Contact
25
+
26
+ For licensing inquiries, contact: legal@arcblock.io
@@ -0,0 +1,215 @@
1
+
2
+ //#region src/adapter.ts
3
+ const SERVICE_NAME_MAP = {
4
+ actions: "actions",
5
+ packages: "packages",
6
+ codespaces: "codespaces",
7
+ copilot: "copilot",
8
+ "shared-storage": "shared-storage",
9
+ "git-lfs-data": "git-lfs",
10
+ "actions-minutes": "actions",
11
+ "actions-storage": "actions-storage",
12
+ "codespaces-compute": "codespaces-compute",
13
+ "codespaces-prebuild": "codespaces-prebuild",
14
+ "copilot-business": "copilot",
15
+ "copilot-enterprise": "copilot"
16
+ };
17
+ function normalizeServiceName(product) {
18
+ if (!product) return "unknown";
19
+ const lower = product.toLowerCase();
20
+ if (SERVICE_NAME_MAP[lower]) return SERVICE_NAME_MAP[lower];
21
+ return lower.replace(/\s+/g, "-");
22
+ }
23
+ const PLAN_PRICE_MAP = {
24
+ free: 0,
25
+ team: 4,
26
+ "business-plus": 21,
27
+ enterprise: 21
28
+ };
29
+ function planPricePerSeat(planName) {
30
+ return PLAN_PRICE_MAP[planName.toLowerCase()] ?? 0;
31
+ }
32
+ /** Number of days in the month that contains `dateStr` (YYYY-MM-DD). */
33
+ function daysInMonth(dateStr) {
34
+ const { year, month } = parseDateParts(dateStr);
35
+ return new Date(year, month, 0).getDate();
36
+ }
37
+ /** Parse "YYYY-MM-DD" into { year, month, day } */
38
+ function parseDateParts(dateStr) {
39
+ const parts = dateStr.split("-").map(Number);
40
+ return {
41
+ year: parts[0] ?? 0,
42
+ month: parts[1] ?? 0,
43
+ day: parts[2] ?? 0
44
+ };
45
+ }
46
+ /** Iterate over each day in [startDate, endDate) */
47
+ function* eachDay(startDate, endDate) {
48
+ const start = /* @__PURE__ */ new Date(`${startDate}T00:00:00Z`);
49
+ const end = /* @__PURE__ */ new Date(`${endDate}T00:00:00Z`);
50
+ const current = new Date(start);
51
+ while (current < end) {
52
+ yield current.toISOString().slice(0, 10);
53
+ current.setUTCDate(current.getUTCDate() + 1);
54
+ }
55
+ }
56
+ /** Map over items with bounded concurrency. No external dependencies. */
57
+ async function pMap(items, fn, concurrency) {
58
+ const results = new Array(items.length);
59
+ let index = 0;
60
+ async function next() {
61
+ const i = index++;
62
+ if (i >= items.length) return;
63
+ results[i] = await fn(items[i]);
64
+ return next();
65
+ }
66
+ await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => next()));
67
+ return results;
68
+ }
69
+ const BILLING_API_BASE = "https://api.github.com/orgs";
70
+ var GitHubCostAdapter = class {
71
+ cloud = "github";
72
+ config;
73
+ client;
74
+ cachedServices = null;
75
+ constructor(config = {}, client) {
76
+ this.config = config;
77
+ this.client = client;
78
+ }
79
+ async isAvailable() {
80
+ if (!this.config.token || !this.config.org) return false;
81
+ try {
82
+ const client = this.getClient();
83
+ if (!client) return false;
84
+ const { year, month, day } = parseDateParts((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
85
+ const url = `${BILLING_API_BASE}/${this.config.org}/settings/billing/usage?year=${year}&month=${month}&day=${day}`;
86
+ return (await client.get(url, { headers: this.buildHeaders() })).status === 200;
87
+ } catch {
88
+ return false;
89
+ }
90
+ }
91
+ async fetchCosts(options) {
92
+ const client = this.getClient();
93
+ if (!client) throw new Error("GitHub HTTP client not available");
94
+ const org = this.config.org;
95
+ if (!org) throw new Error("Missing org in GitHub config");
96
+ const headers = this.buildHeaders();
97
+ const days = [...eachDay(options.startDate, options.endDate)];
98
+ const orgPlan = await this.fetchOrgPlan(client, org, headers);
99
+ const records = (await pMap(days, async (day) => {
100
+ const { year, month, day: d } = parseDateParts(day);
101
+ const url = `${BILLING_API_BASE}/${org}/settings/billing/usage?year=${year}&month=${month}&day=${d}`;
102
+ const response = await client.get(url, { headers });
103
+ if (response.status === 403) {
104
+ const msg = response.data?.message ?? "";
105
+ if (typeof msg === "string" && msg.toLowerCase().includes("rate limit")) throw new Error(`GitHub API rate limit exceeded. Retry after a few minutes.`);
106
+ throw new Error("Insufficient permissions: requires read:billing scope");
107
+ }
108
+ if (response.status === 404) throw new Error("Organization not found or not on Enhanced Billing Platform");
109
+ if (response.status !== 200) throw new Error(`GitHub API error: status ${response.status}`);
110
+ const items = response.data.usageItems ?? [];
111
+ const records$1 = [];
112
+ for (const item of items) {
113
+ const netAmount = item.netAmount ?? item.grossAmount - (item.discountAmount ?? 0);
114
+ const record = {
115
+ cloud: "github",
116
+ service: normalizeServiceName(item.product),
117
+ date: day,
118
+ amount: netAmount,
119
+ currency: "USD"
120
+ };
121
+ if (item.quantity != null) record.usage = {
122
+ value: item.quantity,
123
+ unit: item.unitType ?? "units"
124
+ };
125
+ records$1.push(record);
126
+ }
127
+ if (orgPlan) {
128
+ const pricePerSeat = planPricePerSeat(orgPlan.planName);
129
+ if (pricePerSeat > 0 && orgPlan.seats > 0) {
130
+ const dim = daysInMonth(day);
131
+ const dailyAmount = orgPlan.seats * pricePerSeat / dim;
132
+ records$1.push({
133
+ cloud: "github",
134
+ service: "seats",
135
+ date: day,
136
+ amount: Math.round(dailyAmount * 1e6) / 1e6,
137
+ currency: "USD",
138
+ usage: {
139
+ value: orgPlan.seats,
140
+ unit: "seats"
141
+ }
142
+ });
143
+ }
144
+ }
145
+ return records$1;
146
+ }, 5)).flat();
147
+ this.cachedServices = [...new Set(records.map((r) => r.service))];
148
+ return records;
149
+ }
150
+ /** Fetch org plan info (plan name + seat count). Returns null on failure. */
151
+ async fetchOrgPlan(client, org, headers) {
152
+ try {
153
+ if (client.getOrgPlan) {
154
+ const resp = await client.getOrgPlan(`https://api.github.com/orgs/${org}`, { headers });
155
+ if (resp.status === 200 && resp.data.plan) return {
156
+ planName: resp.data.plan.name,
157
+ seats: resp.data.plan.seats
158
+ };
159
+ }
160
+ } catch {}
161
+ return null;
162
+ }
163
+ async listServices() {
164
+ if (this.cachedServices) return this.cachedServices;
165
+ return [];
166
+ }
167
+ capabilities() {
168
+ return {
169
+ supportsAmount: true,
170
+ supportsUsage: true,
171
+ supportsTags: false,
172
+ supportsRegion: false,
173
+ supportsAccount: false,
174
+ supportsForecast: false
175
+ };
176
+ }
177
+ getClient() {
178
+ if (this.client) return this.client;
179
+ this.client = {
180
+ async get(url, options) {
181
+ const resp = await fetch(url, {
182
+ method: "GET",
183
+ headers: options.headers
184
+ });
185
+ const data = await resp.json();
186
+ return {
187
+ status: resp.status,
188
+ data
189
+ };
190
+ },
191
+ async getOrgPlan(url, options) {
192
+ const resp = await fetch(url, {
193
+ method: "GET",
194
+ headers: options.headers
195
+ });
196
+ const data = await resp.json();
197
+ return {
198
+ status: resp.status,
199
+ data
200
+ };
201
+ }
202
+ };
203
+ return this.client;
204
+ }
205
+ buildHeaders() {
206
+ return {
207
+ Authorization: `Bearer ${this.config.token}`,
208
+ Accept: "application/json",
209
+ "X-GitHub-Api-Version": "2022-11-28"
210
+ };
211
+ }
212
+ };
213
+
214
+ //#endregion
215
+ exports.GitHubCostAdapter = GitHubCostAdapter;
@@ -0,0 +1,66 @@
1
+ import { AdapterCapabilities, CostAdapter, CostRecord } from "@aigne/afs-cost-base";
2
+
3
+ //#region src/adapter.d.ts
4
+ /** Minimal HTTP client for GitHub Enhanced Billing Platform REST API */
5
+ interface GitHubHTTPClient {
6
+ get(url: string, options: {
7
+ headers: Record<string, string>;
8
+ }): Promise<GitHubBillingResponse>;
9
+ /** Fetch org plan/seat info. Optional — when absent, seat costs are not reported. */
10
+ getOrgPlan?(url: string, options: {
11
+ headers: Record<string, string>;
12
+ }): Promise<GitHubOrgPlanResponse>;
13
+ }
14
+ interface GitHubBillingResponse {
15
+ status: number;
16
+ data: {
17
+ usageItems?: GitHubUsageItem[];
18
+ };
19
+ }
20
+ interface GitHubOrgPlanResponse {
21
+ status: number;
22
+ data: {
23
+ plan?: {
24
+ name: string;
25
+ seats: number;
26
+ filled_seats: number;
27
+ };
28
+ };
29
+ }
30
+ interface GitHubUsageItem {
31
+ date: string;
32
+ product: string;
33
+ sku: string;
34
+ quantity: number;
35
+ unitType: string;
36
+ pricePerUnit: number;
37
+ grossAmount: number;
38
+ discountAmount?: number;
39
+ netAmount: number;
40
+ organizationName?: string;
41
+ }
42
+ interface GitHubCostConfig {
43
+ token?: string;
44
+ org?: string;
45
+ }
46
+ declare class GitHubCostAdapter implements CostAdapter {
47
+ readonly cloud = "github";
48
+ private config;
49
+ private client?;
50
+ private cachedServices;
51
+ constructor(config?: GitHubCostConfig, client?: GitHubHTTPClient);
52
+ isAvailable(): Promise<boolean>;
53
+ fetchCosts(options: {
54
+ startDate: string;
55
+ endDate: string;
56
+ }): Promise<CostRecord[]>;
57
+ /** Fetch org plan info (plan name + seat count). Returns null on failure. */
58
+ private fetchOrgPlan;
59
+ listServices(): Promise<string[]>;
60
+ capabilities(): AdapterCapabilities;
61
+ private getClient;
62
+ private buildHeaders;
63
+ }
64
+ //#endregion
65
+ export { GitHubBillingResponse, GitHubCostAdapter, GitHubCostConfig, GitHubHTTPClient, GitHubOrgPlanResponse, GitHubUsageItem };
66
+ //# sourceMappingURL=adapter.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.cts","names":[],"sources":["../src/adapter.ts"],"mappings":";;;;UAOiB,gBAAA;EACf,GAAA,CAAI,GAAA,UAAa,OAAA;IAAW,OAAA,EAAS,MAAA;EAAA,IAA2B,OAAA,CAAQ,qBAAA;EAAnC;EAErC,UAAA,EACE,GAAA,UACA,OAAA;IAAW,OAAA,EAAS,MAAA;EAAA,IACnB,OAAA,CAAQ,qBAAA;AAAA;AAAA,UAGI,qBAAA;EACf,MAAA;EACA,IAAA;IACE,UAAA,GAAa,eAAA;EAAA;AAAA;AAAA,UAIA,qBAAA;EACf,MAAA;EACA,IAAA;IACE,IAAA;MACE,IAAA;MACA,KAAA;MACA,YAAA;IAAA;EAAA;AAAA;AAAA,UAKW,eAAA;EACf,IAAA;EACA,OAAA;EACA,GAAA;EACA,QAAA;EACA,QAAA;EACA,YAAA;EACA,WAAA;EACA,cAAA;EACA,SAAA;EACA,gBAAA;AAAA;AAAA,UAkGe,gBAAA;EACf,KAAA;EACA,GAAA;AAAA;AAAA,cAKW,iBAAA,YAA6B,WAAA;EAAA,SAC/B,KAAA;EAAA,QACD,MAAA;EAAA,QACA,MAAA;EAAA,QACA,cAAA;cAEI,MAAA,GAAQ,gBAAA,EAAuB,MAAA,GAAS,gBAAA;EAK9C,WAAA,CAAA,GAAe,OAAA;EAsBf,UAAA,CAAW,OAAA;IAAW,SAAA;IAAmB,OAAA;EAAA,IAAoB,OAAA,CAAQ,UAAA;;UAwF7D,YAAA;EAkBR,YAAA,CAAA,GAAgB,OAAA;EAKtB,YAAA,CAAA,GAAgB,mBAAA;EAAA,QAWR,SAAA;EAAA,QA8BA,YAAA;AAAA"}
@@ -0,0 +1,66 @@
1
+ import { AdapterCapabilities, CostAdapter, CostRecord } from "@aigne/afs-cost-base";
2
+
3
+ //#region src/adapter.d.ts
4
+ /** Minimal HTTP client for GitHub Enhanced Billing Platform REST API */
5
+ interface GitHubHTTPClient {
6
+ get(url: string, options: {
7
+ headers: Record<string, string>;
8
+ }): Promise<GitHubBillingResponse>;
9
+ /** Fetch org plan/seat info. Optional — when absent, seat costs are not reported. */
10
+ getOrgPlan?(url: string, options: {
11
+ headers: Record<string, string>;
12
+ }): Promise<GitHubOrgPlanResponse>;
13
+ }
14
+ interface GitHubBillingResponse {
15
+ status: number;
16
+ data: {
17
+ usageItems?: GitHubUsageItem[];
18
+ };
19
+ }
20
+ interface GitHubOrgPlanResponse {
21
+ status: number;
22
+ data: {
23
+ plan?: {
24
+ name: string;
25
+ seats: number;
26
+ filled_seats: number;
27
+ };
28
+ };
29
+ }
30
+ interface GitHubUsageItem {
31
+ date: string;
32
+ product: string;
33
+ sku: string;
34
+ quantity: number;
35
+ unitType: string;
36
+ pricePerUnit: number;
37
+ grossAmount: number;
38
+ discountAmount?: number;
39
+ netAmount: number;
40
+ organizationName?: string;
41
+ }
42
+ interface GitHubCostConfig {
43
+ token?: string;
44
+ org?: string;
45
+ }
46
+ declare class GitHubCostAdapter implements CostAdapter {
47
+ readonly cloud = "github";
48
+ private config;
49
+ private client?;
50
+ private cachedServices;
51
+ constructor(config?: GitHubCostConfig, client?: GitHubHTTPClient);
52
+ isAvailable(): Promise<boolean>;
53
+ fetchCosts(options: {
54
+ startDate: string;
55
+ endDate: string;
56
+ }): Promise<CostRecord[]>;
57
+ /** Fetch org plan info (plan name + seat count). Returns null on failure. */
58
+ private fetchOrgPlan;
59
+ listServices(): Promise<string[]>;
60
+ capabilities(): AdapterCapabilities;
61
+ private getClient;
62
+ private buildHeaders;
63
+ }
64
+ //#endregion
65
+ export { GitHubBillingResponse, GitHubCostAdapter, GitHubCostConfig, GitHubHTTPClient, GitHubOrgPlanResponse, GitHubUsageItem };
66
+ //# sourceMappingURL=adapter.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.mts","names":[],"sources":["../src/adapter.ts"],"mappings":";;;;UAOiB,gBAAA;EACf,GAAA,CAAI,GAAA,UAAa,OAAA;IAAW,OAAA,EAAS,MAAA;EAAA,IAA2B,OAAA,CAAQ,qBAAA;EAAnC;EAErC,UAAA,EACE,GAAA,UACA,OAAA;IAAW,OAAA,EAAS,MAAA;EAAA,IACnB,OAAA,CAAQ,qBAAA;AAAA;AAAA,UAGI,qBAAA;EACf,MAAA;EACA,IAAA;IACE,UAAA,GAAa,eAAA;EAAA;AAAA;AAAA,UAIA,qBAAA;EACf,MAAA;EACA,IAAA;IACE,IAAA;MACE,IAAA;MACA,KAAA;MACA,YAAA;IAAA;EAAA;AAAA;AAAA,UAKW,eAAA;EACf,IAAA;EACA,OAAA;EACA,GAAA;EACA,QAAA;EACA,QAAA;EACA,YAAA;EACA,WAAA;EACA,cAAA;EACA,SAAA;EACA,gBAAA;AAAA;AAAA,UAkGe,gBAAA;EACf,KAAA;EACA,GAAA;AAAA;AAAA,cAKW,iBAAA,YAA6B,WAAA;EAAA,SAC/B,KAAA;EAAA,QACD,MAAA;EAAA,QACA,MAAA;EAAA,QACA,cAAA;cAEI,MAAA,GAAQ,gBAAA,EAAuB,MAAA,GAAS,gBAAA;EAK9C,WAAA,CAAA,GAAe,OAAA;EAsBf,UAAA,CAAW,OAAA;IAAW,SAAA;IAAmB,OAAA;EAAA,IAAoB,OAAA,CAAQ,UAAA;;UAwF7D,YAAA;EAkBR,YAAA,CAAA,GAAgB,OAAA;EAKtB,YAAA,CAAA,GAAgB,mBAAA;EAAA,QAWR,SAAA;EAAA,QA8BA,YAAA;AAAA"}
@@ -0,0 +1,215 @@
1
+ //#region src/adapter.ts
2
+ const SERVICE_NAME_MAP = {
3
+ actions: "actions",
4
+ packages: "packages",
5
+ codespaces: "codespaces",
6
+ copilot: "copilot",
7
+ "shared-storage": "shared-storage",
8
+ "git-lfs-data": "git-lfs",
9
+ "actions-minutes": "actions",
10
+ "actions-storage": "actions-storage",
11
+ "codespaces-compute": "codespaces-compute",
12
+ "codespaces-prebuild": "codespaces-prebuild",
13
+ "copilot-business": "copilot",
14
+ "copilot-enterprise": "copilot"
15
+ };
16
+ function normalizeServiceName(product) {
17
+ if (!product) return "unknown";
18
+ const lower = product.toLowerCase();
19
+ if (SERVICE_NAME_MAP[lower]) return SERVICE_NAME_MAP[lower];
20
+ return lower.replace(/\s+/g, "-");
21
+ }
22
+ const PLAN_PRICE_MAP = {
23
+ free: 0,
24
+ team: 4,
25
+ "business-plus": 21,
26
+ enterprise: 21
27
+ };
28
+ function planPricePerSeat(planName) {
29
+ return PLAN_PRICE_MAP[planName.toLowerCase()] ?? 0;
30
+ }
31
+ /** Number of days in the month that contains `dateStr` (YYYY-MM-DD). */
32
+ function daysInMonth(dateStr) {
33
+ const { year, month } = parseDateParts(dateStr);
34
+ return new Date(year, month, 0).getDate();
35
+ }
36
+ /** Parse "YYYY-MM-DD" into { year, month, day } */
37
+ function parseDateParts(dateStr) {
38
+ const parts = dateStr.split("-").map(Number);
39
+ return {
40
+ year: parts[0] ?? 0,
41
+ month: parts[1] ?? 0,
42
+ day: parts[2] ?? 0
43
+ };
44
+ }
45
+ /** Iterate over each day in [startDate, endDate) */
46
+ function* eachDay(startDate, endDate) {
47
+ const start = /* @__PURE__ */ new Date(`${startDate}T00:00:00Z`);
48
+ const end = /* @__PURE__ */ new Date(`${endDate}T00:00:00Z`);
49
+ const current = new Date(start);
50
+ while (current < end) {
51
+ yield current.toISOString().slice(0, 10);
52
+ current.setUTCDate(current.getUTCDate() + 1);
53
+ }
54
+ }
55
+ /** Map over items with bounded concurrency. No external dependencies. */
56
+ async function pMap(items, fn, concurrency) {
57
+ const results = new Array(items.length);
58
+ let index = 0;
59
+ async function next() {
60
+ const i = index++;
61
+ if (i >= items.length) return;
62
+ results[i] = await fn(items[i]);
63
+ return next();
64
+ }
65
+ await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => next()));
66
+ return results;
67
+ }
68
+ const BILLING_API_BASE = "https://api.github.com/orgs";
69
+ var GitHubCostAdapter = class {
70
+ cloud = "github";
71
+ config;
72
+ client;
73
+ cachedServices = null;
74
+ constructor(config = {}, client) {
75
+ this.config = config;
76
+ this.client = client;
77
+ }
78
+ async isAvailable() {
79
+ if (!this.config.token || !this.config.org) return false;
80
+ try {
81
+ const client = this.getClient();
82
+ if (!client) return false;
83
+ const { year, month, day } = parseDateParts((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
84
+ const url = `${BILLING_API_BASE}/${this.config.org}/settings/billing/usage?year=${year}&month=${month}&day=${day}`;
85
+ return (await client.get(url, { headers: this.buildHeaders() })).status === 200;
86
+ } catch {
87
+ return false;
88
+ }
89
+ }
90
+ async fetchCosts(options) {
91
+ const client = this.getClient();
92
+ if (!client) throw new Error("GitHub HTTP client not available");
93
+ const org = this.config.org;
94
+ if (!org) throw new Error("Missing org in GitHub config");
95
+ const headers = this.buildHeaders();
96
+ const days = [...eachDay(options.startDate, options.endDate)];
97
+ const orgPlan = await this.fetchOrgPlan(client, org, headers);
98
+ const records = (await pMap(days, async (day) => {
99
+ const { year, month, day: d } = parseDateParts(day);
100
+ const url = `${BILLING_API_BASE}/${org}/settings/billing/usage?year=${year}&month=${month}&day=${d}`;
101
+ const response = await client.get(url, { headers });
102
+ if (response.status === 403) {
103
+ const msg = response.data?.message ?? "";
104
+ if (typeof msg === "string" && msg.toLowerCase().includes("rate limit")) throw new Error(`GitHub API rate limit exceeded. Retry after a few minutes.`);
105
+ throw new Error("Insufficient permissions: requires read:billing scope");
106
+ }
107
+ if (response.status === 404) throw new Error("Organization not found or not on Enhanced Billing Platform");
108
+ if (response.status !== 200) throw new Error(`GitHub API error: status ${response.status}`);
109
+ const items = response.data.usageItems ?? [];
110
+ const records$1 = [];
111
+ for (const item of items) {
112
+ const netAmount = item.netAmount ?? item.grossAmount - (item.discountAmount ?? 0);
113
+ const record = {
114
+ cloud: "github",
115
+ service: normalizeServiceName(item.product),
116
+ date: day,
117
+ amount: netAmount,
118
+ currency: "USD"
119
+ };
120
+ if (item.quantity != null) record.usage = {
121
+ value: item.quantity,
122
+ unit: item.unitType ?? "units"
123
+ };
124
+ records$1.push(record);
125
+ }
126
+ if (orgPlan) {
127
+ const pricePerSeat = planPricePerSeat(orgPlan.planName);
128
+ if (pricePerSeat > 0 && orgPlan.seats > 0) {
129
+ const dim = daysInMonth(day);
130
+ const dailyAmount = orgPlan.seats * pricePerSeat / dim;
131
+ records$1.push({
132
+ cloud: "github",
133
+ service: "seats",
134
+ date: day,
135
+ amount: Math.round(dailyAmount * 1e6) / 1e6,
136
+ currency: "USD",
137
+ usage: {
138
+ value: orgPlan.seats,
139
+ unit: "seats"
140
+ }
141
+ });
142
+ }
143
+ }
144
+ return records$1;
145
+ }, 5)).flat();
146
+ this.cachedServices = [...new Set(records.map((r) => r.service))];
147
+ return records;
148
+ }
149
+ /** Fetch org plan info (plan name + seat count). Returns null on failure. */
150
+ async fetchOrgPlan(client, org, headers) {
151
+ try {
152
+ if (client.getOrgPlan) {
153
+ const resp = await client.getOrgPlan(`https://api.github.com/orgs/${org}`, { headers });
154
+ if (resp.status === 200 && resp.data.plan) return {
155
+ planName: resp.data.plan.name,
156
+ seats: resp.data.plan.seats
157
+ };
158
+ }
159
+ } catch {}
160
+ return null;
161
+ }
162
+ async listServices() {
163
+ if (this.cachedServices) return this.cachedServices;
164
+ return [];
165
+ }
166
+ capabilities() {
167
+ return {
168
+ supportsAmount: true,
169
+ supportsUsage: true,
170
+ supportsTags: false,
171
+ supportsRegion: false,
172
+ supportsAccount: false,
173
+ supportsForecast: false
174
+ };
175
+ }
176
+ getClient() {
177
+ if (this.client) return this.client;
178
+ this.client = {
179
+ async get(url, options) {
180
+ const resp = await fetch(url, {
181
+ method: "GET",
182
+ headers: options.headers
183
+ });
184
+ const data = await resp.json();
185
+ return {
186
+ status: resp.status,
187
+ data
188
+ };
189
+ },
190
+ async getOrgPlan(url, options) {
191
+ const resp = await fetch(url, {
192
+ method: "GET",
193
+ headers: options.headers
194
+ });
195
+ const data = await resp.json();
196
+ return {
197
+ status: resp.status,
198
+ data
199
+ };
200
+ }
201
+ };
202
+ return this.client;
203
+ }
204
+ buildHeaders() {
205
+ return {
206
+ Authorization: `Bearer ${this.config.token}`,
207
+ Accept: "application/json",
208
+ "X-GitHub-Api-Version": "2022-11-28"
209
+ };
210
+ }
211
+ };
212
+
213
+ //#endregion
214
+ export { GitHubCostAdapter };
215
+ //# sourceMappingURL=adapter.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.mjs","names":["records"],"sources":["../src/adapter.ts"],"sourcesContent":["import type { AdapterCapabilities, CostAdapter, CostRecord } from \"@aigne/afs-cost-base\";\n\n// ---------------------------------------------------------------------------\n// HTTP client interface (minimal subset for DI)\n// ---------------------------------------------------------------------------\n\n/** Minimal HTTP client for GitHub Enhanced Billing Platform REST API */\nexport interface GitHubHTTPClient {\n get(url: string, options: { headers: Record<string, string> }): Promise<GitHubBillingResponse>;\n /** Fetch org plan/seat info. Optional — when absent, seat costs are not reported. */\n getOrgPlan?(\n url: string,\n options: { headers: Record<string, string> },\n ): Promise<GitHubOrgPlanResponse>;\n}\n\nexport interface GitHubBillingResponse {\n status: number;\n data: {\n usageItems?: GitHubUsageItem[];\n };\n}\n\nexport interface GitHubOrgPlanResponse {\n status: number;\n data: {\n plan?: {\n name: string;\n seats: number;\n filled_seats: number;\n };\n };\n}\n\nexport interface GitHubUsageItem {\n date: string;\n product: string;\n sku: string;\n quantity: number;\n unitType: string;\n pricePerUnit: number;\n grossAmount: number;\n discountAmount?: number;\n netAmount: number;\n organizationName?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Service name normalization\n// ---------------------------------------------------------------------------\n\nconst SERVICE_NAME_MAP: Record<string, string> = {\n actions: \"actions\",\n packages: \"packages\",\n codespaces: \"codespaces\",\n copilot: \"copilot\",\n \"shared-storage\": \"shared-storage\",\n \"git-lfs-data\": \"git-lfs\",\n \"actions-minutes\": \"actions\",\n \"actions-storage\": \"actions-storage\",\n \"codespaces-compute\": \"codespaces-compute\",\n \"codespaces-prebuild\": \"codespaces-prebuild\",\n \"copilot-business\": \"copilot\",\n \"copilot-enterprise\": \"copilot\",\n};\n\nfunction normalizeServiceName(product: string): string {\n if (!product) return \"unknown\";\n const lower = product.toLowerCase();\n if (SERVICE_NAME_MAP[lower]) return SERVICE_NAME_MAP[lower];\n // Fallback: kebab-case\n return lower.replace(/\\s+/g, \"-\");\n}\n\n// ---------------------------------------------------------------------------\n// Plan pricing (USD per seat per month)\n// ---------------------------------------------------------------------------\n\nconst PLAN_PRICE_MAP: Record<string, number> = {\n free: 0,\n team: 4,\n \"business-plus\": 21,\n enterprise: 21,\n};\n\nfunction planPricePerSeat(planName: string): number {\n return PLAN_PRICE_MAP[planName.toLowerCase()] ?? 0;\n}\n\n/** Number of days in the month that contains `dateStr` (YYYY-MM-DD). */\nfunction daysInMonth(dateStr: string): number {\n const { year, month } = parseDateParts(dateStr);\n return new Date(year, month, 0).getDate(); // month is 1-based here, Date(y,m,0) gives last day of prev month\n}\n\n// ---------------------------------------------------------------------------\n// Date helpers\n// ---------------------------------------------------------------------------\n\n/** Parse \"YYYY-MM-DD\" into { year, month, day } */\nfunction parseDateParts(dateStr: string): { year: number; month: number; day: number } {\n const parts = dateStr.split(\"-\").map(Number);\n return { year: parts[0] ?? 0, month: parts[1] ?? 0, day: parts[2] ?? 0 };\n}\n\n/** Iterate over each day in [startDate, endDate) */\nfunction* eachDay(startDate: string, endDate: string): Generator<string> {\n const start = new Date(`${startDate}T00:00:00Z`);\n const end = new Date(`${endDate}T00:00:00Z`);\n const current = new Date(start);\n while (current < end) {\n yield current.toISOString().slice(0, 10);\n current.setUTCDate(current.getUTCDate() + 1);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Concurrency helper\n// ---------------------------------------------------------------------------\n\n/** Map over items with bounded concurrency. No external dependencies. */\nasync function pMap<T, R>(\n items: T[],\n fn: (item: T) => Promise<R>,\n concurrency: number,\n): Promise<R[]> {\n const results: R[] = new Array(items.length);\n let index = 0;\n async function next(): Promise<void> {\n const i = index++;\n if (i >= items.length) return;\n results[i] = await fn(items[i]!);\n return next();\n }\n await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => next()));\n return results;\n}\n\n// ---------------------------------------------------------------------------\n// GitHubCostAdapter\n// ---------------------------------------------------------------------------\n\nexport interface GitHubCostConfig {\n token?: string;\n org?: string;\n}\n\nconst BILLING_API_BASE = \"https://api.github.com/orgs\";\n\nexport class GitHubCostAdapter implements CostAdapter {\n readonly cloud = \"github\";\n private config: GitHubCostConfig;\n private client?: GitHubHTTPClient;\n private cachedServices: string[] | null = null;\n\n constructor(config: GitHubCostConfig = {}, client?: GitHubHTTPClient) {\n this.config = config;\n this.client = client;\n }\n\n async isAvailable(): Promise<boolean> {\n if (!this.config.token || !this.config.org) return false;\n\n try {\n const client = this.getClient();\n if (!client) return false;\n\n // Check org billing access with today's date\n const today = new Date().toISOString().slice(0, 10);\n const { year, month, day } = parseDateParts(today);\n const url = `${BILLING_API_BASE}/${this.config.org}/settings/billing/usage?year=${year}&month=${month}&day=${day}`;\n\n const response = await client.get(url, {\n headers: this.buildHeaders(),\n });\n\n return response.status === 200;\n } catch {\n return false;\n }\n }\n\n async fetchCosts(options: { startDate: string; endDate: string }): Promise<CostRecord[]> {\n const client = this.getClient();\n if (!client) throw new Error(\"GitHub HTTP client not available\");\n\n const org = this.config.org;\n if (!org) throw new Error(\"Missing org in GitHub config\");\n\n const headers = this.buildHeaders();\n const days = [...eachDay(options.startDate, options.endDate)];\n\n // Fetch org plan info for seat costs (non-blocking — skip on failure)\n const orgPlan = await this.fetchOrgPlan(client, org, headers);\n\n const dayResults = await pMap(\n days,\n async (day) => {\n const { year, month, day: d } = parseDateParts(day);\n const url = `${BILLING_API_BASE}/${org}/settings/billing/usage?year=${year}&month=${month}&day=${d}`;\n\n const response = await client.get(url, { headers });\n\n if (response.status === 403) {\n const msg = (response.data as Record<string, unknown>)?.message ?? \"\";\n if (typeof msg === \"string\" && msg.toLowerCase().includes(\"rate limit\")) {\n throw new Error(`GitHub API rate limit exceeded. Retry after a few minutes.`);\n }\n throw new Error(\"Insufficient permissions: requires read:billing scope\");\n }\n\n if (response.status === 404) {\n throw new Error(\"Organization not found or not on Enhanced Billing Platform\");\n }\n\n if (response.status !== 200) {\n throw new Error(`GitHub API error: status ${response.status}`);\n }\n\n const items = response.data.usageItems ?? [];\n const records: CostRecord[] = [];\n for (const item of items) {\n const netAmount = item.netAmount ?? item.grossAmount - (item.discountAmount ?? 0);\n\n const record: CostRecord = {\n cloud: \"github\",\n service: normalizeServiceName(item.product),\n date: day,\n amount: netAmount,\n currency: \"USD\",\n };\n\n if (item.quantity != null) {\n record.usage = {\n value: item.quantity,\n unit: item.unitType ?? \"units\",\n };\n }\n\n records.push(record);\n }\n\n // Add seat cost record for this day\n if (orgPlan) {\n const pricePerSeat = planPricePerSeat(orgPlan.planName);\n if (pricePerSeat > 0 && orgPlan.seats > 0) {\n const dim = daysInMonth(day);\n const dailyAmount = (orgPlan.seats * pricePerSeat) / dim;\n records.push({\n cloud: \"github\",\n service: \"seats\",\n date: day,\n amount: Math.round(dailyAmount * 1e6) / 1e6, // avoid floating-point noise\n currency: \"USD\",\n usage: { value: orgPlan.seats, unit: \"seats\" },\n });\n }\n }\n\n return records;\n },\n 5,\n );\n\n const records = dayResults.flat();\n this.cachedServices = [...new Set(records.map((r) => r.service))];\n return records;\n }\n\n /** Fetch org plan info (plan name + seat count). Returns null on failure. */\n private async fetchOrgPlan(\n client: GitHubHTTPClient,\n org: string,\n headers: Record<string, string>,\n ): Promise<{ planName: string; seats: number } | null> {\n try {\n if (client.getOrgPlan) {\n const resp = await client.getOrgPlan(`https://api.github.com/orgs/${org}`, { headers });\n if (resp.status === 200 && resp.data.plan) {\n return { planName: resp.data.plan.name, seats: resp.data.plan.seats };\n }\n }\n } catch {\n // Seat cost is best-effort; don't fail the entire fetch\n }\n return null;\n }\n\n async listServices(): Promise<string[]> {\n if (this.cachedServices) return this.cachedServices;\n return [];\n }\n\n capabilities(): AdapterCapabilities {\n return {\n supportsAmount: true,\n supportsUsage: true,\n supportsTags: false,\n supportsRegion: false,\n supportsAccount: false,\n supportsForecast: false,\n };\n }\n\n private getClient(): GitHubHTTPClient | undefined {\n if (this.client) return this.client;\n // Create a fetch-based client for production use\n this.client = {\n async get(\n url: string,\n options: { headers: Record<string, string> },\n ): Promise<GitHubBillingResponse> {\n const resp = await fetch(url, {\n method: \"GET\",\n headers: options.headers,\n });\n const data = (await resp.json()) as GitHubBillingResponse[\"data\"];\n return { status: resp.status, data };\n },\n async getOrgPlan(\n url: string,\n options: { headers: Record<string, string> },\n ): Promise<GitHubOrgPlanResponse> {\n const resp = await fetch(url, {\n method: \"GET\",\n headers: options.headers,\n });\n const data = (await resp.json()) as GitHubOrgPlanResponse[\"data\"];\n return { status: resp.status, data };\n },\n };\n return this.client;\n }\n\n private buildHeaders(): Record<string, string> {\n return {\n Authorization: `Bearer ${this.config.token}`,\n Accept: \"application/json\",\n \"X-GitHub-Api-Version\": \"2022-11-28\",\n };\n }\n}\n"],"mappings":"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"}
package/dist/index.cjs ADDED
@@ -0,0 +1,101 @@
1
+ const require_adapter = require('./adapter.cjs');
2
+ let _aigne_afs_utils_camelize = require("@aigne/afs/utils/camelize");
3
+ let _aigne_afs_cost_base = require("@aigne/afs-cost-base");
4
+ let zod = require("zod");
5
+
6
+ //#region src/index.ts
7
+ var AFSGithubCost = class AFSGithubCost extends _aigne_afs_cost_base.AFSCostBaseProvider {
8
+ cloudName = "github";
9
+ static schema() {
10
+ return zod.z.object({
11
+ token: zod.z.string().optional(),
12
+ org: zod.z.string().optional(),
13
+ name: zod.z.string().optional(),
14
+ description: zod.z.string().optional(),
15
+ accessMode: zod.z.enum(["readonly", "readwrite"]).optional(),
16
+ startDate: zod.z.string().optional(),
17
+ endDate: zod.z.string().optional(),
18
+ cacheTTL: zod.z.number().optional(),
19
+ dataDir: zod.z.string().optional()
20
+ });
21
+ }
22
+ static manifest() {
23
+ return {
24
+ name: "github-cost",
25
+ description: "GitHub billing data with by-service and by-date views, including seat costs.",
26
+ uriTemplate: "github-cost://{label+}",
27
+ category: "cloud",
28
+ schema: zod.z.object({ label: zod.z.string().optional() }),
29
+ tags: [
30
+ "cost",
31
+ "billing",
32
+ "github"
33
+ ],
34
+ capabilityTags: [
35
+ "read-write",
36
+ "search",
37
+ "auth:token",
38
+ "remote"
39
+ ],
40
+ security: {
41
+ riskLevel: "external",
42
+ resourceAccess: ["cloud-api", "internet"],
43
+ dataSensitivity: ["credentials"],
44
+ requires: ["cloud-credentials"]
45
+ }
46
+ };
47
+ }
48
+ static treeSchema() {
49
+ return {
50
+ operations: [
51
+ "list",
52
+ "read",
53
+ "write",
54
+ "search",
55
+ "stat",
56
+ "explain"
57
+ ],
58
+ tree: {
59
+ "/": { kind: "github-cost:root" },
60
+ "/by-service": { kind: "github-cost:view" },
61
+ "/by-service/{service}": { kind: "github-cost:service-summary" },
62
+ "/by-date": { kind: "github-cost:view" },
63
+ "/by-date/{date}": { kind: "github-cost:date-summary" }
64
+ },
65
+ auth: {
66
+ type: "custom",
67
+ env: ["GITHUB_TOKEN"]
68
+ },
69
+ bestFor: [
70
+ "cost visibility",
71
+ "spend tracking",
72
+ "github billing",
73
+ "seat costs"
74
+ ],
75
+ notFor: ["real-time data", "budget alerts"]
76
+ };
77
+ }
78
+ static async load({ basePath, config } = {}) {
79
+ const camelized = (0, _aigne_afs_utils_camelize.camelize)(config ?? {});
80
+ return new AFSGithubCost({
81
+ ...await AFSGithubCost.schema().parseAsync(camelized),
82
+ cwd: basePath
83
+ });
84
+ }
85
+ createAdapter(config) {
86
+ return new require_adapter.GitHubCostAdapter({
87
+ token: config.token,
88
+ org: config.org
89
+ });
90
+ }
91
+ };
92
+
93
+ //#endregion
94
+ exports.AFSGithubCost = AFSGithubCost;
95
+ exports.GitHubCostAdapter = require_adapter.GitHubCostAdapter;
96
+ Object.defineProperty(exports, 'MockCostAdapter', {
97
+ enumerable: true,
98
+ get: function () {
99
+ return _aigne_afs_cost_base.MockCostAdapter;
100
+ }
101
+ });
@@ -0,0 +1,33 @@
1
+ import { GitHubBillingResponse, GitHubCostAdapter, GitHubCostConfig, GitHubHTTPClient, GitHubOrgPlanResponse, GitHubUsageItem } from "./adapter.cjs";
2
+ import { AFSModuleLoadParams, ProviderManifest, ProviderTreeSchema } from "@aigne/afs";
3
+ import { AFSCostBaseProvider, AdapterCapabilities, CostAdapter, CostAdapter as CostAdapter$1, CostRecord, MockCostAdapter } from "@aigne/afs-cost-base";
4
+ import { z } from "zod";
5
+
6
+ //#region src/index.d.ts
7
+ declare class AFSGithubCost extends AFSCostBaseProvider {
8
+ readonly cloudName = "github";
9
+ static schema(): z.ZodObject<{
10
+ token: z.ZodOptional<z.ZodString>;
11
+ org: z.ZodOptional<z.ZodString>;
12
+ name: z.ZodOptional<z.ZodString>;
13
+ description: z.ZodOptional<z.ZodString>;
14
+ accessMode: z.ZodOptional<z.ZodEnum<{
15
+ readonly: "readonly";
16
+ readwrite: "readwrite";
17
+ }>>;
18
+ startDate: z.ZodOptional<z.ZodString>;
19
+ endDate: z.ZodOptional<z.ZodString>;
20
+ cacheTTL: z.ZodOptional<z.ZodNumber>;
21
+ dataDir: z.ZodOptional<z.ZodString>;
22
+ }, z.core.$strip>;
23
+ static manifest(): ProviderManifest;
24
+ static treeSchema(): ProviderTreeSchema;
25
+ static load({
26
+ basePath,
27
+ config
28
+ }?: AFSModuleLoadParams): Promise<AFSGithubCost>;
29
+ protected createAdapter(config: Record<string, unknown>): CostAdapter$1;
30
+ }
31
+ //#endregion
32
+ export { AFSGithubCost, type AdapterCapabilities, type CostAdapter, type CostRecord, type GitHubBillingResponse, GitHubCostAdapter, type GitHubCostConfig, type GitHubHTTPClient, type GitHubOrgPlanResponse, type GitHubUsageItem, MockCostAdapter };
33
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;cA0Ba,aAAA,SAAsB,mBAAA;EAAA,SACxB,SAAA;EAAA,OAEF,MAAA,CAAA,GAAM,CAAA,CAAA,SAAA;;;;;;;;;;;;;;SAcN,QAAA,CAAA,GAAY,gBAAA;EAAA,OAkBZ,UAAA,CAAA,GAAc,kBAAA;EAAA,OAgBR,IAAA,CAAA;IAAO,QAAA;IAAU;EAAA,IAAU,mBAAA,GAAwB,OAAA,CAAA,aAAA;EAAA,UAOtD,aAAA,CAAc,MAAA,EAAQ,MAAA,oBAA0B,aAAA;AAAA"}
@@ -0,0 +1,33 @@
1
+ import { GitHubBillingResponse, GitHubCostAdapter, GitHubCostConfig, GitHubHTTPClient, GitHubOrgPlanResponse, GitHubUsageItem } from "./adapter.mjs";
2
+ import { AFSCostBaseProvider, AdapterCapabilities, CostAdapter, CostAdapter as CostAdapter$1, CostRecord, MockCostAdapter } from "@aigne/afs-cost-base";
3
+ import { z } from "zod";
4
+ import { AFSModuleLoadParams, ProviderManifest, ProviderTreeSchema } from "@aigne/afs";
5
+
6
+ //#region src/index.d.ts
7
+ declare class AFSGithubCost extends AFSCostBaseProvider {
8
+ readonly cloudName = "github";
9
+ static schema(): z.ZodObject<{
10
+ token: z.ZodOptional<z.ZodString>;
11
+ org: z.ZodOptional<z.ZodString>;
12
+ name: z.ZodOptional<z.ZodString>;
13
+ description: z.ZodOptional<z.ZodString>;
14
+ accessMode: z.ZodOptional<z.ZodEnum<{
15
+ readonly: "readonly";
16
+ readwrite: "readwrite";
17
+ }>>;
18
+ startDate: z.ZodOptional<z.ZodString>;
19
+ endDate: z.ZodOptional<z.ZodString>;
20
+ cacheTTL: z.ZodOptional<z.ZodNumber>;
21
+ dataDir: z.ZodOptional<z.ZodString>;
22
+ }, z.core.$strip>;
23
+ static manifest(): ProviderManifest;
24
+ static treeSchema(): ProviderTreeSchema;
25
+ static load({
26
+ basePath,
27
+ config
28
+ }?: AFSModuleLoadParams): Promise<AFSGithubCost>;
29
+ protected createAdapter(config: Record<string, unknown>): CostAdapter$1;
30
+ }
31
+ //#endregion
32
+ export { AFSGithubCost, type AdapterCapabilities, type CostAdapter, type CostRecord, type GitHubBillingResponse, GitHubCostAdapter, type GitHubCostConfig, type GitHubHTTPClient, type GitHubOrgPlanResponse, type GitHubUsageItem, MockCostAdapter };
33
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;cA0Ba,aAAA,SAAsB,mBAAA;EAAA,SACxB,SAAA;EAAA,OAEF,MAAA,CAAA,GAAM,CAAA,CAAA,SAAA;;;;;;;;;;;;;;SAcN,QAAA,CAAA,GAAY,gBAAA;EAAA,OAkBZ,UAAA,CAAA,GAAc,kBAAA;EAAA,OAgBR,IAAA,CAAA;IAAO,QAAA;IAAU;EAAA,IAAU,mBAAA,GAAwB,OAAA,CAAA,aAAA;EAAA,UAOtD,aAAA,CAAc,MAAA,EAAQ,MAAA,oBAA0B,aAAA;AAAA"}
package/dist/index.mjs ADDED
@@ -0,0 +1,95 @@
1
+ import { GitHubCostAdapter } from "./adapter.mjs";
2
+ import { camelize } from "@aigne/afs/utils/camelize";
3
+ import { AFSCostBaseProvider, MockCostAdapter } from "@aigne/afs-cost-base";
4
+ import { z } from "zod";
5
+
6
+ //#region src/index.ts
7
+ var AFSGithubCost = class AFSGithubCost extends AFSCostBaseProvider {
8
+ cloudName = "github";
9
+ static schema() {
10
+ return z.object({
11
+ token: z.string().optional(),
12
+ org: z.string().optional(),
13
+ name: z.string().optional(),
14
+ description: z.string().optional(),
15
+ accessMode: z.enum(["readonly", "readwrite"]).optional(),
16
+ startDate: z.string().optional(),
17
+ endDate: z.string().optional(),
18
+ cacheTTL: z.number().optional(),
19
+ dataDir: z.string().optional()
20
+ });
21
+ }
22
+ static manifest() {
23
+ return {
24
+ name: "github-cost",
25
+ description: "GitHub billing data with by-service and by-date views, including seat costs.",
26
+ uriTemplate: "github-cost://{label+}",
27
+ category: "cloud",
28
+ schema: z.object({ label: z.string().optional() }),
29
+ tags: [
30
+ "cost",
31
+ "billing",
32
+ "github"
33
+ ],
34
+ capabilityTags: [
35
+ "read-write",
36
+ "search",
37
+ "auth:token",
38
+ "remote"
39
+ ],
40
+ security: {
41
+ riskLevel: "external",
42
+ resourceAccess: ["cloud-api", "internet"],
43
+ dataSensitivity: ["credentials"],
44
+ requires: ["cloud-credentials"]
45
+ }
46
+ };
47
+ }
48
+ static treeSchema() {
49
+ return {
50
+ operations: [
51
+ "list",
52
+ "read",
53
+ "write",
54
+ "search",
55
+ "stat",
56
+ "explain"
57
+ ],
58
+ tree: {
59
+ "/": { kind: "github-cost:root" },
60
+ "/by-service": { kind: "github-cost:view" },
61
+ "/by-service/{service}": { kind: "github-cost:service-summary" },
62
+ "/by-date": { kind: "github-cost:view" },
63
+ "/by-date/{date}": { kind: "github-cost:date-summary" }
64
+ },
65
+ auth: {
66
+ type: "custom",
67
+ env: ["GITHUB_TOKEN"]
68
+ },
69
+ bestFor: [
70
+ "cost visibility",
71
+ "spend tracking",
72
+ "github billing",
73
+ "seat costs"
74
+ ],
75
+ notFor: ["real-time data", "budget alerts"]
76
+ };
77
+ }
78
+ static async load({ basePath, config } = {}) {
79
+ const camelized = camelize(config ?? {});
80
+ return new AFSGithubCost({
81
+ ...await AFSGithubCost.schema().parseAsync(camelized),
82
+ cwd: basePath
83
+ });
84
+ }
85
+ createAdapter(config) {
86
+ return new GitHubCostAdapter({
87
+ token: config.token,
88
+ org: config.org
89
+ });
90
+ }
91
+ };
92
+
93
+ //#endregion
94
+ export { AFSGithubCost, GitHubCostAdapter, MockCostAdapter };
95
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { AFSModuleLoadParams, ProviderManifest, ProviderTreeSchema } from \"@aigne/afs\";\nimport { camelize } from \"@aigne/afs/utils/camelize\";\nimport {\n type AFSCostBaseOptions,\n AFSCostBaseProvider,\n type CostAdapter,\n} from \"@aigne/afs-cost-base\";\nimport { z } from \"zod\";\n\nimport { GitHubCostAdapter, type GitHubCostConfig } from \"./adapter.js\";\n\nexport type { AdapterCapabilities, CostAdapter, CostRecord } from \"@aigne/afs-cost-base\";\nexport { MockCostAdapter } from \"@aigne/afs-cost-base\";\nexport type {\n GitHubBillingResponse,\n GitHubCostConfig,\n GitHubHTTPClient,\n GitHubOrgPlanResponse,\n GitHubUsageItem,\n} from \"./adapter.js\";\nexport { GitHubCostAdapter } from \"./adapter.js\";\n\n// =============================================================================\n// AFSGithubCost Provider\n// =============================================================================\n\nexport class AFSGithubCost extends AFSCostBaseProvider {\n readonly cloudName = \"github\";\n\n static schema() {\n return z.object({\n token: z.string().optional(),\n org: z.string().optional(),\n name: z.string().optional(),\n description: z.string().optional(),\n accessMode: z.enum([\"readonly\", \"readwrite\"]).optional(),\n startDate: z.string().optional(),\n endDate: z.string().optional(),\n cacheTTL: z.number().optional(),\n dataDir: z.string().optional(),\n });\n }\n\n static manifest(): ProviderManifest {\n return {\n name: \"github-cost\",\n description: \"GitHub billing data with by-service and by-date views, including seat costs.\",\n uriTemplate: \"github-cost://{label+}\",\n category: \"cloud\",\n schema: z.object({ label: z.string().optional() }),\n tags: [\"cost\", \"billing\", \"github\"],\n capabilityTags: [\"read-write\", \"search\", \"auth:token\", \"remote\"],\n security: {\n riskLevel: \"external\",\n resourceAccess: [\"cloud-api\", \"internet\"],\n dataSensitivity: [\"credentials\"],\n requires: [\"cloud-credentials\"],\n },\n };\n }\n\n static treeSchema(): ProviderTreeSchema {\n return {\n operations: [\"list\", \"read\", \"write\", \"search\", \"stat\", \"explain\"],\n tree: {\n \"/\": { kind: \"github-cost:root\" },\n \"/by-service\": { kind: \"github-cost:view\" },\n \"/by-service/{service}\": { kind: \"github-cost:service-summary\" },\n \"/by-date\": { kind: \"github-cost:view\" },\n \"/by-date/{date}\": { kind: \"github-cost:date-summary\" },\n },\n auth: { type: \"custom\", env: [\"GITHUB_TOKEN\"] },\n bestFor: [\"cost visibility\", \"spend tracking\", \"github billing\", \"seat costs\"],\n notFor: [\"real-time data\", \"budget alerts\"],\n };\n }\n\n static async load({ basePath, config }: AFSModuleLoadParams = {}) {\n const camelized = camelize(config ?? {});\n const valid = await AFSGithubCost.schema().parseAsync(camelized);\n return new AFSGithubCost({ ...valid, cwd: basePath } as AFSCostBaseOptions &\n Record<string, unknown>);\n }\n\n protected createAdapter(config: Record<string, unknown>): CostAdapter {\n const adapterConfig: GitHubCostConfig = {\n token: config.token as string | undefined,\n org: config.org as string | undefined,\n };\n return new GitHubCostAdapter(adapterConfig);\n }\n}\n"],"mappings":";;;;;;AA0BA,IAAa,gBAAb,MAAa,sBAAsB,oBAAoB;CACrD,AAAS,YAAY;CAErB,OAAO,SAAS;AACd,SAAO,EAAE,OAAO;GACd,OAAO,EAAE,QAAQ,CAAC,UAAU;GAC5B,KAAK,EAAE,QAAQ,CAAC,UAAU;GAC1B,MAAM,EAAE,QAAQ,CAAC,UAAU;GAC3B,aAAa,EAAE,QAAQ,CAAC,UAAU;GAClC,YAAY,EAAE,KAAK,CAAC,YAAY,YAAY,CAAC,CAAC,UAAU;GACxD,WAAW,EAAE,QAAQ,CAAC,UAAU;GAChC,SAAS,EAAE,QAAQ,CAAC,UAAU;GAC9B,UAAU,EAAE,QAAQ,CAAC,UAAU;GAC/B,SAAS,EAAE,QAAQ,CAAC,UAAU;GAC/B,CAAC;;CAGJ,OAAO,WAA6B;AAClC,SAAO;GACL,MAAM;GACN,aAAa;GACb,aAAa;GACb,UAAU;GACV,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,UAAU,EAAE,CAAC;GAClD,MAAM;IAAC;IAAQ;IAAW;IAAS;GACnC,gBAAgB;IAAC;IAAc;IAAU;IAAc;IAAS;GAChE,UAAU;IACR,WAAW;IACX,gBAAgB,CAAC,aAAa,WAAW;IACzC,iBAAiB,CAAC,cAAc;IAChC,UAAU,CAAC,oBAAoB;IAChC;GACF;;CAGH,OAAO,aAAiC;AACtC,SAAO;GACL,YAAY;IAAC;IAAQ;IAAQ;IAAS;IAAU;IAAQ;IAAU;GAClE,MAAM;IACJ,KAAK,EAAE,MAAM,oBAAoB;IACjC,eAAe,EAAE,MAAM,oBAAoB;IAC3C,yBAAyB,EAAE,MAAM,+BAA+B;IAChE,YAAY,EAAE,MAAM,oBAAoB;IACxC,mBAAmB,EAAE,MAAM,4BAA4B;IACxD;GACD,MAAM;IAAE,MAAM;IAAU,KAAK,CAAC,eAAe;IAAE;GAC/C,SAAS;IAAC;IAAmB;IAAkB;IAAkB;IAAa;GAC9E,QAAQ,CAAC,kBAAkB,gBAAgB;GAC5C;;CAGH,aAAa,KAAK,EAAE,UAAU,WAAgC,EAAE,EAAE;EAChE,MAAM,YAAY,SAAS,UAAU,EAAE,CAAC;AAExC,SAAO,IAAI,cAAc;GAAE,GADb,MAAM,cAAc,QAAQ,CAAC,WAAW,UAAU;GAC3B,KAAK;GAAU,CAC1B;;CAG5B,AAAU,cAAc,QAA8C;AAKpE,SAAO,IAAI,kBAJ6B;GACtC,OAAO,OAAO;GACd,KAAK,OAAO;GACb,CAC0C"}
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@aigne/afs-github-cost",
3
+ "version": "1.11.0-beta.12",
4
+ "description": "AIGNE AFS provider for GitHub billing data with seat costs",
5
+ "license": "UNLICENSED",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "author": "Arcblock <blocklet@arcblock.io> https://github.com/arcblock",
10
+ "homepage": "https://github.com/arcblock/afs",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/arcblock/afs"
14
+ },
15
+ "bugs": {
16
+ "url": "https://github.com/arcblock/afs/issues"
17
+ },
18
+ "type": "module",
19
+ "main": "./dist/index.cjs",
20
+ "module": "./dist/index.mjs",
21
+ "types": "./dist/index.d.cts",
22
+ "exports": {
23
+ ".": {
24
+ "require": "./dist/index.cjs",
25
+ "import": "./dist/index.mjs"
26
+ },
27
+ "./*": "./*"
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "LICENSE",
32
+ "README.md",
33
+ "CHANGELOG.md"
34
+ ],
35
+ "dependencies": {
36
+ "ufo": "^1.6.3",
37
+ "zod": "^4.0.0",
38
+ "@aigne/afs": "^1.11.0-beta.12",
39
+ "@aigne/afs-cost-base": "^1.11.0-beta.12"
40
+ },
41
+ "devDependencies": {
42
+ "@types/bun": "latest",
43
+ "npm-run-all": "^4.1.5",
44
+ "rimraf": "^6.1.2",
45
+ "tsdown": "0.20.0-beta.3",
46
+ "typescript": "5.9.2",
47
+ "@aigne/afs-testing": "1.11.0-beta.12",
48
+ "@aigne/scripts": "0.0.0",
49
+ "@aigne/typescript-config": "0.0.0"
50
+ },
51
+ "scripts": {
52
+ "build": "tsdown",
53
+ "check-types": "tsc --noEmit",
54
+ "clean": "rimraf dist coverage",
55
+ "test": "bun test",
56
+ "test:coverage": "bun test --coverage --coverage-reporter=lcov --coverage-reporter=text"
57
+ }
58
+ }