@vultisig/cli 1.8.10 → 2.0.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.
Files changed (3) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/index.js +110 -10
  3. package/package.json +5 -5
package/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # @vultisig/cli
2
2
 
3
+ ## 2.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - [#682](https://github.com/vultisig/vultisig-sdk/pull/682) [`341245e`](https://github.com/vultisig/vultisig-sdk/commit/341245e2d6d49348e38d61bc42805c35ac8d3052) Thanks [@neavra](https://github.com/neavra)! - Gate signing in `agent ask` mode behind explicit confirmation (security fix, **breaking**).
8
+
9
+ Previously `vsig agent ask` auto-signed and broadcast any transaction envelope the
10
+ backend returned, gated only by whether a password was present. Because the backend
11
+ routes read-only swap intents (e.g. "list swap routes from USDC to ETH") to the
12
+ fund-moving `execute_swap` tool, a query could broadcast a real on-chain swap.
13
+
14
+ `runPasswordGatedTool` now calls `ui.requestConfirmation` before any `sign_tx` /
15
+ `sign_typed_data` (the single chokepoint for both the tx_ready path and client-side
16
+ dispatch, covering both legs of a multi-leg swap). In ask mode this defaults to
17
+ **deny**: signing/broadcast now requires the new `agent ask --yes` flag. Without it,
18
+ the proposed transaction is reported (`CONFIRMATION_REQUIRED`) and nothing is signed.
19
+ Interactive (TUI) and pipe (`--via-agent`) modes already prompt/defer for confirmation.
20
+
21
+ **BREAKING — migration for unattended pipelines:** any automation that relied on
22
+ `agent ask` auto-signing must now pass `--yes`. A denied signing still exits **0**
23
+ (a misrouted read-only prompt remains a successful query); detect it via the new
24
+ top-level `confirmation_required: true` field in `--output json` mode, the
25
+ `confirmation-required:` line in text mode, or `tool_calls[].code ===
26
+ "CONFIRMATION_REQUIRED"`. Do not infer "broadcast happened" from exit code alone —
27
+ check the `transactions` array. With `--yes`, each authorization is logged to stderr
28
+ (`[confirm] auto-approved (--yes): <summary>`).
29
+
30
+ ### Patch Changes
31
+
32
+ - Updated dependencies [[`dc75595`](https://github.com/vultisig/vultisig-sdk/commit/dc75595e83360f5bda84b2d91cae177bc7c8c966)]:
33
+ - @vultisig/sdk@2.0.0
34
+ - @vultisig/rujira@33.0.0
35
+ - @vultisig/client-shared@0.2.15
36
+
3
37
  ## 1.8.10
4
38
 
5
39
  ### Patch Changes
package/dist/index.js CHANGED
@@ -4797,12 +4797,14 @@ function normalizeAgentError(err) {
4797
4797
  var AskInterface = class {
4798
4798
  session;
4799
4799
  verbose;
4800
+ autoApprove;
4800
4801
  responseParts = [];
4801
4802
  toolCalls = [];
4802
4803
  transactions = [];
4803
- constructor(session, verbose = false) {
4804
+ constructor(session, verbose = false, autoApprove = false) {
4804
4805
  this.session = session;
4805
4806
  this.verbose = verbose;
4807
+ this.autoApprove = autoApprove;
4806
4808
  }
4807
4809
  /**
4808
4810
  * Get UI callbacks that silently collect results.
@@ -4850,8 +4852,15 @@ var AskInterface = class {
4850
4852
  requestPassword: async () => {
4851
4853
  throw new Error("Password required but not provided. Use --password flag.");
4852
4854
  },
4853
- requestConfirmation: async (_message) => {
4854
- return true;
4855
+ requestConfirmation: async (message) => {
4856
+ if (!this.autoApprove) {
4857
+ process.stderr.write(`[confirm] signing requires --yes \u2014 NOT broadcasting: ${message}
4858
+ `);
4859
+ } else {
4860
+ process.stderr.write(`[confirm] auto-approved (--yes): ${message}
4861
+ `);
4862
+ }
4863
+ return this.autoApprove;
4855
4864
  }
4856
4865
  };
4857
4866
  }
@@ -6192,6 +6201,7 @@ var AgentExecutor = class {
6192
6201
  const chain2 = resolveChainFromTxReady(txReadyData) || Chain10.Ethereum;
6193
6202
  if (getChainKind(chain2) !== "evm") {
6194
6203
  this.pendingPayloads.clear();
6204
+ this.pendingLegs = [];
6195
6205
  this.pendingPayloads.set("latest", {
6196
6206
  payload: { __serverTx: true, ...txReadyData },
6197
6207
  coin: { chain: chain2, address: "", decimals: 18, ticker: "" },
@@ -6215,6 +6225,7 @@ var AgentExecutor = class {
6215
6225
  }
6216
6226
  const chain = resolveChainFromTxReady(txReadyData) || Chain10.Ethereum;
6217
6227
  this.pendingPayloads.clear();
6228
+ this.pendingLegs = [];
6218
6229
  this.pendingPayloads.set("latest", {
6219
6230
  payload: { __serverTx: true, ...txReadyData },
6220
6231
  coin: { chain, address: "", decimals: 18, ticker: "" },
@@ -6231,6 +6242,43 @@ var AgentExecutor = class {
6231
6242
  hasPendingTransaction() {
6232
6243
  return this.pendingPayloads.has("latest");
6233
6244
  }
6245
+ /**
6246
+ * Drop the buffered server tx and any staged multi-leg state. Called when
6247
+ * the user declines the pre-sign confirmation: the rejected envelope must
6248
+ * not linger (a fresh tx_ready always overwrites, but stale legs/payloads
6249
+ * would otherwise survive into later turns).
6250
+ */
6251
+ clearPendingTransaction() {
6252
+ this.pendingPayloads.clear();
6253
+ this.pendingLegs = [];
6254
+ }
6255
+ /**
6256
+ * Human-readable one-line summary of the currently-buffered server tx
6257
+ * (set by storeServerTransaction), for the pre-sign confirmation prompt.
6258
+ * Returns null when nothing is buffered (e.g. sign_typed_data, which has
6259
+ * no tx_ready payload — callers fall back to the tool input).
6260
+ */
6261
+ getPendingSummary() {
6262
+ const stored = this.pendingPayloads.get("latest");
6263
+ if (!stored) return null;
6264
+ const p = stored.payload;
6265
+ const labels = p?.resolved?.labels ?? {};
6266
+ const isSwap = !!(p?.approvalTxArgs || p?.swap_tx || labels.quote_summary || labels.to_token_symbol);
6267
+ if (isSwap) {
6268
+ const usedQuoteSummary = !!labels.quote_summary;
6269
+ const head = labels.quote_summary || `swap ${labels.amount_in ?? p?.txArgs?.amount ?? "?"} ${labels.from_token_symbol ?? ""} \u2192 ${labels.to_token_symbol ?? ""}`.trim();
6270
+ const parts = [head, `on ${stored.chain}`];
6271
+ if (!usedQuoteSummary && labels.provider) parts.push(`via ${labels.provider}`);
6272
+ if (p?.__multiLeg) parts.push("(+ token approval \u2014 2 transactions)");
6273
+ if (labels.estimated_fee) parts.push(`est. fee ${labels.estimated_fee}`);
6274
+ return parts.join(" ");
6275
+ }
6276
+ const amount = labels.resolved_amount ?? p?.txArgs?.amount ?? "?";
6277
+ const symbol = labels.token_resolved || labels.token_symbol || "";
6278
+ const amountWithSymbol = symbol && !amount.endsWith(` ${symbol}`) ? `${amount} ${symbol}` : amount;
6279
+ const to = p?.txArgs?.to || labels.recipient_echo || "?";
6280
+ return `send ${amountWithSymbol} on ${stored.chain} to ${to}`;
6281
+ }
6234
6282
  /**
6235
6283
  * Wrap a per-tool handler body with normalised success/failure → RecentAction
6236
6284
  * conversion. Replaces the legacy executeAction → ActionResult adapter that
@@ -8121,6 +8169,34 @@ var AgentSession = class {
8121
8169
  * prompt was declined).
8122
8170
  */
8123
8171
  async runPasswordGatedTool(toolName, toolCallId, ui, body, input) {
8172
+ if (PASSWORD_REQUIRED_TOOLS.has(toolName)) {
8173
+ const summary = (toolName === "sign_tx" ? this.executor.getPendingSummary() : null) ?? `${toolName}${input ? ` ${JSON.stringify(input)}` : ""}`;
8174
+ const approved = await ui.requestConfirmation(summary);
8175
+ if (!approved) {
8176
+ if (toolName === "sign_tx") {
8177
+ this.executor.clearPendingTransaction();
8178
+ }
8179
+ const declined = {
8180
+ tool: toolName,
8181
+ success: false,
8182
+ data: {
8183
+ error: "Transaction not confirmed",
8184
+ code: "CONFIRMATION_REQUIRED" /* CONFIRMATION_REQUIRED */,
8185
+ proposed: summary
8186
+ }
8187
+ };
8188
+ ui.onToolCall(toolCallId, toolName, input);
8189
+ ui.onToolResult(
8190
+ toolCallId,
8191
+ toolName,
8192
+ false,
8193
+ declined.data,
8194
+ "Transaction not confirmed",
8195
+ "CONFIRMATION_REQUIRED" /* CONFIRMATION_REQUIRED */
8196
+ );
8197
+ return declined;
8198
+ }
8199
+ }
8124
8200
  let promptedPassword;
8125
8201
  if (PASSWORD_REQUIRED_TOOLS.has(toolName) && !this.config.password) {
8126
8202
  try {
@@ -8685,20 +8761,35 @@ async function executeAgentAsk(ctx2, message, options) {
8685
8761
  profile: options.profile ?? process.env.VULTISIG_AGENT_PROFILE ?? ""
8686
8762
  };
8687
8763
  const session = new AgentSession(vault, config);
8688
- const ask = new AskInterface(session, !!config.verbose);
8764
+ const ask = new AskInterface(session, !!config.verbose, !!options.autoApprove);
8689
8765
  const callbacks = ask.getCallbacks();
8690
8766
  await session.initialize(callbacks);
8691
8767
  const result = await ask.ask(message);
8768
+ const confirmationRequired = result.toolCalls.some((tc) => tc.code === "CONFIRMATION_REQUIRED" /* CONFIRMATION_REQUIRED */);
8769
+ const proposedCall = result.toolCalls.find(
8770
+ (tc) => tc.code === "CONFIRMATION_REQUIRED" /* CONFIRMATION_REQUIRED */ && typeof tc.data?.proposed === "string"
8771
+ );
8772
+ const proposed = proposedCall?.data?.proposed;
8692
8773
  if (options.json || isJsonOutput()) {
8693
8774
  outputJson({
8694
8775
  session_id: result.sessionId,
8695
8776
  response: result.response,
8696
8777
  tool_calls: result.toolCalls,
8697
- transactions: result.transactions
8778
+ transactions: result.transactions,
8779
+ ...confirmationRequired ? { confirmation_required: true } : {},
8780
+ ...proposed ? { proposed } : {}
8698
8781
  });
8699
8782
  } else {
8700
8783
  process.stdout.write(`session:${result.sessionId}
8701
8784
  `);
8785
+ if (confirmationRequired) {
8786
+ process.stdout.write(`confirmation-required:pass --yes to authorize signing
8787
+ `);
8788
+ if (proposed) {
8789
+ process.stdout.write(`proposed:${proposed}
8790
+ `);
8791
+ }
8792
+ }
8702
8793
  if (result.response) {
8703
8794
  process.stdout.write(`
8704
8795
  ${result.response}
@@ -8812,7 +8903,7 @@ var cachedVersion = null;
8812
8903
  function getVersion() {
8813
8904
  if (cachedVersion) return cachedVersion;
8814
8905
  if (true) {
8815
- cachedVersion = "1.8.10";
8906
+ cachedVersion = "2.0.0";
8816
8907
  return cachedVersion;
8817
8908
  }
8818
8909
  try {
@@ -11532,13 +11623,21 @@ var agentCmd = program.command("agent").description("AI-powered chat interface f
11532
11623
  });
11533
11624
  }
11534
11625
  );
11535
- agentCmd.command("ask <message>").description("Send a single message and get the response (for AI agent integration)").option("--session <id>", "Continue an existing conversation").option("--backend-url <url>", "Agent backend URL (default: https://abe.vultisig.com)").option("--password <password>", "Vault password for signing operations").option("--verbose", "Show tool calls and debug info on stderr").option("--json", "Output structured JSON (deprecated: use --output json)").option("--profile <api_id>", "Billing profile slug sent as X-Vultisig-Abe-Profile header").addHelpText(
11626
+ agentCmd.command("ask <message>").description("Send a single message and get the response (for AI agent integration)").option("--session <id>", "Continue an existing conversation").option("--backend-url <url>", "Agent backend URL (default: https://abe.vultisig.com)").option("--password <password>", "Vault password for signing operations").option("--verbose", "Show tool calls and debug info on stderr").option("--json", "Output structured JSON (deprecated: use --output json)").option("--profile <api_id>", "Billing profile slug sent as X-Vultisig-Abe-Profile header").option(
11627
+ "--yes",
11628
+ "Auto-approve signing/broadcast. Required for unattended signing; default is to NOT broadcast and report the proposed transaction instead."
11629
+ ).addHelpText(
11536
11630
  "after",
11537
11631
  `
11538
11632
  Examples:
11539
11633
  vultisig agent ask "What is my ETH balance?" --output json
11540
- vultisig agent ask "Send 0.1 ETH to 0x..." --session abc123
11541
- vultisig agent ask "..." --profile station-wallet`
11634
+ vultisig agent ask "Send 0.1 ETH to 0x..." --session abc123 --yes
11635
+ vultisig agent ask "..." --profile station-wallet
11636
+
11637
+ Signing safety:
11638
+ Without --yes, ask mode never signs or broadcasts \u2014 it reports the proposed
11639
+ transaction so a read-only prompt can't move funds. Pass --yes to opt in to
11640
+ unattended signing.`
11542
11641
  ).action(
11543
11642
  async (message, options) => {
11544
11643
  const parentOpts = agentCmd.opts();
@@ -11548,7 +11647,8 @@ Examples:
11548
11647
  backendUrl: options.backendUrl || parentOpts.backendUrl,
11549
11648
  password: options.password || parentOpts.password,
11550
11649
  verbose: options.verbose || parentOpts.verbose,
11551
- profile: options.profile ?? parentOpts.profile
11650
+ profile: options.profile ?? parentOpts.profile,
11651
+ autoApprove: options.yes
11552
11652
  });
11553
11653
  }
11554
11654
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vultisig/cli",
3
- "version": "1.8.10",
3
+ "version": "2.0.0",
4
4
  "description": "The self-custody MPC wallet CLI for AI coding agents (Claude Code, Cursor, OpenCode). Natural-language agent mode, 36+ chains, DKLS23 threshold signatures. Seedless.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -73,10 +73,10 @@
73
73
  "@cosmjs/stargate": "^0.39.0",
74
74
  "@napi-rs/keyring": "^1.3.0",
75
75
  "@noble/hashes": "^2.2.0",
76
- "@vultisig/client-shared": "^0.2.14",
77
- "@vultisig/core-chain": "^2.14.1",
78
- "@vultisig/rujira": "^32.0.1",
79
- "@vultisig/sdk": "^1.8.10",
76
+ "@vultisig/client-shared": "^0.2.15",
77
+ "@vultisig/core-chain": "^2.15.0",
78
+ "@vultisig/rujira": "^33.0.0",
79
+ "@vultisig/sdk": "^2.0.0",
80
80
  "chalk": "^5.6.2",
81
81
  "cli-table3": "^0.6.5",
82
82
  "commander": "^15.0.0",