@vultisig/cli 0.11.0 → 0.14.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # @vultisig/cli
2
2
 
3
+ ## 0.14.0
4
+
5
+ ### Patch Changes
6
+
7
+ - [#205](https://github.com/vultisig/vultisig-sdk/pull/205) [`99296f5`](https://github.com/vultisig/vultisig-sdk/commit/99296f5aaf3f9bfb7fe694de034037683e7435ed) Thanks [@rcoderdev](https://github.com/rcoderdev)! - Classify vault import failures with specific `VaultImportErrorCode` values (`INVALID_FILE_FORMAT`, `INVALID_PASSWORD`, `UNSUPPORTED_FORMAT`, `CORRUPTED_DATA`) instead of wrapping most errors as `CORRUPTED_DATA`. Add unit tests for import edge cases.
8
+
9
+ - Updated dependencies [[`9e2ffd6`](https://github.com/vultisig/vultisig-sdk/commit/9e2ffd6f6a8e2c8ad507b6ed2e2c1232bf8a98c7), [`8bef556`](https://github.com/vultisig/vultisig-sdk/commit/8bef55651cba506a515083765d6f7745cce54abe), [`99296f5`](https://github.com/vultisig/vultisig-sdk/commit/99296f5aaf3f9bfb7fe694de034037683e7435ed)]:
10
+ - @vultisig/sdk@0.14.0
11
+ - @vultisig/rujira@9.0.0
12
+
13
+ ## 0.13.0
14
+
15
+ ### Patch Changes
16
+
17
+ - Updated dependencies [[`3f46444`](https://github.com/vultisig/vultisig-sdk/commit/3f46444b2a11a41dbbb023919c2f168f9d15cff8), [`84a2950`](https://github.com/vultisig/vultisig-sdk/commit/84a295002ed7310320b584fbccb76aaf4a233b31)]:
18
+ - @vultisig/sdk@0.13.0
19
+
20
+ ## 0.12.0
21
+
22
+ ### Minor Changes
23
+
24
+ - [#165](https://github.com/vultisig/vultisig-sdk/pull/165) [`4195641`](https://github.com/vultisig/vultisig-sdk/commit/4195641a9eb27d41fb27d2c6b605b34d4c4635b0) Thanks [@rcoderdev](https://github.com/rcoderdev)! - Fast vault creation (CLI and SDK) no longer runs ML-DSA keygen; VultiServer only adds ML-DSA via `POST /mldsa`. Use `Vultisig.addPostQuantumKeysToFastVault` / `FastVault.addPostQuantumKeys` or CLI `vultisig add-mldsa` when post-quantum keys are needed. TSS batching for fast vault create now requests `ecdsa` and `eddsa` only. `MldsaKeygen` default relay message ids match VultiServer classic keygen (empty string); batch flows still pass `p-mldsa` explicitly.
25
+
26
+ ### Patch Changes
27
+
28
+ - Updated dependencies [[`4195641`](https://github.com/vultisig/vultisig-sdk/commit/4195641a9eb27d41fb27d2c6b605b34d4c4635b0)]:
29
+ - @vultisig/sdk@0.12.0
30
+ - @vultisig/rujira@8.0.0
31
+
3
32
  ## 0.11.0
4
33
 
5
34
  ### Patch Changes
package/README.md CHANGED
@@ -693,6 +693,55 @@ vultisig agent --via-agent --password "$VAULT_PASSWORD"
693
693
  - `--password-ttl <ms>` - Password cache TTL (default: 5min, 24h for `--via-agent`)
694
694
  - `--session-id <id>` - Resume an existing session
695
695
 
696
+ #### Pipe Protocol (`--via-agent`)
697
+
698
+ The pipe interface uses NDJSON (one JSON object per line) on stdin/stdout. Designed for AI agent orchestrators that need programmatic wallet control.
699
+
700
+ **Input commands** (send on stdin):
701
+
702
+ | Type | Fields | Purpose |
703
+ |------|--------|---------|
704
+ | `message` | `content: string` | Send a natural-language message |
705
+ | `confirm` | `confirmed: boolean` | Respond to a confirmation request |
706
+ | `password` | `password: string` | Provide vault password when requested |
707
+
708
+ **Output events** (emitted on stdout):
709
+
710
+ | Type | Fields | When |
711
+ |------|--------|------|
712
+ | `ready` | `vault, addresses` | Session initialized, addresses for all chains |
713
+ | `session` | `id` | Conversation ID for resuming later |
714
+ | `history` | `messages[]` | Previous messages when resuming a session |
715
+ | `auth` | `status, error?` | Authentication result (`authenticated` or `failed`) |
716
+ | `conversation` | `id` | Conversation created or resumed |
717
+ | `text_delta` | `delta` | Streaming text chunk from the agent |
718
+ | `tool_call` | `id, action, params?, status` | Action started (`running`) |
719
+ | `tool_result` | `id, action, success, data?, error?` | Action completed |
720
+ | `tx_status` | `tx_hash, chain, status, explorer_url?` | Transaction broadcast/confirmed/failed |
721
+ | `assistant` | `content` | Full assistant response |
722
+ | `suggestions` | `suggestions[]` | Suggested follow-up actions |
723
+ | `error` | `message` | Error (includes `PASSWORD_REQUIRED` and `CONFIRMATION_REQUIRED` signals) |
724
+ | `done` | `{}` | Response cycle complete |
725
+
726
+ **Example session:**
727
+
728
+ ```bash
729
+ echo '{"type":"message","content":"What is my ETH balance?"}' | vultisig agent --via-agent --password mypass --vault t1
730
+ ```
731
+
732
+ ```json
733
+ {"type":"ready","vault":"t1","addresses":{"Ethereum":"0xabc...","Bitcoin":"bc1q..."}}
734
+ {"type":"session","id":"conv_abc123"}
735
+ {"type":"tool_call","id":"mcp-get_balances","action":"get_balances","status":"running"}
736
+ {"type":"tool_result","id":"mcp-get_balances","action":"get_balances","success":true}
737
+ {"type":"text_delta","delta":"Your ETH"}
738
+ {"type":"text_delta","delta":" balance is 1.5 ETH."}
739
+ {"type":"assistant","content":"Your ETH balance is 1.5 ETH ($3,750.00 USD)."}
740
+ {"type":"done"}
741
+ ```
742
+
743
+ When the agent needs a password mid-session (e.g. for signing), it emits `{"type":"error","message":"PASSWORD_REQUIRED"}`. Respond with `{"type":"password","password":"..."}` on stdin.
744
+
696
745
  #### Session Management
697
746
 
698
747
  ```bash
@@ -811,6 +860,7 @@ Thorguard NFT holders receive a free tier upgrade (up to gold tier).
811
860
  -i, --interactive Start interactive shell mode
812
861
  -o, --output <format> Output format: table, json (default: table)
813
862
  --vault <nameOrId> Specify vault by name or ID
863
+ --server-url <url> Base Vultisig API URL for FastVault and relay endpoints
814
864
  --silent Suppress informational output, show only results
815
865
  --debug Enable debug output
816
866
  -h, --help Show help
@@ -933,6 +983,9 @@ VULTISIG_VAULT=MyWallet
933
983
  # Override config directory
934
984
  VULTISIG_CONFIG_DIR=/custom/path
935
985
 
986
+ # Override FastVault and relay via a shared base URL
987
+ VULTISIG_SERVER_URL=http://127.0.0.1:8080
988
+
936
989
  # Disable colored output
937
990
  VULTISIG_NO_COLOR=1
938
991
 
package/dist/index.js CHANGED
@@ -2420,7 +2420,9 @@ async function executeExecute(ctx2, params) {
2420
2420
  const vault = await ctx2.ensureActiveVault();
2421
2421
  const chainConfig = COSMOS_CHAIN_CONFIG[params.chain];
2422
2422
  if (!chainConfig) {
2423
- throw new Error(`Chain ${params.chain} does not support CosmWasm execute. Supported chains: ${Object.keys(COSMOS_CHAIN_CONFIG).join(", ")}`);
2423
+ throw new Error(
2424
+ `Chain ${params.chain} does not support CosmWasm execute. Supported chains: ${Object.keys(COSMOS_CHAIN_CONFIG).join(", ")}`
2425
+ );
2424
2426
  }
2425
2427
  let msg;
2426
2428
  try {
@@ -2441,7 +2443,9 @@ async function executeContractTransaction(vault, params, chainConfig, msg, funds
2441
2443
  info(`Chain: ${params.chain}`);
2442
2444
  info(`From: ${address}`);
2443
2445
  info(`Contract: ${params.contract}`);
2444
- info(`Message: ${JSON.stringify(msg, null, 2).substring(0, 200)}${JSON.stringify(msg).length > 200 ? "..." : ""}`);
2446
+ info(
2447
+ `Message: ${JSON.stringify(msg, null, 2).substring(0, 200)}${JSON.stringify(msg).length > 200 ? "..." : ""}`
2448
+ );
2445
2449
  if (funds.length > 0) {
2446
2450
  info(`Funds: ${funds.map((f) => `${f.amount} ${f.denom}`).join(", ")}`);
2447
2451
  }
@@ -2479,13 +2483,16 @@ Or use this URL: ${qrPayload}
2479
2483
  signSpinner.start("Waiting for devices to join signing session...");
2480
2484
  }
2481
2485
  });
2482
- vault.on("deviceJoined", ({ deviceId, totalJoined, required }) => {
2483
- if (!isSilent()) {
2484
- signSpinner.text = `Device joined: ${totalJoined}/${required} (${deviceId})`;
2485
- } else if (!isJsonOutput()) {
2486
- printResult(`Device joined: ${totalJoined}/${required}`);
2486
+ vault.on(
2487
+ "deviceJoined",
2488
+ ({ deviceId, totalJoined, required }) => {
2489
+ if (!isSilent()) {
2490
+ signSpinner.text = `Device joined: ${totalJoined}/${required} (${deviceId})`;
2491
+ } else if (!isJsonOutput()) {
2492
+ printResult(`Device joined: ${totalJoined}/${required}`);
2493
+ }
2487
2494
  }
2488
- });
2495
+ );
2489
2496
  }
2490
2497
  try {
2491
2498
  const cosmosChain = params.chain;
@@ -2731,6 +2738,7 @@ function sleep(ms) {
2731
2738
 
2732
2739
  // src/commands/vault-management.ts
2733
2740
  var import_qrcode_terminal4 = __toESM(require_main(), 1);
2741
+ import { FastVault } from "@vultisig/sdk";
2734
2742
  import chalk5 from "chalk";
2735
2743
  import { promises as fs } from "fs";
2736
2744
  import inquirer4 from "inquirer";
@@ -3030,6 +3038,35 @@ async function executeVerify(ctx2, vaultId, options = {}) {
3030
3038
  return false;
3031
3039
  }
3032
3040
  }
3041
+ async function executeAddPostQuantumKeys(ctx2, options) {
3042
+ const vault = await ctx2.ensureActiveVault();
3043
+ if (!(vault instanceof FastVault)) {
3044
+ error("add-mldsa is only supported for fast vaults.");
3045
+ throw new Error("Not a fast vault");
3046
+ }
3047
+ const spinner = createSpinner("Adding ML-DSA keys...");
3048
+ try {
3049
+ let password = options.password;
3050
+ if (password === void 0) {
3051
+ password = await ctx2.getPassword(vault.id, vault.name);
3052
+ }
3053
+ await ctx2.sdk.addPostQuantumKeysToFastVault(vault, {
3054
+ email: options.email,
3055
+ password,
3056
+ signal: options.signal,
3057
+ onProgress: (u) => {
3058
+ if (u.message) {
3059
+ spinner.text = u.message;
3060
+ }
3061
+ }
3062
+ });
3063
+ spinner.succeed("ML-DSA keys added. Vault file updated \u2014 export a backup if needed.");
3064
+ success("Post-quantum signing is now available for this vault (where supported).");
3065
+ } catch (e) {
3066
+ spinner.fail("Failed to add ML-DSA keys");
3067
+ throw e;
3068
+ }
3069
+ }
3033
3070
  async function executeExport(ctx2, options = {}) {
3034
3071
  const vault = await ctx2.ensureActiveVault();
3035
3072
  let exportPassword = options.exportPassword;
@@ -4220,9 +4257,7 @@ var AskInterface = class {
4220
4257
  onDone: () => {
4221
4258
  },
4222
4259
  requestPassword: async () => {
4223
- throw new Error(
4224
- "Password required but not provided. Use --password flag."
4225
- );
4260
+ throw new Error("Password required but not provided. Use --password flag.");
4226
4261
  },
4227
4262
  requestConfirmation: async (_message) => {
4228
4263
  return true;
@@ -4272,10 +4307,7 @@ async function authenticateVault(client, vault, password, maxAttempts = 3) {
4272
4307
  process.stderr.write(` Retry ${attempt}/${maxAttempts}...
4273
4308
  `);
4274
4309
  }
4275
- const signature = await vault.signBytes(
4276
- { data: Buffer.from(messageHash), chain: Chain7.Ethereum },
4277
- {}
4278
- );
4310
+ const signature = await vault.signBytes({ data: Buffer.from(messageHash), chain: Chain7.Ethereum }, {});
4279
4311
  const sigHex = formatSignature65(signature.signature, signature.recovery ?? 0);
4280
4312
  const authResponse = await client.authenticate({
4281
4313
  public_key: publicKey,
@@ -4443,31 +4475,40 @@ var AgentClient = class {
4443
4475
  const reader = res.body.getReader();
4444
4476
  const decoder = new TextDecoder();
4445
4477
  let buffer = "";
4478
+ let currentEvent = "";
4479
+ let currentData = "";
4480
+ const stripLeadingSpace = (v) => v.length > 0 && v[0] === " " ? v.slice(1) : v;
4481
+ const processLine = (raw) => {
4482
+ const line = raw.endsWith("\r") ? raw.slice(0, -1) : raw;
4483
+ if (line.startsWith("event:")) {
4484
+ currentEvent = stripLeadingSpace(line.slice(6)).trim();
4485
+ } else if (line.startsWith("data:")) {
4486
+ currentData += (currentData ? "\n" : "") + stripLeadingSpace(line.slice(5));
4487
+ } else if (line === "") {
4488
+ if (currentData) {
4489
+ this.handleSSEEvent(currentEvent || "message", currentData, result, callbacks);
4490
+ }
4491
+ currentEvent = "";
4492
+ currentData = "";
4493
+ } else if (line[0] === ":") {
4494
+ }
4495
+ };
4446
4496
  try {
4447
4497
  while (true) {
4448
4498
  const { done, value } = await reader.read();
4449
- if (done) break;
4450
- buffer += decoder.decode(value, { stream: true });
4499
+ buffer += decoder.decode(value || new Uint8Array(), { stream: !done });
4451
4500
  const lines = buffer.split("\n");
4452
- buffer = lines.pop() || "";
4453
- let currentEvent = "";
4454
- let currentData = "";
4455
- for (const line of lines) {
4456
- if (line.startsWith("event: ")) {
4457
- currentEvent = line.slice(7).trim();
4458
- } else if (line.startsWith("data: ")) {
4459
- currentData += (currentData ? "\n" : "") + line.slice(6);
4460
- } else if (line === "") {
4461
- if (currentEvent && currentData) {
4462
- this.handleSSEEvent(currentEvent, currentData, result, callbacks);
4463
- }
4464
- currentEvent = "";
4465
- currentData = "";
4466
- } else if (line.startsWith(": ")) {
4467
- }
4501
+ const trailing = lines.pop() ?? "";
4502
+ buffer = done ? "" : trailing;
4503
+ for (const rawLine of lines) {
4504
+ processLine(rawLine);
4468
4505
  }
4469
- if (currentEvent && currentData) {
4470
- this.handleSSEEvent(currentEvent, currentData, result, callbacks);
4506
+ if (done) {
4507
+ if (trailing) processLine(trailing);
4508
+ if (currentData) {
4509
+ this.handleSSEEvent(currentEvent || "message", currentData, result, callbacks);
4510
+ }
4511
+ break;
4471
4512
  }
4472
4513
  }
4473
4514
  } finally {
@@ -4480,8 +4521,10 @@ var AgentClient = class {
4480
4521
  const parsed = JSON.parse(data);
4481
4522
  switch (event) {
4482
4523
  case "text_delta":
4483
- result.fullText += parsed.delta;
4484
- callbacks.onTextDelta?.(parsed.delta);
4524
+ if (typeof parsed.delta === "string") {
4525
+ result.fullText += parsed.delta;
4526
+ callbacks.onTextDelta?.(parsed.delta);
4527
+ }
4485
4528
  break;
4486
4529
  case "tool_progress":
4487
4530
  if (this.verbose) process.stderr.write(`[SSE:tool_progress] raw: ${data.slice(0, 1e3)}
@@ -4517,8 +4560,26 @@ var AgentClient = class {
4517
4560
  case "done":
4518
4561
  break;
4519
4562
  }
4520
- } catch {
4563
+ } catch (e) {
4564
+ if (e instanceof SyntaxError) {
4565
+ if (this.verbose) process.stderr.write(`[SSE] skipping malformed JSON: ${data.slice(0, 200)}
4566
+ `);
4567
+ } else {
4568
+ throw e;
4569
+ }
4570
+ }
4571
+ }
4572
+ // ============================================================================
4573
+ // Calldata
4574
+ // ============================================================================
4575
+ async getCalldata(id) {
4576
+ const res = await fetch(`${this.baseUrl}/agent/calldata/${id}`, {
4577
+ headers: this.authToken ? { Authorization: `Bearer ${this.authToken}` } : {}
4578
+ });
4579
+ if (!res.ok) {
4580
+ throw new Error(`Failed to resolve calldata_id ${id}: ${res.status} ${res.statusText}`);
4521
4581
  }
4582
+ return res.json();
4522
4583
  }
4523
4584
  // ============================================================================
4524
4585
  // Private helpers
@@ -4924,6 +4985,8 @@ var AgentExecutor = class {
4924
4985
  stateStore = null;
4925
4986
  /** Held chain lock release functions, keyed by chain name */
4926
4987
  chainLockReleases = /* @__PURE__ */ new Map();
4988
+ /** Backend client for resolving calldata_id references. */
4989
+ backendClient = null;
4927
4990
  constructor(vault, verbose = false, vaultId) {
4928
4991
  this.vault = vault;
4929
4992
  this.verbose = verbose;
@@ -4934,13 +4997,19 @@ var AgentExecutor = class {
4934
4997
  setPassword(password) {
4935
4998
  this.password = password;
4936
4999
  }
5000
+ setBackendClient(client) {
5001
+ this.backendClient = client;
5002
+ }
4937
5003
  /**
4938
5004
  * Store a server-built transaction (from tx_ready SSE event).
4939
5005
  * This allows sign_tx to find and sign it when the backend requests signing.
4940
5006
  */
4941
5007
  storeServerTransaction(txReadyData) {
4942
- if (this.verbose) process.stderr.write(`[executor] storeServerTransaction called, keys: ${Object.keys(txReadyData || {}).join(",")}
4943
- `);
5008
+ if (this.verbose)
5009
+ process.stderr.write(
5010
+ `[executor] storeServerTransaction called, keys: ${Object.keys(txReadyData || {}).join(",")}
5011
+ `
5012
+ );
4944
5013
  const swapTx = txReadyData.swap_tx || txReadyData.send_tx || txReadyData.tx;
4945
5014
  if (!swapTx) {
4946
5015
  if (this.verbose) process.stderr.write(`[executor] storeServerTransaction: no swap_tx/send_tx/tx found in data
@@ -4955,8 +5024,11 @@ var AgentExecutor = class {
4955
5024
  chain,
4956
5025
  timestamp: Date.now()
4957
5026
  });
4958
- if (this.verbose) process.stderr.write(`[executor] Stored server tx for chain ${chain}, pendingPayloads size=${this.pendingPayloads.size}
4959
- `);
5027
+ if (this.verbose)
5028
+ process.stderr.write(
5029
+ `[executor] Stored server tx for chain ${chain}, pendingPayloads size=${this.pendingPayloads.size}
5030
+ `
5031
+ );
4960
5032
  }
4961
5033
  hasPendingTransaction() {
4962
5034
  return this.pendingPayloads.has("latest");
@@ -5197,7 +5269,8 @@ var AgentExecutor = class {
5197
5269
  }
5198
5270
  }
5199
5271
  async buildSwapTx(params) {
5200
- if (this.verbose) process.stderr.write(`[build_swap_tx] called with params: ${JSON.stringify(params).slice(0, 500)}
5272
+ if (this.verbose)
5273
+ process.stderr.write(`[build_swap_tx] called with params: ${JSON.stringify(params).slice(0, 500)}
5201
5274
  `);
5202
5275
  const fromChainName = params.from_chain || params.chain;
5203
5276
  const toChainName = params.to_chain;
@@ -5231,8 +5304,18 @@ var AgentExecutor = class {
5231
5304
  const messageHashes = await this.vault.extractMessageHashes(payload);
5232
5305
  this.pendingPayloads.clear();
5233
5306
  const payloadId = `swap_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
5234
- this.pendingPayloads.set(payloadId, { payload, coin: { chain, address: "", decimals: 18, ticker: fromSymbol }, chain, timestamp: Date.now() });
5235
- this.pendingPayloads.set("latest", { payload, coin: { chain, address: "", decimals: 18, ticker: fromSymbol }, chain, timestamp: Date.now() });
5307
+ this.pendingPayloads.set(payloadId, {
5308
+ payload,
5309
+ coin: { chain, address: "", decimals: 18, ticker: fromSymbol },
5310
+ chain,
5311
+ timestamp: Date.now()
5312
+ });
5313
+ this.pendingPayloads.set("latest", {
5314
+ payload,
5315
+ coin: { chain, address: "", decimals: 18, ticker: fromSymbol },
5316
+ chain,
5317
+ timestamp: Date.now()
5318
+ });
5236
5319
  return {
5237
5320
  keysign_payload: payloadId,
5238
5321
  from_chain: fromChain.toString(),
@@ -5250,6 +5333,17 @@ var AgentExecutor = class {
5250
5333
  }
5251
5334
  }
5252
5335
  async buildTx(params) {
5336
+ if (params.calldata_id && !params.data && this.backendClient) {
5337
+ const id = params.calldata_id;
5338
+ if (this.verbose) process.stderr.write(`[executor] resolving calldata_id ${id}
5339
+ `);
5340
+ const entry = await this.backendClient.getCalldata(id);
5341
+ params = { ...params, data: entry.data };
5342
+ if (!params.to && entry.to) params = { ...params, to: entry.to };
5343
+ delete params.calldata_id;
5344
+ if (this.verbose) process.stderr.write(`[executor] calldata_id resolved, data len=${entry.data.length}
5345
+ `);
5346
+ }
5253
5347
  if (params.function_name && params.contract_address) {
5254
5348
  return this.buildContractCallTx(params);
5255
5349
  }
@@ -5278,6 +5372,12 @@ var AgentExecutor = class {
5278
5372
  message: "Transaction built. Ready to sign."
5279
5373
  };
5280
5374
  }
5375
+ if (params.contract_address && !params.function_name) {
5376
+ const provided = Object.keys(params).join(", ");
5377
+ throw new Error(
5378
+ `build_custom_tx requires function_name and params for contract calls. Got: ${provided}. Missing: function_name, params.`
5379
+ );
5380
+ }
5281
5381
  return this.buildSendTx(params);
5282
5382
  }
5283
5383
  /**
@@ -5293,8 +5393,11 @@ var AgentExecutor = class {
5293
5393
  const typedParams = params.params;
5294
5394
  const value = params.value || "0";
5295
5395
  const calldata = await encodeContractCall(functionName, typedParams || []);
5296
- if (this.verbose) process.stderr.write(`[build_contract_tx] ${functionName}(${(typedParams || []).map((p) => p.type).join(",")}) on ${contractAddress} chain=${chain}
5297
- `);
5396
+ if (this.verbose)
5397
+ process.stderr.write(
5398
+ `[build_contract_tx] ${functionName}(${(typedParams || []).map((p) => p.type).join(",")}) on ${contractAddress} chain=${chain}
5399
+ `
5400
+ );
5298
5401
  const serverTxData = {
5299
5402
  __serverTx: true,
5300
5403
  tx: {
@@ -5319,7 +5422,8 @@ var AgentExecutor = class {
5319
5422
  async signTx(params) {
5320
5423
  if (this.verbose) process.stderr.write(`[sign_tx] params: ${JSON.stringify(params).slice(0, 500)}
5321
5424
  `);
5322
- if (this.verbose) process.stderr.write(`[sign_tx] pendingPayloads keys: ${[...this.pendingPayloads.keys()].join(", ")}
5425
+ if (this.verbose)
5426
+ process.stderr.write(`[sign_tx] pendingPayloads keys: ${[...this.pendingPayloads.keys()].join(", ")}
5323
5427
  `);
5324
5428
  const payloadId = params.keysign_payload || params.payload_id || "latest";
5325
5429
  const stored = this.pendingPayloads.get(payloadId);
@@ -5333,7 +5437,8 @@ var AgentExecutor = class {
5333
5437
  return await this.buildAndSignSolanaSwapLocally(payload);
5334
5438
  } catch (e) {
5335
5439
  if (e._phase === "prepare") {
5336
- if (this.verbose) process.stderr.write(`[sign_tx] Solana local build failed (${e.message}), falling back to signServerTx
5440
+ if (this.verbose)
5441
+ process.stderr.write(`[sign_tx] Solana local build failed (${e.message}), falling back to signServerTx
5337
5442
  `);
5338
5443
  } else {
5339
5444
  throw e;
@@ -5417,8 +5522,11 @@ var AgentExecutor = class {
5417
5522
  };
5418
5523
  const amount = BigInt(swapTx.value || "0");
5419
5524
  const hasCalldata = !!(swapTx.data && swapTx.data !== "0x");
5420
- if (this.verbose) process.stderr.write(`[sign_server_tx] chain=${chain}, to=${swapTx.to}, value=${swapTx.value}, amount=${amount}, hasCalldata=${hasCalldata}
5421
- `);
5525
+ if (this.verbose)
5526
+ process.stderr.write(
5527
+ `[sign_server_tx] chain=${chain}, to=${swapTx.to}, value=${swapTx.value}, amount=${amount}, hasCalldata=${hasCalldata}
5528
+ `
5529
+ );
5422
5530
  if (this.vault.isEncrypted && !this.vault.isUnlocked?.()) {
5423
5531
  if (this.password) {
5424
5532
  await this.vault.unlock?.(this.password);
@@ -5436,6 +5544,18 @@ var AgentExecutor = class {
5436
5544
  keysignPayload.toAmount = "0";
5437
5545
  }
5438
5546
  await this.patchEvmNonce(chain, keysignPayload);
5547
+ if (swapTx.gas_limit) {
5548
+ const bs = keysignPayload.blockchainSpecific;
5549
+ if (bs?.case === "ethereumSpecific" && bs.value?.gasLimit) {
5550
+ const serverGas = swapTx.gas_limit.toString();
5551
+ const currentGas = bs.value.gasLimit.toString();
5552
+ if (BigInt(serverGas) > BigInt(currentGas)) {
5553
+ bs.value.gasLimit = serverGas;
5554
+ if (this.verbose) process.stderr.write(`[gas] Using server gas_limit: ${serverGas} (was ${currentGas})
5555
+ `);
5556
+ }
5557
+ }
5558
+ }
5439
5559
  await this.patchEvmGas(chain, keysignPayload);
5440
5560
  const messageHashes = await this.vault.extractMessageHashes(keysignPayload);
5441
5561
  const signature = await this.vault.sign(
@@ -5482,16 +5602,25 @@ var AgentExecutor = class {
5482
5602
  const toChain = toChainName ? resolveChain(toChainName) : fromChain;
5483
5603
  if (!toChain) throw Object.assign(new Error(`Unknown to_chain: ${toChainName}`), { _phase: "prepare" });
5484
5604
  const amountStr = serverTxData.amount;
5485
- if (!amountStr) throw Object.assign(new Error("Missing amount in tx_ready data for local Solana swap build"), { _phase: "prepare" });
5605
+ if (!amountStr)
5606
+ throw Object.assign(new Error("Missing amount in tx_ready data for local Solana swap build"), {
5607
+ _phase: "prepare"
5608
+ });
5486
5609
  const fromToken = serverTxData.from_address;
5487
5610
  const toToken = serverTxData.to_address;
5488
5611
  const fromDecimals = serverTxData.from_decimals;
5489
- if (fromDecimals == null) throw Object.assign(new Error("Missing from_decimals in tx_ready data for local Solana swap build"), { _phase: "prepare" });
5612
+ if (fromDecimals == null)
5613
+ throw Object.assign(new Error("Missing from_decimals in tx_ready data for local Solana swap build"), {
5614
+ _phase: "prepare"
5615
+ });
5490
5616
  const fromCoin = { chain: fromChain, token: fromToken || void 0 };
5491
5617
  const toCoin = { chain: toChain, token: toToken || void 0 };
5492
5618
  const humanAmount = Number(amountStr) / Math.pow(10, fromDecimals);
5493
- if (this.verbose) process.stderr.write(`[solana_local_swap] from=${fromChainName} to=${toChainName || fromChainName} amount=${amountStr}
5494
- `);
5619
+ if (this.verbose)
5620
+ process.stderr.write(
5621
+ `[solana_local_swap] from=${fromChainName} to=${toChainName || fromChainName} amount=${amountStr}
5622
+ `
5623
+ );
5495
5624
  if (this.vault.isEncrypted && !this.vault.isUnlocked?.()) {
5496
5625
  if (this.password) {
5497
5626
  await this.vault.unlock?.(this.password);
@@ -5584,15 +5713,21 @@ var AgentExecutor = class {
5584
5713
  if (nextNonce !== rpcNonce) {
5585
5714
  const pendingNonce = await this.fetchEvmPendingNonce(chain);
5586
5715
  if (pendingNonce !== null && pendingNonce === rpcNonce) {
5587
- if (this.verbose) process.stderr.write(`[nonce] Stale local state for ${chain}: local=${nextNonce}, on-chain=${rpcNonce}, no pending txs \u2014 using on-chain nonce
5588
- `);
5716
+ if (this.verbose)
5717
+ process.stderr.write(
5718
+ `[nonce] Stale local state for ${chain}: local=${nextNonce}, on-chain=${rpcNonce}, no pending txs \u2014 using on-chain nonce
5719
+ `
5720
+ );
5589
5721
  this.stateStore.clearEvmState(chain);
5590
5722
  return;
5591
5723
  }
5592
5724
  const nonceGap = nextNonce - rpcNonce;
5593
5725
  if (pendingNonce === null && nonceGap > 3n) {
5594
- if (this.verbose) process.stderr.write(`[nonce] Large nonce gap for ${chain} (${nonceGap}) and couldn't verify pending txs \u2014 using on-chain nonce ${rpcNonce}
5595
- `);
5726
+ if (this.verbose)
5727
+ process.stderr.write(
5728
+ `[nonce] Large nonce gap for ${chain} (${nonceGap}) and couldn't verify pending txs \u2014 using on-chain nonce ${rpcNonce}
5729
+ `
5730
+ );
5596
5731
  this.stateStore.clearEvmState(chain);
5597
5732
  return;
5598
5733
  }
@@ -5632,8 +5767,11 @@ var AgentExecutor = class {
5632
5767
  const minMaxFee = baseFee * 25n / 10n + currentPriorityFee;
5633
5768
  if (currentMaxFee < minMaxFee) {
5634
5769
  bs.value.maxFeePerGasWei = minMaxFee.toString();
5635
- if (this.verbose) process.stderr.write(`[gas] Bumped ${chain} maxFeePerGas: ${currentMaxFee} \u2192 ${minMaxFee} (baseFee=${baseFee})
5636
- `);
5770
+ if (this.verbose)
5771
+ process.stderr.write(
5772
+ `[gas] Bumped ${chain} maxFeePerGas: ${currentMaxFee} \u2192 ${minMaxFee} (baseFee=${baseFee})
5773
+ `
5774
+ );
5637
5775
  }
5638
5776
  } catch {
5639
5777
  if (this.verbose) process.stderr.write(`[gas] Failed to refresh base fee for ${chain}, keeping original
@@ -5750,7 +5888,8 @@ var AgentExecutor = class {
5750
5888
  data: eip712Hash,
5751
5889
  chain
5752
5890
  });
5753
- if (this.verbose) process.stderr.write(`[sign_typed_data] signed, format=${sigResult.format}, recovery=${sigResult.recovery}
5891
+ if (this.verbose)
5892
+ process.stderr.write(`[sign_typed_data] signed, format=${sigResult.format}, recovery=${sigResult.recovery}
5754
5893
  `);
5755
5894
  const { r, s } = parseDERSignature(sigResult.signature);
5756
5895
  const v = (sigResult.recovery ?? 0) + 27;
@@ -5786,12 +5925,14 @@ var AgentExecutor = class {
5786
5925
  }
5787
5926
  async listVaults() {
5788
5927
  return {
5789
- vaults: [{
5790
- name: this.vault.name,
5791
- id: this.vault.id,
5792
- type: this.vault.type,
5793
- chains: this.vault.chains.map((c) => c.toString())
5794
- }]
5928
+ vaults: [
5929
+ {
5930
+ name: this.vault.name,
5931
+ id: this.vault.id,
5932
+ type: this.vault.type,
5933
+ chains: this.vault.chains.map((c) => c.toString())
5934
+ }
5935
+ ]
5795
5936
  };
5796
5937
  }
5797
5938
  async scanTx(params) {
@@ -5802,8 +5943,9 @@ var AgentExecutor = class {
5802
5943
  }
5803
5944
  };
5804
5945
  async function encodeContractCall(functionName, params) {
5946
+ const baseName = functionName.includes("(") ? functionName.split("(")[0] : functionName;
5805
5947
  const types = params.map((p) => p.type);
5806
- const sig = `${functionName}(${types.join(",")})`;
5948
+ const sig = `${baseName}(${types.join(",")})`;
5807
5949
  const selector = await keccak256Selector(sig);
5808
5950
  let encoded = "";
5809
5951
  for (const param of params) {
@@ -6264,6 +6406,7 @@ var PipeInterface = class {
6264
6406
  import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
6265
6407
  import { homedir as homedir2 } from "node:os";
6266
6408
  import { join as join2 } from "node:path";
6409
+ import { MemoryStorage, PushNotificationService } from "@vultisig/sdk";
6267
6410
  var AgentSession = class {
6268
6411
  client;
6269
6412
  vault;
@@ -6274,6 +6417,7 @@ var AgentSession = class {
6274
6417
  cachedContext = null;
6275
6418
  abortController = null;
6276
6419
  historyMessages = [];
6420
+ pushService = null;
6277
6421
  constructor(vault, config) {
6278
6422
  this.vault = vault;
6279
6423
  this.config = config;
@@ -6307,6 +6451,7 @@ var AgentSession = class {
6307
6451
  this.client.setAuthToken(auth.token);
6308
6452
  saveCachedToken(this.publicKey, auth.token, auth.expiresAt);
6309
6453
  }
6454
+ this.executor.setBackendClient(this.client);
6310
6455
  } catch (err) {
6311
6456
  throw new Error(`Authentication failed: ${err.message}`);
6312
6457
  }
@@ -6335,6 +6480,35 @@ var AgentSession = class {
6335
6480
  this.conversationId = conv.id;
6336
6481
  }
6337
6482
  this.cachedContext = this.config.viaAgent || this.config.askMode ? await buildMinimalContext(this.vault) : await buildMessageContext(this.vault);
6483
+ if (this.config.notificationUrl && ui.onNotification) {
6484
+ try {
6485
+ if (!globalThis.WebSocket) {
6486
+ const { WebSocket } = await import("ws");
6487
+ globalThis.WebSocket = WebSocket;
6488
+ }
6489
+ const token = crypto.randomUUID();
6490
+ this.pushService = new PushNotificationService(new MemoryStorage(), this.config.notificationUrl);
6491
+ await this.pushService.registerDevice({
6492
+ vaultId: this.publicKey,
6493
+ partyName: "cli-agent",
6494
+ token,
6495
+ deviceType: "electron"
6496
+ });
6497
+ this.pushService.onSigningRequest((notification) => {
6498
+ ui.onNotification?.(notification.vaultName, notification.qrCodeData);
6499
+ });
6500
+ this.pushService.connect({
6501
+ vaultId: this.publicKey,
6502
+ partyName: "cli-agent",
6503
+ token
6504
+ });
6505
+ } catch (err) {
6506
+ if (this.config.verbose) {
6507
+ process.stderr.write(`[session] push notification setup failed: ${err}
6508
+ `);
6509
+ }
6510
+ }
6511
+ }
6338
6512
  }
6339
6513
  getConversationId() {
6340
6514
  return this.conversationId;
@@ -6428,7 +6602,8 @@ var AgentSession = class {
6428
6602
  onTxReady: (tx) => {
6429
6603
  const txData = tx?.swap_tx || tx?.send_tx || tx?.tx;
6430
6604
  if (txData?.status === "error" || txData?.error) {
6431
- if (this.config.verbose) process.stderr.write(`[session] skipping error tx_ready: ${txData.error || "unknown error"}
6605
+ if (this.config.verbose)
6606
+ process.stderr.write(`[session] skipping error tx_ready: ${txData.error || "unknown error"}
6432
6607
  `);
6433
6608
  return;
6434
6609
  }
@@ -6445,7 +6620,7 @@ var AgentSession = class {
6445
6620
  },
6446
6621
  this.abortController?.signal
6447
6622
  );
6448
- const responseText = streamResult.fullText || streamResult.message?.content || "";
6623
+ const responseText = streamResult.message?.content || streamResult.fullText || "";
6449
6624
  const inlineActions = parseInlineToolCalls(responseText);
6450
6625
  if (inlineActions.length > 0) {
6451
6626
  const cleanText = responseText.replace(/<invoke\s+name="[^"]*">[\s\S]*?<\/invoke>/g, "").replace(/<\/?minimax:tool_call>/g, "").trim();
@@ -6459,11 +6634,10 @@ var AgentSession = class {
6459
6634
  const actions = streamResult.actions.filter((a) => a.type !== "sign_tx");
6460
6635
  if (actions.length > 0) {
6461
6636
  const results = await this.executeActions(actions, ui);
6462
- const hasBuildSuccess = results.some(
6463
- (r) => r.success && r.action.startsWith("build_")
6464
- );
6637
+ const hasBuildSuccess = results.some((r) => r.success && r.action.startsWith("build_"));
6465
6638
  if (hasBuildSuccess && this.executor.hasPendingTransaction()) {
6466
- if (this.config.verbose) process.stderr.write(`[session] build_* action produced pending tx, auto-signing client-side
6639
+ if (this.config.verbose)
6640
+ process.stderr.write(`[session] build_* action produced pending tx, auto-signing client-side
6467
6641
  `);
6468
6642
  const signAction = {
6469
6643
  id: `tx_sign_${Date.now()}`,
@@ -6487,7 +6661,8 @@ var AgentSession = class {
6487
6661
  }
6488
6662
  }
6489
6663
  if (streamResult.transactions.length > 0 && this.executor.hasPendingTransaction()) {
6490
- if (this.config.verbose) process.stderr.write(`[session] ${streamResult.transactions.length} tx_ready events, signing client-side
6664
+ if (this.config.verbose)
6665
+ process.stderr.write(`[session] ${streamResult.transactions.length} tx_ready events, signing client-side
6491
6666
  `);
6492
6667
  const signAction = {
6493
6668
  id: `tx_sign_${Date.now()}`,
@@ -6558,6 +6733,8 @@ var AgentSession = class {
6558
6733
  */
6559
6734
  dispose() {
6560
6735
  this.cancel();
6736
+ this.pushService?.disconnect();
6737
+ this.pushService = null;
6561
6738
  this.cachedContext = null;
6562
6739
  this.conversationId = null;
6563
6740
  this.historyMessages = [];
@@ -6651,10 +6828,12 @@ var ChatTUI = class {
6651
6828
  rl;
6652
6829
  session;
6653
6830
  isStreaming = false;
6831
+ isProcessing = false;
6654
6832
  currentStreamText = "";
6655
6833
  vaultName;
6656
6834
  stopped = false;
6657
6835
  verbose;
6836
+ pendingNotifications = [];
6658
6837
  constructor(session, vaultName, verbose = false) {
6659
6838
  this.session = session;
6660
6839
  this.vaultName = vaultName;
@@ -6859,6 +7038,20 @@ var ChatTUI = class {
6859
7038
  }
6860
7039
  });
6861
7040
  },
7041
+ onNotification: (title, deeplink) => {
7042
+ const currentConvId = this.session.getConversationId();
7043
+ if (currentConvId && deeplink) {
7044
+ const segments = deeplink.split("/");
7045
+ const deeplinkConvId = segments[segments.length - 1];
7046
+ if (deeplinkConvId !== currentConvId) return;
7047
+ }
7048
+ if (this.isProcessing) {
7049
+ this.pendingNotifications.push({ title, deeplink });
7050
+ return;
7051
+ }
7052
+ this.displayNotification(title);
7053
+ this.showPrompt();
7054
+ },
6862
7055
  requestConfirmation: async (message) => {
6863
7056
  return new Promise((resolve) => {
6864
7057
  this.rl.question(chalk8.yellow(` ${message} (y/N): `), (answer) => {
@@ -6868,9 +7061,31 @@ var ChatTUI = class {
6868
7061
  }
6869
7062
  };
6870
7063
  }
7064
+ displayNotification(title) {
7065
+ const [heading, ...bodyLines] = title.split("\n");
7066
+ const body = bodyLines.join("\n").trim();
7067
+ const ts = this.timestamp();
7068
+ process.stdout.write(`
7069
+ ${chalk8.gray(ts)} ${chalk8.magenta.bold("Notification")}: ${chalk8.bold(heading)}
7070
+ `);
7071
+ if (body) {
7072
+ process.stdout.write(` ${body}
7073
+ `);
7074
+ }
7075
+ process.stdout.write("\n");
7076
+ }
7077
+ flushPendingNotifications() {
7078
+ if (this.pendingNotifications.length === 0) return;
7079
+ const queued = this.pendingNotifications.splice(0);
7080
+ for (const { title } of queued) {
7081
+ this.displayNotification(title);
7082
+ }
7083
+ this.showPrompt();
7084
+ }
6871
7085
  async handleMessage(content) {
6872
7086
  const callbacks = this.getCallbacks();
6873
7087
  this.isStreaming = false;
7088
+ this.isProcessing = true;
6874
7089
  try {
6875
7090
  await this.session.sendMessage(content, callbacks);
6876
7091
  } catch (err) {
@@ -6879,12 +7094,17 @@ var ChatTUI = class {
6879
7094
  } else {
6880
7095
  console.log(chalk8.red(` Error: ${err.message}`));
6881
7096
  }
7097
+ } finally {
7098
+ this.isProcessing = false;
7099
+ this.flushPendingNotifications();
6882
7100
  }
6883
7101
  }
6884
7102
  printHeader() {
6885
7103
  console.log("");
6886
7104
  console.log(chalk8.bold.cyan(` \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557`));
6887
- console.log(chalk8.bold.cyan(` \u2551`) + chalk8.bold(` Vultisig Agent - ${this.vaultName}`.padEnd(38).slice(0, 38)) + chalk8.bold.cyan(`\u2551`));
7105
+ console.log(
7106
+ chalk8.bold.cyan(` \u2551`) + chalk8.bold(` Vultisig Agent - ${this.vaultName}`.padEnd(38).slice(0, 38)) + chalk8.bold.cyan(`\u2551`)
7107
+ );
6888
7108
  console.log(chalk8.bold.cyan(` \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D`));
6889
7109
  console.log("");
6890
7110
  }
@@ -6961,7 +7181,8 @@ async function executeAgent(ctx2, options) {
6961
7181
  password: options.password,
6962
7182
  viaAgent: options.viaAgent,
6963
7183
  sessionId: options.sessionId,
6964
- verbose: options.verbose
7184
+ verbose: options.verbose,
7185
+ notificationUrl: options.notificationUrl || process.env.VULTISIG_NOTIFICATION_URL || ""
6965
7186
  };
6966
7187
  const session = new AgentSession(vault, config);
6967
7188
  if (options.viaAgent) {
@@ -7123,6 +7344,49 @@ function formatDate(iso) {
7123
7344
  }
7124
7345
  }
7125
7346
 
7347
+ // src/core/server-endpoints.ts
7348
+ function firstNonEmpty(...values) {
7349
+ return values.find((value) => typeof value === "string" && value.trim() !== "");
7350
+ }
7351
+ function stripTrailingSlash(url) {
7352
+ return url.replace(/\/+$/, "");
7353
+ }
7354
+ function joinEndpoint(baseUrl, path4) {
7355
+ return `${stripTrailingSlash(baseUrl)}${path4}`;
7356
+ }
7357
+ function resolveServerEndpoints(overrides = {}) {
7358
+ const serverUrl = firstNonEmpty(overrides.serverUrl, process.env.VULTISIG_SERVER_URL);
7359
+ if (!serverUrl) {
7360
+ return void 0;
7361
+ }
7362
+ return {
7363
+ fastVault: stripTrailingSlash(joinEndpoint(serverUrl, "/vault")),
7364
+ messageRelay: stripTrailingSlash(joinEndpoint(serverUrl, "/router"))
7365
+ };
7366
+ }
7367
+ function parseServerEndpointOverridesFromArgv(args) {
7368
+ return {
7369
+ serverUrl: readArgValue(args, "--server-url")
7370
+ };
7371
+ }
7372
+ function readArgValue(args, optionName) {
7373
+ for (let i = 0; i < args.length; i += 1) {
7374
+ const arg = args[i];
7375
+ if (arg === optionName) {
7376
+ const next = args[i + 1];
7377
+ if (!next || next.startsWith("-")) {
7378
+ return void 0;
7379
+ }
7380
+ return next;
7381
+ }
7382
+ if (arg.startsWith(`${optionName}=`)) {
7383
+ const value = arg.slice(optionName.length + 1);
7384
+ return value.trim() === "" ? void 0 : value;
7385
+ }
7386
+ }
7387
+ return void 0;
7388
+ }
7389
+
7126
7390
  // src/interactive/completer.ts
7127
7391
  import { Chain as Chain10 } from "@vultisig/sdk";
7128
7392
  import fs3 from "fs";
@@ -8528,7 +8792,7 @@ var cachedVersion = null;
8528
8792
  function getVersion() {
8529
8793
  if (cachedVersion) return cachedVersion;
8530
8794
  if (true) {
8531
- cachedVersion = "0.11.0";
8795
+ cachedVersion = "0.14.0";
8532
8796
  return cachedVersion;
8533
8797
  }
8534
8798
  try {
@@ -9001,7 +9265,7 @@ setupUserAgent();
9001
9265
  if (handled) process.exit(0);
9002
9266
  })();
9003
9267
  var ctx;
9004
- program.name("vultisig").description("Vultisig CLI - Secure multi-party crypto wallet").version(formatVersionShort(), "-v, --version", "Show version").option("--debug", "Enable debug output").option("--silent", "Suppress informational output, show only results").option("-o, --output <format>", "Output format: table, json (default: table)", "table").option("-i, --interactive", "Start interactive shell mode").option("--vault <nameOrId>", "Specify vault by name or ID").hook("preAction", (thisCommand) => {
9268
+ program.name("vultisig").description("Vultisig CLI - Secure multi-party crypto wallet").version(formatVersionShort(), "-v, --version", "Show version").option("--debug", "Enable debug output").option("--silent", "Suppress informational output, show only results").option("-o, --output <format>", "Output format: table, json (default: table)", "table").option("-i, --interactive", "Start interactive shell mode").option("--vault <nameOrId>", "Specify vault by name or ID").option("--server-url <url>", "Base Vultisig API URL for FastVault and relay endpoints").hook("preAction", (thisCommand) => {
9005
9269
  const opts = thisCommand.opts();
9006
9270
  initOutputMode({ silent: opts.silent, output: opts.output });
9007
9271
  });
@@ -9021,8 +9285,11 @@ async function init(vaultOverride, unlockPassword, passwordTTL) {
9021
9285
  if (unlockPassword && vaultSelector) {
9022
9286
  cachePassword(vaultSelector, unlockPassword);
9023
9287
  }
9288
+ const globalOptions = program.opts();
9289
+ const serverEndpoints = resolveServerEndpoints(globalOptions);
9024
9290
  const sdk = new Vultisig7({
9025
9291
  onPasswordRequired: createPasswordCallback(),
9292
+ ...serverEndpoints ? { serverEndpoints } : {},
9026
9293
  ...passwordTTL !== void 0 ? { passwordCache: { defaultTTL: passwordTTL } } : {}
9027
9294
  });
9028
9295
  await sdk.initialize();
@@ -9061,6 +9328,15 @@ createCmd.command("secure").description("Create a secure vault (multi-device MPC
9061
9328
  });
9062
9329
  })
9063
9330
  );
9331
+ program.command("add-mldsa").description("Add ML-DSA (post-quantum) keys to the active fast vault (VultiServer /mldsa)").requiredOption("--email <email>", "Email registered on the vault").option("--password <password>", "Vault password (otherwise prompted or from cache)").action(
9332
+ withExit(async (options) => {
9333
+ const context = await init(program.opts().vault);
9334
+ await executeAddPostQuantumKeys(context, {
9335
+ email: options.email,
9336
+ password: options.password
9337
+ });
9338
+ })
9339
+ );
9064
9340
  program.command("import <file>").description("Import vault from .vult file").option("--password <password>", "Password to decrypt the vault file").action(
9065
9341
  withExit(async (file, options) => {
9066
9342
  const context = await init(program.opts().vault);
@@ -9531,34 +9807,36 @@ rujiraCmd.command("withdraw <asset> <amount> <l1Address>").description("Withdraw
9531
9807
  }
9532
9808
  )
9533
9809
  );
9534
- var agentCmd = program.command("agent").description("AI-powered chat interface for wallet operations").option("--via-agent", "Use NDJSON pipe mode for agent-to-agent communication").option("--verbose", "Show detailed tool call parameters and debug output").option("--backend-url <url>", "Agent backend URL (default: https://abe.vultisig.com)").option("--password <password>", "Vault password for signing operations").option("--password-ttl <ms>", "Password cache TTL in milliseconds (default: 300000, 86400000/24h for --via-agent)").option("--session-id <id>", "Resume an existing session").action(async (options) => {
9535
- const MAX_TTL = 864e5;
9536
- let passwordTTL;
9537
- if (options.passwordTtl) {
9538
- const parsed = parseInt(options.passwordTtl, 10);
9539
- if (Number.isNaN(parsed) || parsed < 0) {
9540
- throw new Error(`Invalid --password-ttl value: "${options.passwordTtl}". Expected a non-negative integer in milliseconds.`);
9541
- }
9542
- passwordTTL = parsed;
9543
- } else if (options.viaAgent) {
9544
- passwordTTL = MAX_TTL;
9545
- }
9546
- const context = await init(program.opts().vault, options.password, passwordTTL);
9547
- await executeAgent(context, {
9548
- viaAgent: options.viaAgent,
9549
- verbose: options.verbose,
9550
- backendUrl: options.backendUrl,
9551
- password: options.password,
9552
- sessionId: options.sessionId
9553
- });
9554
- });
9810
+ var agentCmd = program.command("agent").description("AI-powered chat interface for wallet operations").option("--via-agent", "Use NDJSON pipe mode for agent-to-agent communication").option("--verbose", "Show detailed tool call parameters and debug output").option("--backend-url <url>", "Agent backend URL (default: https://abe.vultisig.com)").option("--password <password>", "Vault password for signing operations").option("--password-ttl <ms>", "Password cache TTL in milliseconds (default: 300000, 86400000/24h for --via-agent)").option("--session-id <id>", "Resume an existing session").option("--notification-url <url>", "Notification service URL for push notifications").action(
9811
+ async (options) => {
9812
+ const MAX_TTL = 864e5;
9813
+ let passwordTTL;
9814
+ if (options.passwordTtl) {
9815
+ const parsed = parseInt(options.passwordTtl, 10);
9816
+ if (Number.isNaN(parsed) || parsed < 0) {
9817
+ throw new Error(
9818
+ `Invalid --password-ttl value: "${options.passwordTtl}". Expected a non-negative integer in milliseconds.`
9819
+ );
9820
+ }
9821
+ passwordTTL = parsed;
9822
+ } else if (options.viaAgent) {
9823
+ passwordTTL = MAX_TTL;
9824
+ }
9825
+ const context = await init(program.opts().vault, options.password, passwordTTL);
9826
+ await executeAgent(context, {
9827
+ viaAgent: options.viaAgent,
9828
+ verbose: options.verbose,
9829
+ backendUrl: options.backendUrl,
9830
+ password: options.password,
9831
+ sessionId: options.sessionId,
9832
+ notificationUrl: options.notificationUrl
9833
+ });
9834
+ }
9835
+ );
9555
9836
  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 instead of text").action(
9556
9837
  async (message, options) => {
9557
9838
  const parentOpts = agentCmd.opts();
9558
- const context = await init(
9559
- program.opts().vault,
9560
- options.password || parentOpts.password
9561
- );
9839
+ const context = await init(program.opts().vault, options.password || parentOpts.password);
9562
9840
  await executeAgentAsk(context, message, {
9563
9841
  ...options,
9564
9842
  backendUrl: options.backendUrl || parentOpts.backendUrl,
@@ -9625,8 +9903,10 @@ program.command("update").description("Check for updates and show update command
9625
9903
  );
9626
9904
  setupCompletionCommand(program);
9627
9905
  async function startInteractiveMode() {
9906
+ const serverEndpoints = resolveServerEndpoints(parseServerEndpointOverridesFromArgv(process.argv.slice(2)));
9628
9907
  const sdk = new Vultisig7({
9629
- onPasswordRequired: createPasswordCallback()
9908
+ onPasswordRequired: createPasswordCallback(),
9909
+ ...serverEndpoints ? { serverEndpoints } : {}
9630
9910
  });
9631
9911
  await sdk.initialize();
9632
9912
  const session = new ShellSession(sdk);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vultisig/cli",
3
- "version": "0.11.0",
3
+ "version": "0.14.0",
4
4
  "description": "Command-line wallet for Vultisig - multi-chain MPC wallet management",
5
5
  "type": "module",
6
6
  "bin": {
@@ -53,8 +53,8 @@
53
53
  "@cosmjs/proto-signing": "^0.38.1",
54
54
  "@cosmjs/stargate": "^0.38.1",
55
55
  "@noble/hashes": "^2.0.1",
56
- "@vultisig/rujira": "^7.0.0",
57
- "@vultisig/sdk": "^0.11.0",
56
+ "@vultisig/rujira": "^9.0.0",
57
+ "@vultisig/sdk": "^0.14.1",
58
58
  "chalk": "^5.6.2",
59
59
  "cli-table3": "^0.6.5",
60
60
  "commander": "^14.0.3",