@agent-finops/core 0.1.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.
@@ -0,0 +1,109 @@
1
+ import type { UsageSignal } from "./discovery.js";
2
+ export type SourceType = "local_folder" | "provider_export" | "provider_api" | "browser_account" | "local_tool_detection" | "mcp_tool" | "internal_system";
3
+ export type SourceAccessMethod = "file" | "api" | "browser" | "cli_detection" | "mcp" | "internal" | "manual";
4
+ export type ConnectorAuthMode = "oauth" | "api_token_ref" | "browser_session" | "mcp_auth" | "manual_export" | "none";
5
+ export type ConnectorTokenStorage = "local_reference_only" | "keychain_reference" | "none";
6
+ export type SourceVerificationStatus = "verified" | "estimated" | "detected_unverified" | "missing";
7
+ export type IngestionLaneId = "local_files_exports" | "provider_apis" | "browser_account_ui" | "local_cli_tool_detection" | "mcp_internal_systems";
8
+ export type IngestionLane = {
9
+ id: IngestionLaneId;
10
+ label: string;
11
+ sourceTypes: SourceType[];
12
+ defaultVerification: SourceVerificationStatus;
13
+ };
14
+ export type ApprovedSource = {
15
+ id: string;
16
+ type: SourceType;
17
+ label: string;
18
+ path?: string;
19
+ provider?: string;
20
+ readOnly: boolean;
21
+ approvedAt: string;
22
+ scope: string;
23
+ lane: IngestionLaneId;
24
+ accessMethod: SourceAccessMethod;
25
+ verification: SourceVerificationStatus;
26
+ fieldsVerified: string[];
27
+ fieldsEstimated: string[];
28
+ fieldsMissing: string[];
29
+ authMode?: ConnectorAuthMode;
30
+ authScopes?: string[];
31
+ tokenStorage?: ConnectorTokenStorage;
32
+ authReference?: string;
33
+ };
34
+ export type SourceRegistry = {
35
+ version: 1;
36
+ localOnly: true;
37
+ cloudUpload: false;
38
+ approvedSources: ApprovedSource[];
39
+ deniedGlobs: string[];
40
+ ingestionLanes: IngestionLane[];
41
+ supportedSourceTypes: SourceType[];
42
+ updatedAt: string;
43
+ };
44
+ export type ScanAuditEvent = {
45
+ timestamp: string;
46
+ action: "source_registered" | "scan_started" | "source_scanned" | "source_skipped" | "secret_redacted" | "scan_completed" | "missing_source_prompted" | "mapping_confirmed";
47
+ sourceId?: string;
48
+ path?: string;
49
+ reason?: string;
50
+ detail?: string;
51
+ };
52
+ export type ScanAuditLog = {
53
+ version: 1;
54
+ localOnly: true;
55
+ events: ScanAuditEvent[];
56
+ };
57
+ export type ProviderCatalogEntry = {
58
+ id: string;
59
+ label: string;
60
+ preferredSourceType: SourceType;
61
+ preferredAccessMethod: SourceAccessMethod;
62
+ verifiedFields: string[];
63
+ missingFields: string[];
64
+ fallbackConnector?: SourceType;
65
+ };
66
+ export type ProviderConnectorCatalogEntry = {
67
+ provider: string;
68
+ preferredAuthMode: ConnectorAuthMode;
69
+ fallbackAuthModes: ConnectorAuthMode[];
70
+ scopes: string[];
71
+ tokenStorage: ConnectorTokenStorage;
72
+ setupHint: string;
73
+ };
74
+ export type MissingSourcePrompt = {
75
+ provider: string;
76
+ status: Extract<SourceVerificationStatus, "detected_unverified" | "missing">;
77
+ reason: string;
78
+ detectedEvidence: string[];
79
+ suggestedConnector: string;
80
+ suggestedSourceTypes: SourceType[];
81
+ };
82
+ export type ConfirmedMapping = {
83
+ id: string;
84
+ provider: string;
85
+ sourceId: string;
86
+ team?: string;
87
+ person?: string;
88
+ client?: string;
89
+ project?: string;
90
+ agent?: string;
91
+ workflow?: string;
92
+ evidence: string[];
93
+ confidence: number;
94
+ status: "confirmed";
95
+ confirmedAt: string;
96
+ };
97
+ export declare const ingestionLanes: IngestionLane[];
98
+ export declare const supportedSourceTypes: SourceType[];
99
+ export declare const providerCatalog: ProviderCatalogEntry[];
100
+ export declare const providerConnectorCatalog: ProviderConnectorCatalogEntry[];
101
+ export declare const defaultDeniedGlobs: string[];
102
+ export declare function createLocalFolderSourceRegistry(rootPath: string, now?: Date): SourceRegistry;
103
+ export declare function addApprovedSource(registry: SourceRegistry, source: Omit<ApprovedSource, "approvedAt" | "readOnly" | "scope" | "lane" | "accessMethod" | "verification" | "fieldsVerified" | "fieldsEstimated" | "fieldsMissing"> & Partial<Pick<ApprovedSource, "readOnly" | "scope" | "lane" | "accessMethod" | "verification" | "fieldsVerified" | "fieldsEstimated" | "fieldsMissing" | "authMode" | "authScopes" | "tokenStorage" | "authReference">>, now?: Date): SourceRegistry;
104
+ export declare function createProviderConnectorStub(provider: string, type?: SourceType, now?: Date): ApprovedSource;
105
+ export declare function buildMissingSourcePrompts(signals: UsageSignal[], registry: SourceRegistry): MissingSourcePrompt[];
106
+ export declare function confirmMapping(input: Omit<ConfirmedMapping, "id" | "status" | "confirmedAt">, now?: Date): ConfirmedMapping;
107
+ export declare function createScanAuditLog(events?: ScanAuditEvent[]): ScanAuditLog;
108
+ export declare function slugifySourceId(label: string): string;
109
+ //# sourceMappingURL=sourceRegistry.d.ts.map
@@ -0,0 +1,380 @@
1
+ export const ingestionLanes = [
2
+ {
3
+ id: "local_files_exports",
4
+ label: "Local files and provider exports",
5
+ sourceTypes: ["local_folder", "provider_export"],
6
+ defaultVerification: "estimated"
7
+ },
8
+ {
9
+ id: "provider_apis",
10
+ label: "Official provider APIs",
11
+ sourceTypes: ["provider_api"],
12
+ defaultVerification: "verified"
13
+ },
14
+ {
15
+ id: "browser_account_ui",
16
+ label: "Browser Account UI",
17
+ sourceTypes: ["browser_account"],
18
+ defaultVerification: "verified"
19
+ },
20
+ {
21
+ id: "local_cli_tool_detection",
22
+ label: "Local CLI/tool detection path",
23
+ sourceTypes: ["local_tool_detection"],
24
+ defaultVerification: "detected_unverified"
25
+ },
26
+ {
27
+ id: "mcp_internal_systems",
28
+ label: "MCP and internal systems",
29
+ sourceTypes: ["mcp_tool", "internal_system"],
30
+ defaultVerification: "verified"
31
+ }
32
+ ];
33
+ export const supportedSourceTypes = ingestionLanes.flatMap((lane) => lane.sourceTypes);
34
+ export const providerCatalog = [
35
+ {
36
+ id: "openai",
37
+ label: "OpenAI / Codex account usage",
38
+ preferredSourceType: "provider_api",
39
+ preferredAccessMethod: "api",
40
+ verifiedFields: ["organization costs", "project usage", "model usage", "api key usage"],
41
+ missingFields: ["admin API token reference", "organization id"]
42
+ },
43
+ {
44
+ id: "anthropic",
45
+ label: "Anthropic / Claude / Claude Code",
46
+ preferredSourceType: "provider_api",
47
+ preferredAccessMethod: "api",
48
+ verifiedFields: ["organization cost report", "Claude Code usage", "workspace/user usage"],
49
+ missingFields: ["admin API token reference", "organization id"],
50
+ fallbackConnector: "browser_account"
51
+ },
52
+ {
53
+ id: "github-copilot",
54
+ label: "GitHub Copilot",
55
+ preferredSourceType: "provider_api",
56
+ preferredAccessMethod: "api",
57
+ verifiedFields: ["Copilot usage metrics", "seat usage", "premium request usage"],
58
+ missingFields: ["GitHub token reference", "organization or enterprise slug"],
59
+ fallbackConnector: "browser_account"
60
+ },
61
+ {
62
+ id: "codex",
63
+ label: "Codex / OpenAI coding tools",
64
+ preferredSourceType: "provider_api",
65
+ preferredAccessMethod: "api",
66
+ verifiedFields: ["OpenAI project usage", "OpenAI costs", "tool/project attribution"],
67
+ missingFields: ["OpenAI admin API token reference", "project mapping"],
68
+ fallbackConnector: "browser_account"
69
+ },
70
+ {
71
+ id: "cursor",
72
+ label: "Cursor",
73
+ preferredSourceType: "provider_api",
74
+ preferredAccessMethod: "api",
75
+ verifiedFields: ["Cursor Admin API spend", "team usage", "seat usage"],
76
+ missingFields: ["Cursor admin API key reference or approved browser account session"],
77
+ fallbackConnector: "browser_account"
78
+ },
79
+ {
80
+ id: "gemini",
81
+ label: "Google Gemini",
82
+ preferredSourceType: "provider_api",
83
+ preferredAccessMethod: "api",
84
+ verifiedFields: ["Google/Vertex billing export", "model usage"],
85
+ missingFields: ["approved billing export or API source"]
86
+ },
87
+ {
88
+ id: "langfuse",
89
+ label: "Langfuse",
90
+ preferredSourceType: "provider_api",
91
+ preferredAccessMethod: "api",
92
+ verifiedFields: ["trace usage", "model cost observations"],
93
+ missingFields: ["Langfuse API token reference", "project id"]
94
+ },
95
+ {
96
+ id: "helicone",
97
+ label: "Helicone",
98
+ preferredSourceType: "provider_api",
99
+ preferredAccessMethod: "api",
100
+ verifiedFields: ["gateway usage", "model costs", "request metadata"],
101
+ missingFields: ["Helicone API token reference"]
102
+ },
103
+ {
104
+ id: "litellm",
105
+ label: "LiteLLM",
106
+ preferredSourceType: "internal_system",
107
+ preferredAccessMethod: "internal",
108
+ verifiedFields: ["proxy spend logs", "team/user/model spend"],
109
+ missingFields: ["database/API/MCP source"]
110
+ },
111
+ {
112
+ id: "vercel-ai-sdk",
113
+ label: "Vercel AI SDK",
114
+ preferredSourceType: "local_tool_detection",
115
+ preferredAccessMethod: "cli_detection",
116
+ verifiedFields: [],
117
+ missingFields: ["underlying provider source", "project mapping"]
118
+ },
119
+ {
120
+ id: "continue",
121
+ label: "Continue",
122
+ preferredSourceType: "local_tool_detection",
123
+ preferredAccessMethod: "cli_detection",
124
+ verifiedFields: [],
125
+ missingFields: ["underlying provider source"]
126
+ },
127
+ {
128
+ id: "aider",
129
+ label: "Aider",
130
+ preferredSourceType: "local_tool_detection",
131
+ preferredAccessMethod: "cli_detection",
132
+ verifiedFields: [],
133
+ missingFields: ["underlying provider source"]
134
+ }
135
+ ];
136
+ export const providerConnectorCatalog = [
137
+ {
138
+ provider: "openai",
139
+ preferredAuthMode: "oauth",
140
+ fallbackAuthModes: ["api_token_ref", "browser_session"],
141
+ scopes: ["organization:usage:read", "organization:costs:read", "projects:read"],
142
+ tokenStorage: "local_reference_only",
143
+ setupHint: "Prefer OAuth/admin consent for org usage and costs; fallback to a local keychain/token reference or dashboard export."
144
+ },
145
+ {
146
+ provider: "anthropic",
147
+ preferredAuthMode: "oauth",
148
+ fallbackAuthModes: ["api_token_ref", "browser_session"],
149
+ scopes: ["organization:usage:read", "organization:costs:read", "claude_code:usage:read"],
150
+ tokenStorage: "local_reference_only",
151
+ setupHint: "Prefer org/admin OAuth or admin API token reference for Claude cost and Claude Code usage reports."
152
+ },
153
+ {
154
+ provider: "github-copilot",
155
+ preferredAuthMode: "oauth",
156
+ fallbackAuthModes: ["api_token_ref", "browser_session"],
157
+ scopes: ["copilot:usage:read", "enterprise:read", "org:read"],
158
+ tokenStorage: "local_reference_only",
159
+ setupHint: "Prefer GitHub App/OAuth read-only org or enterprise consent for Copilot seats and usage metrics."
160
+ },
161
+ {
162
+ provider: "cursor",
163
+ preferredAuthMode: "api_token_ref",
164
+ fallbackAuthModes: ["browser_session", "manual_export"],
165
+ scopes: ["admin:*", "team:usage:read", "team:spend:read"],
166
+ tokenStorage: "local_reference_only",
167
+ setupHint: "Use Cursor Admin API key reference for Enterprise team usage/spend when available; fallback to Browser Account UI or manual export."
168
+ }
169
+ ];
170
+ export const defaultDeniedGlobs = [
171
+ ".env*",
172
+ "**/.git/**",
173
+ "**/node_modules/**",
174
+ "**/.ssh/**",
175
+ "**/Library/Keychains/**",
176
+ "**/*keychain*",
177
+ "**/*token*",
178
+ "**/*secret*",
179
+ "**/*password*"
180
+ ];
181
+ export function createLocalFolderSourceRegistry(rootPath, now = new Date()) {
182
+ const timestamp = now.toISOString();
183
+ return {
184
+ version: 1,
185
+ localOnly: true,
186
+ cloudUpload: false,
187
+ approvedSources: [
188
+ {
189
+ id: "local-root",
190
+ type: "local_folder",
191
+ label: "Approved local scan root",
192
+ path: rootPath,
193
+ readOnly: true,
194
+ approvedAt: timestamp,
195
+ scope: "Read-only scan of the explicit --path root. No writes outside .ai-spend-agent. No cloud upload.",
196
+ lane: "local_files_exports",
197
+ accessMethod: "file",
198
+ verification: "verified",
199
+ fieldsVerified: ["approved local folder boundary"],
200
+ fieldsEstimated: [],
201
+ fieldsMissing: ["provider account billing data"]
202
+ }
203
+ ],
204
+ deniedGlobs: defaultDeniedGlobs,
205
+ ingestionLanes,
206
+ supportedSourceTypes,
207
+ updatedAt: timestamp
208
+ };
209
+ }
210
+ export function addApprovedSource(registry, source, now = new Date()) {
211
+ const timestamp = now.toISOString();
212
+ const nextSource = {
213
+ ...source,
214
+ readOnly: source.readOnly ?? true,
215
+ approvedAt: timestamp,
216
+ scope: source.scope ?? defaultScopeForSource(source.type),
217
+ lane: source.lane ?? laneForSourceType(source.type),
218
+ accessMethod: source.accessMethod ?? accessMethodForSourceType(source.type),
219
+ verification: source.verification ?? ingestionLanes.find((lane) => lane.sourceTypes.includes(source.type))?.defaultVerification ?? "estimated",
220
+ fieldsVerified: source.fieldsVerified ?? [],
221
+ fieldsEstimated: source.fieldsEstimated ?? [],
222
+ fieldsMissing: source.fieldsMissing ?? [],
223
+ authMode: source.authMode,
224
+ authScopes: source.authScopes,
225
+ tokenStorage: source.tokenStorage,
226
+ authReference: source.authReference
227
+ };
228
+ const withoutExisting = registry.approvedSources.filter((candidate) => candidate.id !== nextSource.id);
229
+ return {
230
+ ...registry,
231
+ ingestionLanes: registry.ingestionLanes ?? ingestionLanes,
232
+ supportedSourceTypes: registry.supportedSourceTypes ?? supportedSourceTypes,
233
+ approvedSources: [...withoutExisting, nextSource],
234
+ updatedAt: timestamp
235
+ };
236
+ }
237
+ export function createProviderConnectorStub(provider, type = providerCatalog.find((entry) => entry.id === provider)?.preferredSourceType ?? "provider_api", now = new Date()) {
238
+ const catalogEntry = providerCatalog.find((entry) => entry.id === provider);
239
+ const connectorEntry = providerConnectorCatalog.find((entry) => entry.provider === provider);
240
+ const id = slugifySourceId(`${provider}-${type}`);
241
+ return {
242
+ id,
243
+ type,
244
+ label: catalogEntry?.label ?? `${provider} connector`,
245
+ provider,
246
+ readOnly: true,
247
+ approvedAt: now.toISOString(),
248
+ scope: defaultScopeForSource(type),
249
+ lane: laneForSourceType(type),
250
+ accessMethod: accessMethodForSourceType(type, catalogEntry),
251
+ verification: "missing",
252
+ fieldsVerified: catalogEntry?.verifiedFields ?? [],
253
+ fieldsEstimated: [],
254
+ fieldsMissing: catalogEntry?.missingFields ?? ["approved account/API/export source"],
255
+ authMode: authModeForConnectorType(type, connectorEntry),
256
+ authScopes: connectorEntry?.scopes ?? [],
257
+ tokenStorage: tokenStorageForConnectorType(type, connectorEntry)
258
+ };
259
+ }
260
+ export function buildMissingSourcePrompts(signals, registry) {
261
+ const providerSignals = new Map();
262
+ for (const signal of signals) {
263
+ if (signal.kind === "provider_export" || signal.kind === "invoice") {
264
+ continue;
265
+ }
266
+ providerSignals.set(signal.provider, [...(providerSignals.get(signal.provider) ?? []), signal]);
267
+ }
268
+ const prompts = [];
269
+ for (const [provider, detectedSignals] of Array.from(providerSignals.entries())) {
270
+ if (hasVerifiedProviderSource(registry, provider)) {
271
+ continue;
272
+ }
273
+ const catalogEntry = providerCatalog.find((entry) => entry.id === provider);
274
+ const preferredType = catalogEntry?.preferredSourceType ?? "provider_api";
275
+ const suggestedSourceTypes = [preferredType, catalogEntry?.fallbackConnector].filter(Boolean);
276
+ prompts.push({
277
+ provider,
278
+ status: "detected_unverified",
279
+ reason: `${provider} was detected locally, but no verified provider/API/browser/export source is connected.`,
280
+ detectedEvidence: detectedSignals.map((signal) => signal.evidence),
281
+ suggestedConnector: `connect ${provider} --type ${preferredType}`,
282
+ suggestedSourceTypes
283
+ });
284
+ }
285
+ return prompts.sort((left, right) => left.provider.localeCompare(right.provider));
286
+ }
287
+ export function confirmMapping(input, now = new Date()) {
288
+ return {
289
+ id: slugifySourceId([
290
+ input.provider,
291
+ input.team,
292
+ input.person,
293
+ input.client,
294
+ input.project,
295
+ input.agent,
296
+ input.workflow
297
+ ].filter(Boolean).join("-")),
298
+ ...input,
299
+ status: "confirmed",
300
+ confirmedAt: now.toISOString()
301
+ };
302
+ }
303
+ export function createScanAuditLog(events = []) {
304
+ return {
305
+ version: 1,
306
+ localOnly: true,
307
+ events
308
+ };
309
+ }
310
+ export function slugifySourceId(label) {
311
+ const slug = label
312
+ .trim()
313
+ .toLowerCase()
314
+ .replace(/[^a-z0-9]+/g, "-")
315
+ .replace(/^-+|-+$/g, "");
316
+ return slug || "approved-source";
317
+ }
318
+ function hasVerifiedProviderSource(registry, provider) {
319
+ return registry.approvedSources.some((source) => {
320
+ if (source.provider !== provider) {
321
+ return false;
322
+ }
323
+ if (source.verification === "verified" && source.type !== "local_tool_detection") {
324
+ return true;
325
+ }
326
+ return source.type === "provider_export" || source.type === "provider_api" || source.type === "browser_account" || source.type === "internal_system";
327
+ });
328
+ }
329
+ function authModeForConnectorType(type, connectorEntry) {
330
+ if (type === "browser_account")
331
+ return "browser_session";
332
+ if (type === "provider_export")
333
+ return "manual_export";
334
+ if (type === "mcp_tool" || type === "internal_system")
335
+ return "mcp_auth";
336
+ if (type === "local_tool_detection")
337
+ return "none";
338
+ return connectorEntry?.preferredAuthMode ?? "api_token_ref";
339
+ }
340
+ function tokenStorageForConnectorType(type, connectorEntry) {
341
+ if (type === "browser_account" || type === "provider_export" || type === "local_tool_detection")
342
+ return "none";
343
+ return connectorEntry?.tokenStorage ?? "local_reference_only";
344
+ }
345
+ function defaultScopeForSource(type) {
346
+ if (type === "provider_api") {
347
+ return "Read-only provider API/account usage source. Store token references only; no raw secrets. No billing changes. No cloud upload.";
348
+ }
349
+ if (type === "browser_account") {
350
+ return "Read-only Browser Account UI source. User logs in locally; agent never sees passwords; 2FA/CAPTCHA handoff; audit all page reads/downloads.";
351
+ }
352
+ if (type === "local_tool_detection") {
353
+ return "Read-only local CLI/tool detection path. Detection is not verified spend until account/API/export source is connected.";
354
+ }
355
+ if (type === "mcp_tool" || type === "internal_system") {
356
+ return "Read-only approved MCP/internal-system source. No writes, sends, deletes, or production changes without approval.";
357
+ }
358
+ return "Read-only approved source. No cloud upload.";
359
+ }
360
+ function laneForSourceType(type) {
361
+ const lane = ingestionLanes.find((candidate) => candidate.sourceTypes.includes(type));
362
+ return lane?.id ?? "local_files_exports";
363
+ }
364
+ function accessMethodForSourceType(type, catalogEntry) {
365
+ if (catalogEntry?.preferredSourceType === type) {
366
+ return catalogEntry.preferredAccessMethod;
367
+ }
368
+ if (type === "provider_api")
369
+ return "api";
370
+ if (type === "browser_account")
371
+ return "browser";
372
+ if (type === "local_tool_detection")
373
+ return "cli_detection";
374
+ if (type === "mcp_tool")
375
+ return "mcp";
376
+ if (type === "internal_system")
377
+ return "internal";
378
+ return "file";
379
+ }
380
+ //# sourceMappingURL=sourceRegistry.js.map