ai-spend-agent 0.9.10 → 0.9.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/README.md CHANGED
@@ -1,58 +1,80 @@
1
- # ai-spend-agent
1
+ # ai-spend-agent — Tilden’s free AI spend CLI
2
2
 
3
- The full [aibill](https://github.com/futurastudio/ai-spend-agent) CLI.
3
+ Inspect Claude Code and Codex activity locally, see its estimated value at
4
+ published API rates, and identify what deserves a closer look. Optional provider
5
+ reports add billing evidence; estimates and reported costs stay separate.
4
6
 
5
7
  ```bash
6
- npx aibill init
7
- npx aibill statusline install # optional Claude Code cache-only status line
8
- npx ai-spend-agent
9
- # short alias
10
- npx aibill
8
+ npx aibill@latest init
9
+ npx aibill@latest --full
11
10
  ```
12
11
 
13
- Run `npx aibill init` from a project to detect machine-wide Claude Code, Codex,
14
- and experimental Gemini CLI financial evidence, print the first
15
- evidence-labeled personal receipt, and seed a private Claude/Codex aggregate
16
- cache under `~/.aibill/cache/`. Gemini financial rows come only from
17
- `~/.gemini/tmp/<opaque-project-id>/chats/**/*.{json,jsonl}`; `logs.json` is a
18
- presence signal and never a financial source. Init never replaces missing personal
19
- evidence with the bundled sample and never overwrites existing connected
20
- source or audit state.
21
-
22
- The optional status line is explicit and reversible. It installs a standalone
23
- Node-builtins-only runner at Claude user scope, rereads only the private
24
- aggregate cache, and never scans transcripts or contacts a provider from the
25
- hook. Subscription runway appears only when it was transcript-reported; `~`
26
- means API-equivalent value, and untilded `billed` money requires verified
27
- provider evidence. Remove it with `npx aibill statusline uninstall`.
28
-
29
- It reads supported local Claude Code, Codex, and Gemini CLI financial metadata,
30
- labels API-equivalent estimates, and can optionally add official OpenAI or
31
- Anthropic provider-reported cost
32
- through an environment-variable reference.
12
+ Requires Node.js 22+. Free and MIT-licensed; no account or provider key is
13
+ needed for local analysis. `aibill` and `ai-spend-agent` run the same CLI.
14
+ Run from a project directory: `init` reviews the last 30 days of supported
15
+ machine-wide records, uses that project for local state, and seeds the private
16
+ Claude Code/Codex status cache. It does not change Claude settings or connect
17
+ Workspace. Missing evidence stays missing; `--sample` explicitly selects a demo.
18
+
19
+ Local values are API-equivalent estimates, not subscription charges or invoices.
20
+ Claude Code/Codex readers are live-verified; Gemini CLI is experimental and
21
+ fixture-verified, supported only in financial CLI/report/local MCP surfaces.
22
+ Gemini does not feed statusline, Glance, Context Health, Apply or plan/runway.
23
+ `npx aibill@latest doctor --sources` shows reader validation separately from
24
+ each number’s financial label and coverage.
25
+
26
+ ## Choose a next step
27
+
28
+ - `npx aibill@latest context`: inspect context evidence.
29
+ - `npx aibill@latest improve`: guide one reversible token experiment; a result
30
+ requires matched completed sessions and user-declared quality, not a claim
31
+ of cash savings or ROI.
32
+ - `npx aibill@latest statusline install`: optionally install the cache-only
33
+ Claude Code status line. Remove it with `statusline uninstall`.
34
+ - `npx aibill@latest connect openai` or `connect anthropic`: register local
35
+ provider setup and print the separate `sync-provider` command. Only an
36
+ explicit sync reads the provider, using the required admin environment
37
+ credential reference. Cursor/Copilot connectors remain fixture-verified beta
38
+ pending real-account acceptance.
39
+
40
+ ## Use standalone or connect to Workspace
41
+
42
+ Use the free CLI on its own, or keep working locally and connect it to your
43
+ invited [Tilden Workspace](https://asktilden.com/docs/workspace) for a shared
44
+ spending review. Start with `npx aibill@latest workspace connect`, complete the
45
+ browser enrollment and sharing consent, then run
46
+ `npx aibill@latest workspace push` to preview and approve a share. The browser
47
+ does not need to stay open for later pushes while the machine’s grant and
48
+ sharing consent remain valid. Shared facts include eligible
49
+ Claude Code/Codex sessions, tokens and project labels where available, plus
50
+ hashed identities; they exclude raw prompts, transcript contents, full paths,
51
+ raw session IDs and dollar amounts. Local provider records are not uploaded.
52
+ Hosted provider connections are set up separately.
53
+
54
+ Read [Workspace setup and limits](https://github.com/futurastudio/ai-spend-agent/blob/main/docs/WORKSPACE.md)
55
+ before pairing. [Join the Tilden waitlist](https://asktilden.com/?ref=npm-cli#beta)
56
+ for updates and access invitations; joining does not grant immediate access.
57
+
58
+ ## Privacy
59
+
60
+ Default transcript analysis runs locally. Reports remain local until shared.
61
+ Explicit Workspace pushes send only the approved facts. Local MCP results go
62
+ to the AI client you configure and follow its data policy. Explicit provider
63
+ sync uses the referenced credential only with that provider’s read-only API;
33
64
  aibill never sits in the inference path and never stores, prints, or proxies provider credentials.
65
+ Workspace pairing uses separate private local device credentials.
66
+
67
+ **Telemetry: command counts, disclosed at first run.** Events begin
68
+ only after the first interactive notice. They include a random installation ID
69
+ and operational fields. Events can be linked to the same installation, but do
70
+ not include your arguments, paths, prompts, project names,
71
+ dollar amounts or email. `npx aibill telemetry off` disables them; so do
72
+ `DO_NOT_TRACK`, `CI` or `AI_SPEND_NO_TELEMETRY` when non-empty.
73
+ `npx aibill telemetry` shows the exact last payload.
74
+ [Full telemetry disclosure](https://github.com/futurastudio/ai-spend-agent/blob/main/docs/TELEMETRY.md).
75
+ The optional signup command sends its displayed email/ref payload only after
76
+ confirmation; `outcome github` explicitly uses the installed `gh` CLI.
34
77
 
35
- **Telemetry: anonymous command counts, disclosed at first run.** Terminal
36
- analysis stays on your machine — transcripts, prompts, file names, and dollar
37
- amounts are never uploaded. Separately, the CLI counts *which* commands run:
38
- anonymous, never your arguments, paths, content, dollars, or your email.
39
- Nothing is sent before a one-time notice has been printed on an interactive
40
- run, so a machine that has never seen that notice (CI, scripts, pipes) never
41
- sends anything. One command turns it off — `aibill telemetry off` (or
42
- `DO_NOT_TRACK`, `CI`, or `AI_SPEND_NO_TELEMETRY`) — `aibill telemetry` prints
43
- the exact last payload verbatim, and while it is on every receipt says so in
44
- place of "nothing uploaded". Full scope and the never-list:
45
- [`docs/TELEMETRY.md`](https://github.com/futurastudio/ai-spend-agent/blob/main/docs/TELEMETRY.md).
46
-
47
- Connector validation (`live_verified`, `fixture_verified`, `untested`, or
48
- `failed`) and each number's financial evidence (`verified`, `estimated`,
49
- `detected_unverified`, or `missing`) are separate status axes. Run `npx aibill
50
- doctor --sources` to see both with freshness and the last sanitized error.
51
- The source registry also records read-boundary approval separately; an approved
52
- local folder is permission to scan, not verified financial evidence.
53
-
54
- See the repository
55
- [README](https://github.com/futurastudio/ai-spend-agent#readme) for commands,
56
- privacy boundaries, supported sources, and public-beta limitations.
57
-
58
- MIT licensed.
78
+ [Full guide](https://github.com/futurastudio/ai-spend-agent#readme) ·
79
+ [Source coverage](https://asktilden.com/docs/sources) ·
80
+ [Command reference](https://github.com/futurastudio/ai-spend-agent/blob/main/docs/CLI_REFERENCE.md)
package/dist/index.js CHANGED
@@ -592,7 +592,7 @@ function quickstartNextSteps(mode, detected) {
592
592
  command: "npx aibill --group-by project",
593
593
  description: "see which project has the most observed activity"
594
594
  });
595
- steps.push("Need team reconciliation, allocation, budgets, and approvals? Workspace design partners: https://asktilden.com");
595
+ steps.push("Review supported AI spend in Tilden Workspace with our team’s help. The pilot has one owner seat. CLI sharing is optional and explicit; register interest: https://asktilden.com/?ref=aibill-workspace#beta");
596
596
  if (mode === "demo") {
597
597
  // Static pointer only — sample output is built for recordings and
598
598
  // screenshots, so it never prompts (capture design moments map).
@@ -1098,14 +1098,17 @@ async function workspaceCommand(args, runtime) {
1098
1098
  : fail("Pairing outcome is unknown. Do not replay this response bundle; inspect and revoke the enrollment in Settings before starting another.");
1099
1099
  }
1100
1100
  if (action === "disconnect") {
1101
- const result = await disconnectWorkspace({ home: runtime.homeDirectory, transport: runtime.workspaceDisconnectTransport,
1101
+ const result = await disconnectWorkspace({ home: runtime.homeDirectory, retry: args.workspaceDisconnectRetry, transport: runtime.workspaceDisconnectTransport,
1102
1102
  confirm: action => consent(action === "abandon_unexchanged"
1103
1103
  ? "This native request has never attempted exchange. Destroy its unused private pairing key so it can no longer complete enrollment? Cancel any browser challenge in Settings as well. [y/N] "
1104
- : "Revoke this machine's Workspace grant and remove its local pairing after the accepted receipt? [y/N] ") });
1105
- return result.state === "revoked" ? ok("Workspace revoked this machine grant. Local pairing was removed.")
1106
- : result.state === "abandoned" ? ok("Unused native pairing key removed. No remote revocation was claimed. Run npx aibill workspace connect to start a new request.")
1107
- : result.state === "cancelled" ? fail("Pairing kept. Disconnect was not confirmed; no request or key was removed.") : result.state === "not_paired" ? ok("No completed local pairing exists.")
1108
- : fail(`Disconnect outcome is unknown. Local keys remain; no retry was sent. Reconcile this machine at ${WORKSPACE_ORIGIN}/settings/machines.`);
1104
+ : action === "retry_revoke"
1105
+ ? "Repeat the identical retained disconnect request once? If already revoked, Tilden returns that outcome; otherwise this revokes the same grant. No session facts are sent. Local pairing is removed only after a matching receipt. [y/N] "
1106
+ : "Revoke this machine's Workspace grant and remove its local pairing after the accepted receipt? [y/N] ") });
1107
+ return result.state === "retry_not_pending" ? fail("No uncertain disconnect is retained. Run workspace disconnect without --retry to start a new disconnect.")
1108
+ : result.state === "revoked" ? ok("Workspace revoked this machine grant. Local pairing was removed.")
1109
+ : result.state === "abandoned" ? ok("Unused native pairing key removed. No remote revocation was claimed. Run npx aibill workspace connect to start a new request.")
1110
+ : result.state === "cancelled" ? fail("Pairing kept. Disconnect was not confirmed; no request or key was removed.") : result.state === "not_paired" ? ok("No completed local pairing exists.")
1111
+ : fail(`Disconnect outcome is unknown. Local keys remain and uploads are blocked. Run npx aibill@latest workspace disconnect --retry to review and explicitly repeat the identical retained request once, or inspect this machine at ${WORKSPACE_ORIGIN}/settings/machines.`);
1109
1112
  }
1110
1113
  const status = await workspaceStatus(runtime.homeDirectory);
1111
1114
  if (status.state !== "connected")
@@ -1253,7 +1256,7 @@ async function signupCommand(args, runtime) {
1253
1256
  };
1254
1257
  }
1255
1258
  /**
1256
- * `aibill telemetry [on|off]` — inspect or switch anonymous command-count
1259
+ * `aibill telemetry [on|off]` — inspect or switch installation-linked command-count
1257
1260
  * telemetry. Status shows the EXACT last payload verbatim; state is
1258
1261
  * fail-closed (corrupt/readonly ⇒ off).
1259
1262
  */
@@ -1312,8 +1315,8 @@ async function telemetryCommand(args, runtime) {
1312
1315
  };
1313
1316
  }
1314
1317
  return ok([
1315
- "telemetry on · anonymous command counts only",
1316
- `counted: command name, version, os, arch, ci flag, duration bucket, ok flag, timestamp`,
1318
+ "telemetry on · installation-linked command counts only",
1319
+ `counted: random installation ID, command name, version, os, arch, ci flag, duration bucket, ok flag, timestamp`,
1317
1320
  "never: arguments, paths, file contents, project names, or your email",
1318
1321
  "events start with your next run · see payloads anytime: npx aibill telemetry"
1319
1322
  ].join("\n"));
@@ -1335,12 +1338,12 @@ async function telemetryCommand(args, runtime) {
1335
1338
  else {
1336
1339
  lines.push(`status: on · noticed ${read.state.noticedAt}`);
1337
1340
  }
1338
- lines.push("counted: command name, version, os, arch, ci flag, duration bucket, ok flag, timestamp", "never: arguments, paths, file contents, project names, or your email");
1341
+ lines.push("counted: random installation ID, command name, version, os, arch, ci flag, duration bucket, ok flag, timestamp", "never: arguments, paths, file contents, project names, or your email");
1339
1342
  if (read.kind === "ok" && read.state.lastPayload !== undefined) {
1340
- lines.push("last payload sent (verbatim):", read.state.lastPayload);
1343
+ lines.push("last attempted payload (verbatim; delivery unconfirmed):", read.state.lastPayload);
1341
1344
  }
1342
1345
  else {
1343
- lines.push("last payload sent: none");
1346
+ lines.push("last attempted payload: none");
1344
1347
  }
1345
1348
  lines.push("switch: npx aibill telemetry on · npx aibill telemetry off");
1346
1349
  return ok(lines.join("\n"));
@@ -3398,10 +3401,10 @@ const providerAdminEnvHint = {
3398
3401
  };
3399
3402
  function providerSyncSetupCommand(provider, adminRef) {
3400
3403
  if (provider === "cursor") {
3401
- return `npx aibill sync-provider --provider cursor --auth-reference ${adminRef} --account-id <team-label>`;
3404
+ return `npx aibill sync-provider --provider cursor --auth-reference ${adminRef} --account-id '<team-label>'`;
3402
3405
  }
3403
3406
  if (provider === "github-copilot") {
3404
- return `npx aibill sync-provider --provider github-copilot --auth-reference ${adminRef} --org <organization>`;
3407
+ return `npx aibill sync-provider --provider github-copilot --auth-reference ${adminRef} --org '<organization>'`;
3405
3408
  }
3406
3409
  const thirtyDaysAgoUnix = Math.floor(Date.now() / 1_000) - 30 * 24 * 60 * 60;
3407
3410
  return `npx aibill sync-provider --provider ${provider} --auth-reference ${adminRef} --start-time ${thirtyDaysAgoUnix}`;
@@ -6756,8 +6759,12 @@ function parseArgs(argv) {
6756
6759
  parsed.workspaceRestart = true;
6757
6760
  rest.shift();
6758
6761
  }
6762
+ if (parsed.workspaceAction === "disconnect" && rest.length === 1 && rest[0] === "--retry") {
6763
+ parsed.workspaceDisconnectRetry = true;
6764
+ rest.shift();
6765
+ }
6759
6766
  if (rest.length)
6760
- parsed.parseErrors.push("workspace accepts only connect [response-bundle], connect --restart, push, status, or disconnect");
6767
+ parsed.parseErrors.push("workspace accepts only connect [response-bundle], connect --restart, push, status, or disconnect [--retry]");
6761
6768
  }
6762
6769
  if (command === "statusline" && rest[0] && !rest[0].startsWith("--")) {
6763
6770
  parsed.statuslineAction = rest.shift();
@@ -7429,6 +7436,7 @@ function helpText(telemetryDisclosure) {
7429
7436
  " npx aibill workspace push Preview and send local session facts",
7430
7437
  " npx aibill workspace status Read local pairing/pending status only",
7431
7438
  " npx aibill workspace disconnect Revoke the grant or abandon an unused pairing key",
7439
+ " npx aibill workspace disconnect --retry Confirm one identical uncertain disconnect attempt",
7432
7440
  "",
7433
7441
  "Add official provider-reported cost (ADMIN/owner-gated):",
7434
7442
  " npx aibill connect openai Requires an org-owner Admin credential reference",
@@ -7453,7 +7461,7 @@ function helpText(telemetryDisclosure) {
7453
7461
  " statusline expand Print every subscription with committed price, runways, and 7d API-equivalent",
7454
7462
  " signup <email> [--ref <token>] Join the launch list · email only · the exact payload is shown before send",
7455
7463
  " [--never] Never ask again (nothing is sent) [--forget] Clear local signup state",
7456
- " telemetry [on|off] Show anonymous command-count status + the exact last payload · switch it",
7464
+ " telemetry [on|off] Show installation-linked command-count status + the exact last payload · switch it",
7457
7465
  " doctor [--sources] Launch diagnostics; --sources shows validation, evidence, freshness, and errors",
7458
7466
  " reset [--path <dir>] Clear persisted spend state (so sample state can't mask real logs)",
7459
7467
  " --ignore-state On the default/quickstart run, ignore persisted spend.json for this run",
@@ -1,6 +1,6 @@
1
1
  import { spawn } from "node:child_process";
2
2
  /**
3
- * CLI telemetry — anonymous command counts, notice-before-first-byte.
3
+ * CLI telemetry — installation-linked command counts, notice-before-first-byte.
4
4
  *
5
5
  * Consent model (founder decision 2026-08-24, disclosed opt-out):
6
6
  * - Run 1 (interactive) prints a three-line notice AFTER the output and
@@ -55,7 +55,7 @@ export type TelemetryEvent = {
55
55
  * incident — "command not found").
56
56
  */
57
57
  export declare const telemetryDisclosureLine: string;
58
- export declare const telemetryNoticeLines: readonly ["aibill counts which commands run — anonymous, never your data or content", `turn off: ${string}`, `see payloads: ${string}`];
58
+ export declare const telemetryNoticeLines: readonly ["aibill counts commands by random installation ID — no arguments, paths or content", `turn off: ${string}`, `see payloads: ${string}`];
59
59
  export declare function telemetryOsLabel(platform?: string): TelemetryOs;
60
60
  export declare function telemetryArchLabel(arch?: string): TelemetryArch;
61
61
  export declare function telemetryDurationBucket(durationMs: number): TelemetryDurationBucket;
package/dist/telemetry.js CHANGED
@@ -5,7 +5,7 @@ import { homedir } from "node:os";
5
5
  import { dirname, join } from "node:path";
6
6
  import { aibillCommandV0 } from "@agent-finops/core";
7
7
  /**
8
- * CLI telemetry — anonymous command counts, notice-before-first-byte.
8
+ * CLI telemetry — installation-linked command counts, notice-before-first-byte.
9
9
  *
10
10
  * Consent model (founder decision 2026-08-24, disclosed opt-out):
11
11
  * - Run 1 (interactive) prints a three-line notice AFTER the output and
@@ -69,9 +69,9 @@ export const telemetryCommands = [
69
69
  * npx form): npx users have no bare `aibill` on PATH (0.9.2 founder
70
70
  * incident — "command not found").
71
71
  */
72
- export const telemetryDisclosureLine = `anonymous command counts shared · ${aibillCommandV0("telemetry off")}`;
72
+ export const telemetryDisclosureLine = `installation-linked command counts shared · ${aibillCommandV0("telemetry off")}`;
73
73
  export const telemetryNoticeLines = [
74
- "aibill counts which commands run — anonymous, never your data or content",
74
+ "aibill counts commands by random installation ID — no arguments, paths or content",
75
75
  `turn off: ${aibillCommandV0("telemetry off")}`,
76
76
  `see payloads: ${aibillCommandV0("telemetry")}`
77
77
  ];
@@ -318,7 +318,7 @@ export async function openCliTelemetry(options = {}) {
318
318
  return;
319
319
  // `glance` is a machine-invoked poll (the Glance menu-bar app spawns
320
320
  // it every ~30s), not a human command — counting it is noise by
321
- // definition and would flood the anonymous command counts (~2,880
321
+ // definition and would flood the installation-linked command counts (~2,880
322
322
  // events/day/user). Never emit for it, notice or not; the label
323
323
  // still exists in the map so any stray event is at least honest.
324
324
  if (telemetryCommandForArgv(input.argv) === "glance")
@@ -227,13 +227,14 @@ export declare function finishWorkspaceEnrollment(options: {
227
227
  }): Promise<{
228
228
  state: "connected" | "cancelled" | "unconfirmed";
229
229
  }>;
230
- /** Revocation is one native attempt. Unknown outcomes retain keys and block further writes. */
230
+ /** One native attempt per confirmation. Explicit recovery repeats the retained proof; unknown outcomes keep writes blocked. */
231
231
  export declare function disconnectWorkspace(options: {
232
232
  home?: string;
233
- confirm: (action: "revoke" | "abandon_unexchanged") => Promise<boolean>;
233
+ retry?: boolean;
234
+ confirm: (action: "revoke" | "retry_revoke" | "abandon_unexchanged") => Promise<boolean>;
234
235
  transport?: WorkspaceDisconnectTransport;
235
236
  }): Promise<{
236
- state: "revoked" | "abandoned" | "cancelled" | "unconfirmed" | "not_paired";
237
+ state: "revoked" | "abandoned" | "cancelled" | "unconfirmed" | "not_paired" | "retry_not_pending";
237
238
  }>;
238
239
  export {};
239
240
  //# sourceMappingURL=workspaceConnect.d.ts.map
@@ -668,9 +668,11 @@ export async function finishWorkspaceEnrollment(options) {
668
668
  }
669
669
  });
670
670
  }
671
- /** Revocation is one native attempt. Unknown outcomes retain keys and block further writes. */
671
+ /** One native attempt per confirmation. Explicit recovery repeats the retained proof; unknown outcomes keep writes blocked. */
672
672
  export async function disconnectWorkspace(options) {
673
673
  return withState(options.home ?? homedir(), async (state, save, path) => {
674
+ if (options.retry && state?.disconnect?.state !== "uncertain")
675
+ return { state: "retry_not_pending" };
674
676
  if (!state) {
675
677
  const enrollment = await readEnrollment(path);
676
678
  if (!enrollment)
@@ -692,14 +694,14 @@ export async function disconnectWorkspace(options) {
692
694
  }
693
695
  return { state: "abandoned" };
694
696
  }
695
- if (state.disconnect?.state === "uncertain")
697
+ if (state.disconnect?.state === "uncertain" && !options.retry)
696
698
  return { state: "unconfirmed" };
697
- if (!await options.confirm("revoke"))
699
+ if (!await options.confirm(state.disconnect?.state === "uncertain" ? "retry_revoke" : "revoke"))
698
700
  return { state: "cancelled" };
699
- if (!state.disconnect) {
701
+ if (state.disconnect?.state !== "revoked") {
700
702
  const proof = { purpose: "machine_disconnect_v1", grantId: state.device.grantId,
701
703
  grantRevision: state.device.grantRevision, connectionEpoch: state.device.connectionEpoch,
702
- requestId: `oref_${randomBytes(32).toString("base64url")}` };
704
+ requestId: state.disconnect?.requestId ?? `oref_${randomBytes(32).toString("base64url")}` };
703
705
  const body = { ...proof, publicKey: createPublicKey(state.device.privateKeyPem).export({ format: "jwk" }).x,
704
706
  signature: sign(null, Buffer.from(workspaceCanonicalJson(proof)), state.device.privateKeyPem).toString("base64url") };
705
707
  const uncertain = { ...state, disconnect: { state: "uncertain", requestId: proof.requestId, revokedAt: null } };
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "ai-spend-agent",
3
- "version": "0.9.10",
3
+ "version": "0.9.12",
4
4
  "funding": "https://asktilden.com",
5
- "description": "Local-first financial accountability CLI: Claude Code/Codex attribution, provenance, and next actions, plus experimental Gemini CLI cost evidence.",
5
+ "description": "Tilden\u2019s free CLI for local coding-agent usage and optional provider cost reports. Estimates, reported costs and missing coverage stay separate.",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
8
8
  "types": "./dist/index.d.ts",
@@ -55,8 +55,8 @@
55
55
  "prepack": "npm run build"
56
56
  },
57
57
  "dependencies": {
58
- "@agent-finops/core": "0.9.10",
59
- "@agent-finops/report": "0.9.10",
58
+ "@agent-finops/core": "0.9.12",
59
+ "@agent-finops/report": "0.9.12",
60
60
  "yocto-spinner": "^1.2.0"
61
61
  }
62
62
  }