ai-spend-agent 0.9.10 → 0.9.11

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/dist/index.js CHANGED
@@ -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")
@@ -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",
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-spend-agent",
3
- "version": "0.9.10",
3
+ "version": "0.9.11",
4
4
  "funding": "https://asktilden.com",
5
5
  "description": "Local-first financial accountability CLI: Claude Code/Codex attribution, provenance, and next actions, plus experimental Gemini CLI cost evidence.",
6
6
  "type": "module",
@@ -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.11",
59
+ "@agent-finops/report": "0.9.11",
60
60
  "yocto-spinner": "^1.2.0"
61
61
  }
62
62
  }