@agent-finops/core 0.8.1 → 0.9.1

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 (51) hide show
  1. package/README.md +5 -3
  2. package/dist/actionPlanner.d.ts +140 -0
  3. package/dist/actionPlanner.js +938 -0
  4. package/dist/actionVerification.d.ts +1240 -0
  5. package/dist/actionVerification.js +1028 -0
  6. package/dist/activitySnapshot.d.ts +142 -50
  7. package/dist/activitySnapshot.js +145 -6
  8. package/dist/activitySnapshotCache.d.ts +8 -1
  9. package/dist/activitySnapshotCache.js +103 -7
  10. package/dist/agentDraftToken.d.ts +80 -0
  11. package/dist/agentDraftToken.js +188 -0
  12. package/dist/agentEconomicsReceipt.d.ts +74 -74
  13. package/dist/agentLoopContract.d.ts +27 -0
  14. package/dist/agentLoopContract.js +36 -0
  15. package/dist/glance.d.ts +27 -1
  16. package/dist/glance.js +151 -12
  17. package/dist/guidedAnswer.d.ts +51 -0
  18. package/dist/guidedAnswer.js +352 -0
  19. package/dist/index.d.ts +14 -2
  20. package/dist/index.js +13 -1
  21. package/dist/localAgentFormats/gemini.js +2 -2
  22. package/dist/localAgentFormats/registry.js +6 -2
  23. package/dist/localAgentFormats/runtimeRegistry.js +5 -2
  24. package/dist/localAgentFormats/types.d.ts +2 -1
  25. package/dist/localAgentLogs.d.ts +362 -3
  26. package/dist/localAgentLogs.js +1964 -165
  27. package/dist/modelPricing.d.ts +1 -1
  28. package/dist/modelPricing.js +1 -1
  29. package/dist/projectEconomics.d.ts +617 -0
  30. package/dist/projectEconomics.js +620 -0
  31. package/dist/projectEconomicsBuilder.d.ts +89 -0
  32. package/dist/projectEconomicsBuilder.js +473 -0
  33. package/dist/projectIndexStore.d.ts +545 -0
  34. package/dist/projectIndexStore.js +606 -0
  35. package/dist/providerConnectors.d.ts +161 -1
  36. package/dist/providerConnectors.js +406 -11
  37. package/dist/qualitativeIndexCache.d.ts +494 -0
  38. package/dist/qualitativeIndexCache.js +930 -0
  39. package/dist/resultCard.d.ts +350 -0
  40. package/dist/resultCard.js +604 -0
  41. package/dist/runtimeCommands.d.ts +36 -0
  42. package/dist/runtimeCommands.js +50 -0
  43. package/dist/scanGuard.d.ts +3 -1
  44. package/dist/scanGuard.js +164 -4
  45. package/dist/schema.d.ts +33 -31
  46. package/dist/schema.js +9 -1
  47. package/dist/sessionVitals.d.ts +145 -0
  48. package/dist/sessionVitals.js +521 -0
  49. package/dist/toolInvocations.d.ts +40 -1
  50. package/dist/toolInvocations.js +101 -20
  51. package/package.json +1 -1
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Distribution truth for the guided action loop.
3
+ *
4
+ * Release gate: change this single value to `published` only in the exact,
5
+ * coordinated new-version release candidate. Its packed-install gate must
6
+ * prove every generated handoff uses the new package command before publish;
7
+ * the clean public-registry smoke then verifies that same exact commit. Until
8
+ * that release candidate exists, handoffs execute the already-built checkout
9
+ * and must not let `npx` download the older public package.
10
+ */
11
+ export const AIBILL_IMPROVE_DELIVERY_V0 = "published";
12
+ /**
13
+ * Build a command for capabilities that exist only in the current source
14
+ * preview. Keep every generated handoff on the checkout until the coordinated
15
+ * npm release containing those capabilities has passed its registry smoke.
16
+ */
17
+ export function aibillCommandV0(args, delivery = AIBILL_IMPROVE_DELIVERY_V0) {
18
+ const commandArgs = args.trim();
19
+ return delivery === "source_preview"
20
+ ? `node packages/cli/dist/index.js${commandArgs.length > 0 ? ` ${commandArgs}` : ""}`
21
+ : `npx aibill${commandArgs.length > 0 ? ` ${commandArgs}` : ""}`;
22
+ }
23
+ /** One privacy-safe command shared by terminal, MCP, and Glance. */
24
+ export function aibillImproveCommandV0(delivery = AIBILL_IMPROVE_DELIVERY_V0) {
25
+ return aibillCommandV0(delivery === "source_preview" ? "improve --path ." : "improve", delivery);
26
+ }
27
+ /** Published semver shape a composed pin must have (charset-safe by regex). */
28
+ const pinnableVersionPattern = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
29
+ /**
30
+ * Version-pinned command for machine-composed lines (M4c): a command an AI
31
+ * client relays to a human must be reproducible and must not silently
32
+ * resolve to a different release, so `draft_improve_command` pins to the
33
+ * composing package's own version (`npx aibill@<version> …`). A version
34
+ * that is not a plain semver falls back to the unpinned published command
35
+ * rather than composing an unrunnable line. In source-preview builds the
36
+ * checkout command needs no pin.
37
+ *
38
+ * Release gate (n2): the coordinated release must also prove the pinned
39
+ * version EXISTS on the public registry and supports the composed flags —
40
+ * the packed-install gate described on AIBILL_IMPROVE_DELIVERY_V0 is the
41
+ * natural home for that check; QA 24 asserts only that the pin is present.
42
+ */
43
+ export function aibillPinnedCommandV0(args, version, delivery = AIBILL_IMPROVE_DELIVERY_V0) {
44
+ const commandArgs = args.trim();
45
+ if (delivery === "source_preview" || !pinnableVersionPattern.test(version)) {
46
+ return aibillCommandV0(commandArgs, delivery);
47
+ }
48
+ return `npx aibill@${version}${commandArgs.length > 0 ? ` ${commandArgs}` : ""}`;
49
+ }
50
+ //# sourceMappingURL=runtimeCommands.js.map
@@ -33,7 +33,9 @@ export declare function resolveSafeStateDirectory(rootPath: string, options?: {
33
33
  * inside a cloned repository, so validating only the parent directory is not
34
34
  * enough: a committed child symlink must never expose an arbitrary local file.
35
35
  */
36
- export declare function readSafeStateText(stateDir: string, fileName: string): Promise<string>;
36
+ export declare function readSafeStateText(stateDir: string, fileName: string, options?: {
37
+ maxBytes?: number;
38
+ }): Promise<string>;
37
39
  /**
38
40
  * Atomically replace one regular child file. The temporary file is created
39
41
  * exclusively with mode 0600, and rename replaces a last-moment symlink
package/dist/scanGuard.js CHANGED
@@ -1,8 +1,10 @@
1
1
  import { homedir } from "node:os";
2
2
  import { constants, realpathSync } from "node:fs";
3
+ import { execFile as execFileCallback } from "node:child_process";
3
4
  import { chmod, lstat, mkdir, open, realpath, rename, stat, unlink } from "node:fs/promises";
4
5
  import { randomUUID } from "node:crypto";
5
- import { basename, join, resolve, sep } from "node:path";
6
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
7
+ import { promisify } from "node:util";
6
8
  /**
7
9
  * Shared unsafe-scan-root policy for EVERY scan entrypoint (CLI `scan`, MCP
8
10
  * `scan_ai_spend`, and any future surface). Scanning the home directory, the
@@ -29,6 +31,7 @@ const systemRootDirectories = new Set([
29
31
  "/sys",
30
32
  "/dev"
31
33
  ]);
34
+ const execFile = promisify(execFileCallback);
32
35
  export class UnsafeScanRootError extends Error {
33
36
  rootPath;
34
37
  constructor(rootPath, reason) {
@@ -127,9 +130,11 @@ export async function resolveSafeStateDirectory(rootPath, options = {}) {
127
130
  return undefined;
128
131
  throw error;
129
132
  });
133
+ let createdStateDirectory = false;
130
134
  if (!stateInfo && options.create) {
131
135
  try {
132
136
  await mkdir(statePath, { mode: 0o700 });
137
+ createdStateDirectory = true;
133
138
  }
134
139
  catch (error) {
135
140
  // A concurrent creator is safe only after the same lstat checks below.
@@ -153,17 +158,145 @@ export async function resolveSafeStateDirectory(rootPath, options = {}) {
153
158
  if (canonicalState !== statePath) {
154
159
  throw new UnsafeStateDirectoryError(statePath, ".ai-spend-agent does not resolve directly inside the approved root");
155
160
  }
156
- if (options.create) {
161
+ // Project state can contain private receipts and experiment evidence. A
162
+ // repository-authored marker or tracked state is not a trust boundary: the
163
+ // exact marker, private permissions, untracked status, and effective Git
164
+ // ignore rule must all be proven before either reads or writes proceed.
165
+ await assertPrivateStateGitBoundary(canonicalRoot, canonicalState, options.create === true);
166
+ const directoryMode = stateInfo.mode & 0o777;
167
+ if ((directoryMode & 0o077) !== 0) {
168
+ if (options.create !== true) {
169
+ throw new UnsafeStateDirectoryError(statePath, ".ai-spend-agent permissions expose private state metadata to other users");
170
+ }
171
+ // Explicit create/write calls may safely migrate a legacy aibill-owned
172
+ // directory only after the Git privacy boundary above proved that neither
173
+ // it nor a child is tracked and that the exact private marker is effective.
174
+ // Read-only callers never change permissions implicitly.
175
+ await chmod(statePath, 0o700);
176
+ const securedInfo = await lstat(statePath);
177
+ if (!securedInfo.isDirectory() || securedInfo.isSymbolicLink() ||
178
+ securedInfo.dev !== stateInfo.dev || securedInfo.ino !== stateInfo.ino ||
179
+ (securedInfo.mode & 0o077) !== 0) {
180
+ throw new UnsafeStateDirectoryError(statePath, ".ai-spend-agent permissions could not be secured without changing directory identity");
181
+ }
182
+ stateInfo = securedInfo;
183
+ }
184
+ else if (options.create && createdStateDirectory && directoryMode !== 0o700) {
185
+ // An unusually restrictive umask is still normalized for stable private
186
+ // access only on the directory this call created.
157
187
  await chmod(statePath, 0o700);
158
188
  }
159
189
  return statePath;
160
190
  }
191
+ async function assertPrivateStateGitBoundary(canonicalRoot, statePath, create) {
192
+ const gitEntryRoot = await findEnclosingGitEntryRoot(canonicalRoot);
193
+ const gitRoot = await resolveGitRoot(canonicalRoot, gitEntryRoot);
194
+ // Outside a Git worktree, the state files' own 0600 permissions and safe
195
+ // no-follow I/O are the privacy boundary. The ignore marker is specifically
196
+ // a repository-leak guard and is required only when Git can see this root.
197
+ if (!gitRoot)
198
+ return;
199
+ const relativeState = relative(gitRoot, statePath);
200
+ if (!relativeState || relativeState === ".." || relativeState.startsWith(`..${sep}`) ||
201
+ isAbsolute(relativeState)) {
202
+ throw new UnsafeStateDirectoryError(statePath, ".ai-spend-agent is outside the resolved Git worktree");
203
+ }
204
+ const tracked = await execFile("git", ["-C", gitRoot, "ls-files", "--", relativeState], { encoding: "utf8", maxBuffer: 64 * 1024 }).then(({ stdout }) => stdout.trim()).catch(() => {
205
+ throw new UnsafeStateDirectoryError(statePath, "Git tracking status for .ai-spend-agent could not be verified");
206
+ });
207
+ if (tracked) {
208
+ throw new UnsafeStateDirectoryError(statePath, ".ai-spend-agent or one of its children is already tracked by Git");
209
+ }
210
+ const markerPath = join(statePath, ".gitignore");
211
+ let handle;
212
+ try {
213
+ handle = await open(markerPath, constants.O_RDONLY | noFollowFlag());
214
+ }
215
+ catch (error) {
216
+ if (!isNodeError(error, "ENOENT") || !create) {
217
+ if (isNodeError(error, "ELOOP")) {
218
+ throw new UnsafeStateDirectoryError(statePath, ".ai-spend-agent/.gitignore is a symbolic link");
219
+ }
220
+ throw error;
221
+ }
222
+ let writer;
223
+ try {
224
+ writer = await open(markerPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | noFollowFlag(), 0o600);
225
+ await writer.writeFile("*\n", "utf8");
226
+ await writer.sync();
227
+ }
228
+ catch (createError) {
229
+ if (isNodeError(createError, "ELOOP")) {
230
+ throw new UnsafeStateDirectoryError(statePath, ".ai-spend-agent/.gitignore is a symbolic link");
231
+ }
232
+ if (!isNodeError(createError, "EEXIST"))
233
+ throw createError;
234
+ }
235
+ finally {
236
+ await writer?.close().catch(() => undefined);
237
+ }
238
+ handle = await open(markerPath, constants.O_RDONLY | noFollowFlag());
239
+ }
240
+ try {
241
+ const info = await handle.stat();
242
+ if (!info.isFile() || (info.mode & 0o077) !== 0 || info.size !== 2) {
243
+ throw new UnsafeStateDirectoryError(statePath, ".ai-spend-agent/.gitignore is not the private aibill marker");
244
+ }
245
+ const bytes = Buffer.alloc(2);
246
+ const { bytesRead } = await handle.read(bytes, 0, 2, 0);
247
+ if (bytesRead !== 2 || bytes.toString("utf8") !== "*\n") {
248
+ throw new UnsafeStateDirectoryError(statePath, ".ai-spend-agent/.gitignore is not the exact private aibill marker");
249
+ }
250
+ }
251
+ finally {
252
+ await handle.close().catch(() => undefined);
253
+ }
254
+ const relativeProbe = join(relativeState, "privacy-probe.json");
255
+ const ignored = await execFile("git", ["-C", gitRoot, "check-ignore", "--quiet", "--no-index", "--", relativeProbe], { maxBuffer: 64 * 1024 }).then(() => true).catch(() => false);
256
+ if (!ignored) {
257
+ throw new UnsafeStateDirectoryError(statePath, ".ai-spend-agent is not proven ignored by Git");
258
+ }
259
+ }
260
+ async function findEnclosingGitEntryRoot(path) {
261
+ let current = resolve(path);
262
+ while (true) {
263
+ const entry = await lstat(join(current, ".git")).catch((error) => {
264
+ if (isNodeError(error, "ENOENT") || isNodeError(error, "ENOTDIR"))
265
+ return undefined;
266
+ throw error;
267
+ });
268
+ if (entry)
269
+ return current;
270
+ const parent = dirname(current);
271
+ if (parent === current)
272
+ return undefined;
273
+ current = parent;
274
+ }
275
+ }
276
+ async function resolveGitRoot(canonicalRoot, gitEntryRoot) {
277
+ try {
278
+ const { stdout } = await execFile("git", ["-C", canonicalRoot, "rev-parse", "--show-toplevel"], { encoding: "utf8", maxBuffer: 64 * 1024 });
279
+ const reported = stdout.trim();
280
+ if (!reported || !isAbsolute(reported)) {
281
+ throw new UnsafeStateDirectoryError(canonicalRoot, "Git returned an invalid worktree root");
282
+ }
283
+ return await realpath(reported);
284
+ }
285
+ catch (error) {
286
+ if (error instanceof UnsafeStateDirectoryError)
287
+ throw error;
288
+ if (gitEntryRoot) {
289
+ throw new UnsafeStateDirectoryError(canonicalRoot, "the enclosing Git worktree could not be verified");
290
+ }
291
+ return undefined;
292
+ }
293
+ }
161
294
  /**
162
295
  * Read one regular child file without following a symbolic link. State can sit
163
296
  * inside a cloned repository, so validating only the parent directory is not
164
297
  * enough: a committed child symlink must never expose an arbitrary local file.
165
298
  */
166
- export async function readSafeStateText(stateDir, fileName) {
299
+ export async function readSafeStateText(stateDir, fileName, options = {}) {
167
300
  const filePath = await resolveSafeStateChild(stateDir, fileName, false);
168
301
  let handle;
169
302
  try {
@@ -172,7 +305,34 @@ export async function readSafeStateText(stateDir, fileName) {
172
305
  if (!info.isFile()) {
173
306
  throw new UnsafeStateFileError(filePath, "the state entry is not a regular file");
174
307
  }
175
- return await handle.readFile("utf8");
308
+ const maxBytes = options.maxBytes;
309
+ if (maxBytes === undefined)
310
+ return await handle.readFile("utf8");
311
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) {
312
+ throw new RangeError("maxBytes must be a non-negative safe integer");
313
+ }
314
+ if (info.size > maxBytes) {
315
+ throw new UnsafeStateFileError(filePath, `the state entry exceeds ${maxBytes} bytes`);
316
+ }
317
+ // Never use readFile for attacker-authored bounded state: a same-inode
318
+ // growth race could otherwise allocate the new whole size after fstat.
319
+ const buffer = Buffer.alloc(Math.min(maxBytes + 1, Math.max(1, info.size + 1)));
320
+ let offset = 0;
321
+ while (offset < buffer.length) {
322
+ const { bytesRead } = await handle.read(buffer, offset, buffer.length - offset, offset);
323
+ if (bytesRead === 0)
324
+ break;
325
+ offset += bytesRead;
326
+ }
327
+ const after = await handle.stat();
328
+ if (offset > maxBytes || after.size > maxBytes) {
329
+ throw new UnsafeStateFileError(filePath, `the state entry exceeds ${maxBytes} bytes`);
330
+ }
331
+ if (after.size !== info.size || after.mtimeMs !== info.mtimeMs ||
332
+ after.ctimeMs !== info.ctimeMs) {
333
+ throw new UnsafeStateFileError(filePath, "the state entry changed while it was read");
334
+ }
335
+ return buffer.subarray(0, offset).toString("utf8");
176
336
  }
177
337
  catch (error) {
178
338
  if (isNodeError(error, "ELOOP")) {
package/dist/schema.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { z } from "zod";
2
2
  export declare const costConfidenceValues: readonly ["verified", "estimated", "detected_unverified", "missing"];
3
3
  export declare const costConfidenceSchema: z.ZodEnum<{
4
- verified: "verified";
5
4
  estimated: "estimated";
5
+ verified: "verified";
6
6
  detected_unverified: "detected_unverified";
7
7
  missing: "missing";
8
8
  }>;
@@ -12,12 +12,13 @@ export declare const spendSourceSchema: z.ZodObject<{
12
12
  name: z.ZodString;
13
13
  provider: z.ZodString;
14
14
  confidence: z.ZodEnum<{
15
- verified: "verified";
16
15
  estimated: "estimated";
16
+ verified: "verified";
17
17
  detected_unverified: "detected_unverified";
18
18
  missing: "missing";
19
19
  }>;
20
20
  observedFrom: z.ZodString;
21
+ account: z.ZodOptional<z.ZodString>;
21
22
  }, z.core.$strip>;
22
23
  export type SpendSource = z.infer<typeof spendSourceSchema>;
23
24
  /**
@@ -30,9 +31,9 @@ export type SpendSource = z.infer<typeof spendSourceSchema>;
30
31
  */
31
32
  export declare const usageGranularityValues: readonly ["call", "invocation", "session", "daily_aggregate", "usage_bucket", "billing_bucket", "seat", "user_aggregate"];
32
33
  export declare const usageGranularitySchema: z.ZodEnum<{
34
+ session: "session";
33
35
  call: "call";
34
36
  invocation: "invocation";
35
- session: "session";
36
37
  daily_aggregate: "daily_aggregate";
37
38
  usage_bucket: "usage_bucket";
38
39
  billing_bucket: "billing_bucket";
@@ -59,12 +60,13 @@ export declare const usageRecordSchema: z.ZodObject<{
59
60
  name: z.ZodString;
60
61
  provider: z.ZodString;
61
62
  confidence: z.ZodEnum<{
62
- verified: "verified";
63
63
  estimated: "estimated";
64
+ verified: "verified";
64
65
  detected_unverified: "detected_unverified";
65
66
  missing: "missing";
66
67
  }>;
67
68
  observedFrom: z.ZodString;
69
+ account: z.ZodOptional<z.ZodString>;
68
70
  }, z.core.$strip>;
69
71
  model: z.ZodString;
70
72
  inputTokens: z.ZodNumber;
@@ -89,8 +91,8 @@ export declare const usageRecordSchema: z.ZodObject<{
89
91
  sourceVersions: z.ZodOptional<z.ZodArray<z.ZodString>>;
90
92
  amountUsd: z.ZodNullable<z.ZodNumber>;
91
93
  costConfidence: z.ZodEnum<{
92
- verified: "verified";
93
94
  estimated: "estimated";
95
+ verified: "verified";
94
96
  detected_unverified: "detected_unverified";
95
97
  missing: "missing";
96
98
  }>;
@@ -104,9 +106,9 @@ export declare const usageRecordSchema: z.ZodObject<{
104
106
  agentId: z.ZodOptional<z.ZodString>;
105
107
  operation: z.ZodOptional<z.ZodString>;
106
108
  usageGranularity: z.ZodOptional<z.ZodEnum<{
109
+ session: "session";
107
110
  call: "call";
108
111
  invocation: "invocation";
109
- session: "session";
110
112
  daily_aggregate: "daily_aggregate";
111
113
  usage_bucket: "usage_bucket";
112
114
  billing_bucket: "billing_bucket";
@@ -152,10 +154,10 @@ export declare function downgradeSampleUsageEvidence(records: UsageRecord[]): Us
152
154
  export declare function spendComparisonKey(record: UsageRecord): string | undefined;
153
155
  export declare const attributionCandidateSchema: z.ZodObject<{
154
156
  entityType: z.ZodEnum<{
155
- client: "client";
157
+ user: "user";
156
158
  project: "project";
157
159
  agent: "agent";
158
- user: "user";
160
+ client: "client";
159
161
  workspace: "workspace";
160
162
  api_key: "api_key";
161
163
  }>;
@@ -168,10 +170,10 @@ export declare const attributionMappingSchema: z.ZodObject<{
168
170
  usageRecordId: z.ZodString;
169
171
  candidates: z.ZodArray<z.ZodObject<{
170
172
  entityType: z.ZodEnum<{
171
- client: "client";
173
+ user: "user";
172
174
  project: "project";
173
175
  agent: "agent";
174
- user: "user";
176
+ client: "client";
175
177
  workspace: "workspace";
176
178
  api_key: "api_key";
177
179
  }>;
@@ -181,10 +183,10 @@ export declare const attributionMappingSchema: z.ZodObject<{
181
183
  }, z.core.$strip>>;
182
184
  selected: z.ZodOptional<z.ZodObject<{
183
185
  entityType: z.ZodEnum<{
184
- client: "client";
186
+ user: "user";
185
187
  project: "project";
186
188
  agent: "agent";
187
- user: "user";
189
+ client: "client";
188
190
  workspace: "workspace";
189
191
  api_key: "api_key";
190
192
  }>;
@@ -206,8 +208,8 @@ export declare const spendBreakdownEntrySchema: z.ZodObject<{
206
208
  amountUsd: z.ZodNumber;
207
209
  recordCount: z.ZodNumber;
208
210
  confidence: z.ZodEnum<{
209
- verified: "verified";
210
211
  estimated: "estimated";
212
+ verified: "verified";
211
213
  detected_unverified: "detected_unverified";
212
214
  missing: "missing";
213
215
  }>;
@@ -224,8 +226,8 @@ export declare const spendAnomalySchema: z.ZodObject<{
224
226
  currentAmountUsd: z.ZodNumber;
225
227
  multiplier: z.ZodNumber;
226
228
  confidence: z.ZodEnum<{
227
- verified: "verified";
228
229
  estimated: "estimated";
230
+ verified: "verified";
229
231
  detected_unverified: "detected_unverified";
230
232
  missing: "missing";
231
233
  }>;
@@ -241,8 +243,8 @@ export declare const workflowWatchEntrySchema: z.ZodObject<{
241
243
  shareOfSpend: z.ZodNumber;
242
244
  recordCount: z.ZodNumber;
243
245
  confidence: z.ZodEnum<{
244
- verified: "verified";
245
246
  estimated: "estimated";
247
+ verified: "verified";
246
248
  detected_unverified: "detected_unverified";
247
249
  missing: "missing";
248
250
  }>;
@@ -266,8 +268,8 @@ export declare const recommendationSchema: z.ZodObject<{
266
268
  }>;
267
269
  estimatedImpactUsd: z.ZodNumber;
268
270
  confidence: z.ZodEnum<{
269
- verified: "verified";
270
271
  estimated: "estimated";
272
+ verified: "verified";
271
273
  detected_unverified: "detected_unverified";
272
274
  missing: "missing";
273
275
  }>;
@@ -311,8 +313,8 @@ export declare const spendInsightSchema: z.ZodObject<{
311
313
  affectedModels: z.ZodArray<z.ZodString>;
312
314
  estimatedImpactUsd: z.ZodNumber;
313
315
  confidence: z.ZodEnum<{
314
- verified: "verified";
315
316
  estimated: "estimated";
317
+ verified: "verified";
316
318
  detected_unverified: "detected_unverified";
317
319
  missing: "missing";
318
320
  }>;
@@ -324,14 +326,14 @@ export declare const spendSummarySchema: z.ZodObject<{
324
326
  totalUsd: z.ZodNumber;
325
327
  recordCount: z.ZodNumber;
326
328
  confidence: z.ZodEnum<{
327
- verified: "verified";
328
329
  estimated: "estimated";
330
+ verified: "verified";
329
331
  detected_unverified: "detected_unverified";
330
332
  missing: "missing";
331
333
  }>;
332
334
  confidenceBreakdown: z.ZodRecord<z.ZodEnum<{
333
- verified: "verified";
334
335
  estimated: "estimated";
336
+ verified: "verified";
335
337
  detected_unverified: "detected_unverified";
336
338
  missing: "missing";
337
339
  }>, z.ZodNumber>;
@@ -340,8 +342,8 @@ export declare const spendSummarySchema: z.ZodObject<{
340
342
  amountUsd: z.ZodNumber;
341
343
  recordCount: z.ZodNumber;
342
344
  confidence: z.ZodEnum<{
343
- verified: "verified";
344
345
  estimated: "estimated";
346
+ verified: "verified";
345
347
  detected_unverified: "detected_unverified";
346
348
  missing: "missing";
347
349
  }>;
@@ -351,8 +353,8 @@ export declare const spendSummarySchema: z.ZodObject<{
351
353
  amountUsd: z.ZodNumber;
352
354
  recordCount: z.ZodNumber;
353
355
  confidence: z.ZodEnum<{
354
- verified: "verified";
355
356
  estimated: "estimated";
357
+ verified: "verified";
356
358
  detected_unverified: "detected_unverified";
357
359
  missing: "missing";
358
360
  }>;
@@ -362,8 +364,8 @@ export declare const spendSummarySchema: z.ZodObject<{
362
364
  amountUsd: z.ZodNumber;
363
365
  recordCount: z.ZodNumber;
364
366
  confidence: z.ZodEnum<{
365
- verified: "verified";
366
367
  estimated: "estimated";
368
+ verified: "verified";
367
369
  detected_unverified: "detected_unverified";
368
370
  missing: "missing";
369
371
  }>;
@@ -373,8 +375,8 @@ export declare const spendSummarySchema: z.ZodObject<{
373
375
  amountUsd: z.ZodNumber;
374
376
  recordCount: z.ZodNumber;
375
377
  confidence: z.ZodEnum<{
376
- verified: "verified";
377
378
  estimated: "estimated";
379
+ verified: "verified";
378
380
  detected_unverified: "detected_unverified";
379
381
  missing: "missing";
380
382
  }>;
@@ -384,8 +386,8 @@ export declare const spendSummarySchema: z.ZodObject<{
384
386
  amountUsd: z.ZodNumber;
385
387
  recordCount: z.ZodNumber;
386
388
  confidence: z.ZodEnum<{
387
- verified: "verified";
388
389
  estimated: "estimated";
390
+ verified: "verified";
389
391
  detected_unverified: "detected_unverified";
390
392
  missing: "missing";
391
393
  }>;
@@ -395,8 +397,8 @@ export declare const spendSummarySchema: z.ZodObject<{
395
397
  amountUsd: z.ZodNumber;
396
398
  recordCount: z.ZodNumber;
397
399
  confidence: z.ZodEnum<{
398
- verified: "verified";
399
400
  estimated: "estimated";
401
+ verified: "verified";
400
402
  detected_unverified: "detected_unverified";
401
403
  missing: "missing";
402
404
  }>;
@@ -406,8 +408,8 @@ export declare const spendSummarySchema: z.ZodObject<{
406
408
  amountUsd: z.ZodNumber;
407
409
  recordCount: z.ZodNumber;
408
410
  confidence: z.ZodEnum<{
409
- verified: "verified";
410
411
  estimated: "estimated";
412
+ verified: "verified";
411
413
  detected_unverified: "detected_unverified";
412
414
  missing: "missing";
413
415
  }>;
@@ -417,8 +419,8 @@ export declare const spendSummarySchema: z.ZodObject<{
417
419
  amountUsd: z.ZodNumber;
418
420
  recordCount: z.ZodNumber;
419
421
  confidence: z.ZodEnum<{
420
- verified: "verified";
421
422
  estimated: "estimated";
423
+ verified: "verified";
422
424
  detected_unverified: "detected_unverified";
423
425
  missing: "missing";
424
426
  }>;
@@ -433,8 +435,8 @@ export declare const spendSummarySchema: z.ZodObject<{
433
435
  shareOfSpend: z.ZodNumber;
434
436
  recordCount: z.ZodNumber;
435
437
  confidence: z.ZodEnum<{
436
- verified: "verified";
437
438
  estimated: "estimated";
439
+ verified: "verified";
438
440
  detected_unverified: "detected_unverified";
439
441
  missing: "missing";
440
442
  }>;
@@ -455,8 +457,8 @@ export declare const spendSummarySchema: z.ZodObject<{
455
457
  currentAmountUsd: z.ZodNumber;
456
458
  multiplier: z.ZodNumber;
457
459
  confidence: z.ZodEnum<{
458
- verified: "verified";
459
460
  estimated: "estimated";
461
+ verified: "verified";
460
462
  detected_unverified: "detected_unverified";
461
463
  missing: "missing";
462
464
  }>;
@@ -474,8 +476,8 @@ export declare const spendSummarySchema: z.ZodObject<{
474
476
  }>;
475
477
  estimatedImpactUsd: z.ZodNumber;
476
478
  confidence: z.ZodEnum<{
477
- verified: "verified";
478
479
  estimated: "estimated";
480
+ verified: "verified";
479
481
  detected_unverified: "detected_unverified";
480
482
  missing: "missing";
481
483
  }>;
@@ -512,8 +514,8 @@ export declare const spendSummarySchema: z.ZodObject<{
512
514
  affectedModels: z.ZodArray<z.ZodString>;
513
515
  estimatedImpactUsd: z.ZodNumber;
514
516
  confidence: z.ZodEnum<{
515
- verified: "verified";
516
517
  estimated: "estimated";
518
+ verified: "verified";
517
519
  detected_unverified: "detected_unverified";
518
520
  missing: "missing";
519
521
  }>;
package/dist/schema.js CHANGED
@@ -11,7 +11,15 @@ export const spendSourceSchema = z.object({
11
11
  name: z.string().min(1),
12
12
  provider: z.string().min(1),
13
13
  confidence: costConfidenceSchema,
14
- observedFrom: z.string().min(1)
14
+ observedFrom: z.string().min(1),
15
+ /**
16
+ * Stable per-account identity within one provider (an organization, team,
17
+ * or enterprise slice). Derived from the user-chosen credential reference
18
+ * or an explicit --org/--enterprise/--account-id flag — never from secret
19
+ * material. Absent on local-agent records and on provider records synced
20
+ * before multi-account support (treated as one unnamed legacy slice).
21
+ */
22
+ account: z.string().min(1).optional()
15
23
  });
16
24
  /**
17
25
  * What one normalized usage record represents.