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 +73 -51
- package/dist/index.js +25 -17
- package/dist/telemetry.d.ts +2 -2
- package/dist/telemetry.js +4 -4
- package/dist/workspaceConnect.d.ts +4 -3
- package/dist/workspaceConnect.js +7 -5
- package/package.json +4 -4
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
|
-
|
|
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
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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("
|
|
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
|
-
:
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
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
|
|
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 ·
|
|
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
|
|
1343
|
+
lines.push("last attempted payload (verbatim; delivery unconfirmed):", read.state.lastPayload);
|
|
1341
1344
|
}
|
|
1342
1345
|
else {
|
|
1343
|
-
lines.push("last payload
|
|
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
|
|
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",
|
package/dist/telemetry.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
/**
|
|
3
|
-
* CLI telemetry —
|
|
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
|
|
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 —
|
|
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 = `
|
|
72
|
+
export const telemetryDisclosureLine = `installation-linked command counts shared · ${aibillCommandV0("telemetry off")}`;
|
|
73
73
|
export const telemetryNoticeLines = [
|
|
74
|
-
"aibill counts
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
package/dist/workspaceConnect.js
CHANGED
|
@@ -668,9 +668,11 @@ export async function finishWorkspaceEnrollment(options) {
|
|
|
668
668
|
}
|
|
669
669
|
});
|
|
670
670
|
}
|
|
671
|
-
/**
|
|
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 (
|
|
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.
|
|
3
|
+
"version": "0.9.12",
|
|
4
4
|
"funding": "https://asktilden.com",
|
|
5
|
-
"description": "
|
|
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.
|
|
59
|
-
"@agent-finops/report": "0.9.
|
|
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
|
}
|