@vultisig/cli 0.15.2 → 0.16.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/dist/index.js CHANGED
@@ -1049,14 +1049,14 @@ var require_main = __commonJS({
1049
1049
  cb = opts;
1050
1050
  opts = {};
1051
1051
  }
1052
- var qrcode5 = new QRCode(-1, this.error);
1053
- qrcode5.addData(input);
1054
- qrcode5.make();
1052
+ var qrcode4 = new QRCode(-1, this.error);
1053
+ qrcode4.addData(input);
1054
+ qrcode4.make();
1055
1055
  var output = "";
1056
1056
  if (opts && opts.small) {
1057
1057
  var BLACK = true, WHITE = false;
1058
- var moduleCount = qrcode5.getModuleCount();
1059
- var moduleData = qrcode5.modules.slice();
1058
+ var moduleCount = qrcode4.getModuleCount();
1059
+ var moduleData = qrcode4.modules.slice();
1060
1060
  var oddRow = moduleCount % 2 === 1;
1061
1061
  if (oddRow) {
1062
1062
  moduleData.push(fill(moduleCount, WHITE));
@@ -1089,9 +1089,9 @@ var require_main = __commonJS({
1089
1089
  output += borderBottom;
1090
1090
  }
1091
1091
  } else {
1092
- var border = repeat(white).times(qrcode5.getModuleCount() + 3);
1092
+ var border = repeat(white).times(qrcode4.getModuleCount() + 3);
1093
1093
  output += border + "\n";
1094
- qrcode5.modules.forEach(function(row2) {
1094
+ qrcode4.modules.forEach(function(row2) {
1095
1095
  output += white;
1096
1096
  output += row2.map(toCell).join("");
1097
1097
  output += white + "\n";
@@ -1452,9 +1452,10 @@ var init_formatUnits = __esm({
1452
1452
  // src/index.ts
1453
1453
  import "dotenv/config";
1454
1454
  import { promises as fs4 } from "node:fs";
1455
+ import { descriptions } from "@vultisig/client-shared";
1455
1456
  import { parseKeygenQR, Vultisig as Vultisig6 } from "@vultisig/sdk";
1456
1457
  import chalk15 from "chalk";
1457
- import { program } from "commander";
1458
+ import { InvalidArgumentError, program } from "commander";
1458
1459
  import inquirer8 from "inquirer";
1459
1460
 
1460
1461
  // src/core/command-context.ts
@@ -1536,12 +1537,32 @@ import chalk from "chalk";
1536
1537
  import ora from "ora";
1537
1538
  var silentMode = false;
1538
1539
  var outputFormat = "table";
1540
+ var nonInteractive = false;
1541
+ var quietMode = false;
1542
+ var fieldFilter;
1539
1543
  function setSilentMode(silent) {
1540
1544
  silentMode = silent;
1541
1545
  }
1542
1546
  function isSilent() {
1543
1547
  return silentMode;
1544
1548
  }
1549
+ function setNonInteractive(value) {
1550
+ nonInteractive = value;
1551
+ }
1552
+ function isNonInteractive() {
1553
+ return nonInteractive;
1554
+ }
1555
+ function requireInteractive(hint) {
1556
+ if (nonInteractive) {
1557
+ throw new Error(`Interactive prompt required but --non-interactive is set. ${hint}`);
1558
+ }
1559
+ }
1560
+ function setQuiet(value) {
1561
+ quietMode = value;
1562
+ }
1563
+ function setFields(fields) {
1564
+ fieldFilter = fields;
1565
+ }
1545
1566
  function initOutputMode(options) {
1546
1567
  outputFormat = options.output ?? "table";
1547
1568
  silentMode = options.silent ?? process.env.VULTISIG_SILENT === "1";
@@ -1552,14 +1573,72 @@ function initOutputMode(options) {
1552
1573
  function isJsonOutput() {
1553
1574
  return outputFormat === "json";
1554
1575
  }
1576
+ function stripEmpty(data) {
1577
+ if (Array.isArray(data)) return data.map(stripEmpty);
1578
+ if (data !== null && typeof data === "object") {
1579
+ return Object.fromEntries(
1580
+ Object.entries(data).filter(([, v]) => v != null && v !== "").map(([k, v]) => [k, stripEmpty(v)])
1581
+ );
1582
+ }
1583
+ return data;
1584
+ }
1585
+ function filterFields(data, fields) {
1586
+ if (!fields.length) return data;
1587
+ if (Array.isArray(data)) return data.map((item) => filterFields(item, fields));
1588
+ if (data !== null && typeof data === "object") {
1589
+ const entries = Object.entries(data);
1590
+ const matched = entries.filter(([k]) => fields.includes(k));
1591
+ if (matched.length > 0) return Object.fromEntries(matched);
1592
+ const recursed = entries.map(([k, v]) => [k, filterFields(v, fields)]);
1593
+ return Object.fromEntries(recursed);
1594
+ }
1595
+ return data;
1596
+ }
1597
+ function collectKeys(data) {
1598
+ const keys = /* @__PURE__ */ new Set();
1599
+ if (Array.isArray(data)) {
1600
+ for (const item of data) {
1601
+ if (item !== null && typeof item === "object") {
1602
+ for (const k of Object.keys(item)) keys.add(k);
1603
+ }
1604
+ }
1605
+ } else if (data !== null && typeof data === "object") {
1606
+ for (const [k, v] of Object.entries(data)) {
1607
+ keys.add(k);
1608
+ if (v !== null && typeof v === "object") {
1609
+ for (const nested of collectKeys(v)) keys.add(nested);
1610
+ }
1611
+ }
1612
+ }
1613
+ return keys;
1614
+ }
1615
+ function warnInvalidFields(data, fields) {
1616
+ const available = collectKeys(data);
1617
+ if (available.size === 0) return;
1618
+ const invalid = fields.filter((f) => !available.has(f));
1619
+ if (invalid.length > 0) {
1620
+ process.stderr.write(`Warning: unknown field(s): ${invalid.join(", ")}. Available: ${[...available].join(", ")}
1621
+ `);
1622
+ }
1623
+ }
1624
+ function applyOutputTransforms(data) {
1625
+ let out = quietMode ? stripEmpty(data) : data;
1626
+ if (fieldFilter?.length) {
1627
+ warnInvalidFields(out, fieldFilter);
1628
+ out = filterFields(out, fieldFilter);
1629
+ }
1630
+ return out;
1631
+ }
1555
1632
  function bigIntReplacer(_key, value) {
1556
1633
  return typeof value === "bigint" ? value.toString() : value;
1557
1634
  }
1558
1635
  function outputJson(data) {
1559
- console.log(JSON.stringify({ success: true, data }, bigIntReplacer, 2));
1636
+ const transformed = applyOutputTransforms(data);
1637
+ console.log(JSON.stringify({ success: true, v: 1, data: transformed }, bigIntReplacer, 2));
1560
1638
  }
1561
- function outputJsonError(message, code) {
1562
- console.log(JSON.stringify({ success: false, error: { message, code } }, bigIntReplacer, 2));
1639
+ function outputErrorJson(errJson) {
1640
+ const transformed = applyOutputTransforms(errJson);
1641
+ console.log(JSON.stringify(transformed, bigIntReplacer, 2));
1563
1642
  }
1564
1643
  function info(message) {
1565
1644
  if (!silentMode) {
@@ -1661,11 +1740,24 @@ function createSpinner(text) {
1661
1740
  activeSpinners.add(spinner2);
1662
1741
  return spinner2;
1663
1742
  }
1664
- const spinner = wrapOraSpinner(ora(text).start());
1743
+ const spinner = wrapOraSpinner(ora({ text, stream: process.stderr }).start());
1665
1744
  activeSpinners.add(spinner);
1666
1745
  return spinner;
1667
1746
  }
1668
1747
 
1748
+ // src/core/credential-store.ts
1749
+ import {
1750
+ _resetAll,
1751
+ clearCredentials,
1752
+ getDecryptionPassword,
1753
+ getServerPassword,
1754
+ getStoredServerPassword,
1755
+ isUsingFileFallback,
1756
+ setDecryptionPassword,
1757
+ setFilePassphrase,
1758
+ setServerPassword
1759
+ } from "@vultisig/client-shared";
1760
+
1669
1761
  // src/core/password-manager.ts
1670
1762
  var passwordCache = /* @__PURE__ */ new Map();
1671
1763
  function cachePassword(vaultIdOrName, password) {
@@ -1700,12 +1792,15 @@ function getPasswordFromEnv(vaultId, vaultName) {
1700
1792
  if (vaultPasswords.has(vaultId)) {
1701
1793
  return vaultPasswords.get(vaultId);
1702
1794
  }
1703
- if (process.env.VAULT_PASSWORD) {
1704
- return process.env.VAULT_PASSWORD;
1795
+ if (process.env.VAULT_PASSWORD || process.env.VULTISIG_PASSWORD) {
1796
+ return process.env.VAULT_PASSWORD || process.env.VULTISIG_PASSWORD;
1705
1797
  }
1706
1798
  return null;
1707
1799
  }
1708
1800
  async function promptForPassword(vaultName, vaultId) {
1801
+ requireInteractive(
1802
+ 'Use --password flag, VAULT_PASSWORD env var, or "vsig auth setup" to store credentials in keyring.'
1803
+ );
1709
1804
  const displayName = vaultName || vaultId || "vault";
1710
1805
  const { password } = await inquirer.prompt([
1711
1806
  {
@@ -1722,14 +1817,25 @@ async function getPassword(vaultId, vaultName) {
1722
1817
  if (cachedPassword) {
1723
1818
  return cachedPassword;
1724
1819
  }
1820
+ try {
1821
+ const keyringPassword = await getServerPassword(vaultId);
1822
+ if (keyringPassword) {
1823
+ cachePassword(vaultId, keyringPassword);
1824
+ if (vaultName) cachePassword(vaultName, keyringPassword);
1825
+ return keyringPassword;
1826
+ }
1827
+ } catch {
1828
+ }
1725
1829
  const envPassword = getPasswordFromEnv(vaultId, vaultName);
1726
1830
  if (envPassword) {
1727
1831
  cachePassword(vaultId, envPassword);
1728
1832
  if (vaultName) cachePassword(vaultName, envPassword);
1729
1833
  return envPassword;
1730
1834
  }
1731
- if (isSilent() || isJsonOutput()) {
1732
- throw new Error("Password required but not provided. Set VAULT_PASSWORD or VAULT_PASSWORDS environment variable.");
1835
+ if (isSilent() || isJsonOutput() || isNonInteractive()) {
1836
+ throw new Error(
1837
+ "Password required but not provided. Set VAULT_PASSWORD or VAULT_PASSWORDS environment variable, or use --password flag."
1838
+ );
1733
1839
  }
1734
1840
  const password = await promptForPassword(vaultName, vaultId);
1735
1841
  cachePassword(vaultId, password);
@@ -1745,12 +1851,8 @@ async function ensureVaultUnlocked(vault, password) {
1745
1851
  if (!vault.isEncrypted || vault.isUnlocked()) {
1746
1852
  return;
1747
1853
  }
1748
- if (password) {
1749
- await vault.unlock(password);
1750
- return;
1751
- }
1752
- const inputPassword = await promptForPassword(vault.name, vault.id);
1753
- await vault.unlock(inputPassword);
1854
+ const resolvedPassword = password || await getPassword(vault.id, vault.name);
1855
+ await vault.unlock(resolvedPassword);
1754
1856
  }
1755
1857
 
1756
1858
  // src/adapters/cli-context.ts
@@ -1768,6 +1870,229 @@ var CLIContext = class extends BaseCommandContext {
1768
1870
  }
1769
1871
  };
1770
1872
 
1873
+ // src/core/errors.ts
1874
+ import { VaultError, VaultErrorCode, VaultImportError, VaultImportErrorCode } from "@vultisig/sdk";
1875
+ var EXIT_CODE_DESCRIPTIONS = {
1876
+ [0 /* SUCCESS */]: "Success",
1877
+ [1 /* USAGE */]: "Usage error (bad arguments, unknown command)",
1878
+ [2 /* AUTH_REQUIRED */]: "Authentication required",
1879
+ [3 /* NETWORK */]: "Network error (retryable)",
1880
+ [4 /* INVALID_INPUT */]: "Invalid input (bad chain, address, amount)",
1881
+ [5 /* RESOURCE_NOT_FOUND */]: "Resource not found (token, route)",
1882
+ [6 /* EXTERNAL_SERVICE */]: "External service error (retryable)",
1883
+ [7 /* UNKNOWN */]: "Unknown/unexpected error"
1884
+ };
1885
+ var VsigError = class extends Error {
1886
+ hint;
1887
+ suggestions;
1888
+ context;
1889
+ retryable = false;
1890
+ constructor(message, hint, suggestions, context) {
1891
+ super(message);
1892
+ this.name = this.constructor.name;
1893
+ this.hint = hint;
1894
+ this.suggestions = suggestions;
1895
+ this.context = context;
1896
+ }
1897
+ };
1898
+ var UsageError = class extends VsigError {
1899
+ exitCode = 1 /* USAGE */;
1900
+ code = "USAGE_ERROR";
1901
+ constructor(message, hint, suggestions) {
1902
+ super(message, hint, suggestions);
1903
+ }
1904
+ };
1905
+ var AuthRequiredError = class extends VsigError {
1906
+ exitCode = 2 /* AUTH_REQUIRED */;
1907
+ code = "AUTH_REQUIRED";
1908
+ constructor(message) {
1909
+ super(message ?? "Authentication required. Set up vault credentials.", "Ensure your vault is unlocked", [
1910
+ "vsig vaults",
1911
+ "vsig create"
1912
+ ]);
1913
+ }
1914
+ };
1915
+ var NetworkError = class extends VsigError {
1916
+ exitCode = 3 /* NETWORK */;
1917
+ code = "NETWORK_ERROR";
1918
+ retryable = true;
1919
+ constructor(message, hint, suggestions) {
1920
+ super(message, hint, suggestions);
1921
+ }
1922
+ };
1923
+ var InvalidChainError = class extends VsigError {
1924
+ exitCode = 4 /* INVALID_INPUT */;
1925
+ code = "INVALID_CHAIN";
1926
+ constructor(message, hint, suggestions, context) {
1927
+ super(message, hint, suggestions, context);
1928
+ }
1929
+ };
1930
+ var InvalidAddressError = class extends VsigError {
1931
+ exitCode = 4 /* INVALID_INPUT */;
1932
+ code = "INVALID_ADDRESS";
1933
+ constructor(message, hint, suggestions, context) {
1934
+ super(message, hint, suggestions, context);
1935
+ }
1936
+ };
1937
+ var InvalidInputError = class extends VsigError {
1938
+ exitCode = 4 /* INVALID_INPUT */;
1939
+ code = "INVALID_INPUT";
1940
+ constructor(message, hint, suggestions, context) {
1941
+ super(message, hint, suggestions, context);
1942
+ }
1943
+ };
1944
+ var InsufficientBalanceError = class extends VsigError {
1945
+ exitCode = 4 /* INVALID_INPUT */;
1946
+ code = "INSUFFICIENT_BALANCE";
1947
+ constructor(message, hint, suggestions, context) {
1948
+ super(message, hint, suggestions, context);
1949
+ }
1950
+ };
1951
+ var NoRouteError = class extends VsigError {
1952
+ exitCode = 5 /* RESOURCE_NOT_FOUND */;
1953
+ code = "NO_ROUTE";
1954
+ constructor(message, hint, suggestions, context) {
1955
+ super(message, hint, suggestions, context);
1956
+ }
1957
+ };
1958
+ var TokenNotFoundError = class extends VsigError {
1959
+ exitCode = 5 /* RESOURCE_NOT_FOUND */;
1960
+ code = "TOKEN_NOT_FOUND";
1961
+ constructor(message, hint, suggestions, context) {
1962
+ super(message, hint, suggestions, context);
1963
+ }
1964
+ };
1965
+ var ExternalServiceError = class extends VsigError {
1966
+ exitCode = 6 /* EXTERNAL_SERVICE */;
1967
+ code = "EXTERNAL_SERVICE";
1968
+ retryable = true;
1969
+ constructor(message, hint, suggestions) {
1970
+ super(message, hint, suggestions);
1971
+ }
1972
+ };
1973
+ var PricingUnavailableError = class extends VsigError {
1974
+ exitCode = 6 /* EXTERNAL_SERVICE */;
1975
+ code = "PRICING_UNAVAILABLE";
1976
+ retryable = true;
1977
+ constructor(message, hint, suggestions) {
1978
+ super(message, hint, suggestions);
1979
+ }
1980
+ };
1981
+ var UnknownError = class extends VsigError {
1982
+ exitCode = 7 /* UNKNOWN */;
1983
+ code = "UNKNOWN_ERROR";
1984
+ constructor(message) {
1985
+ super(message);
1986
+ }
1987
+ };
1988
+ function classifyError(err) {
1989
+ if (err instanceof VsigError) return err;
1990
+ if (err instanceof VaultError) {
1991
+ if (err.code === VaultErrorCode.BalanceFetchFailed && err.originalError) {
1992
+ const inner = classifyError(err.originalError);
1993
+ if (!(inner instanceof UnknownError)) return inner;
1994
+ }
1995
+ switch (err.code) {
1996
+ case VaultErrorCode.UnsupportedChain:
1997
+ case VaultErrorCode.ChainNotSupported:
1998
+ return new InvalidChainError(err.message);
1999
+ case VaultErrorCode.NetworkError:
2000
+ case VaultErrorCode.BalanceFetchFailed:
2001
+ case VaultErrorCode.Timeout:
2002
+ return new NetworkError(err.message);
2003
+ case VaultErrorCode.InvalidAmount:
2004
+ return new InvalidInputError(err.message);
2005
+ case VaultErrorCode.InvalidConfig: {
2006
+ const lowerMsg = err.message.toLowerCase();
2007
+ if (lowerMsg.includes("unknown chain") || lowerMsg.includes("unsupported chain") || lowerMsg.includes("chain not supported")) {
2008
+ const chainMatch = err.message.match(/chain[:\s]*"([^"]+)"/i);
2009
+ return new InvalidChainError(
2010
+ err.message,
2011
+ void 0,
2012
+ void 0,
2013
+ chainMatch ? { chain: chainMatch[1] } : void 0
2014
+ );
2015
+ }
2016
+ return new UsageError(err.message);
2017
+ }
2018
+ case VaultErrorCode.UnsupportedToken:
2019
+ return new TokenNotFoundError(err.message);
2020
+ case VaultErrorCode.BroadcastFailed:
2021
+ return new ExternalServiceError(err.message, "Broadcast failed \u2014 the node may be temporarily unavailable", [
2022
+ "Retry the transaction"
2023
+ ]);
2024
+ case VaultErrorCode.GasEstimationFailed:
2025
+ return new InvalidInputError(err.message, "Gas estimation failed \u2014 check balance and transaction params");
2026
+ case VaultErrorCode.SigningFailed:
2027
+ return new UnknownError(err.message);
2028
+ default:
2029
+ return new UnknownError(err.message);
2030
+ }
2031
+ }
2032
+ if (err instanceof VaultImportError) {
2033
+ switch (err.code) {
2034
+ case VaultImportErrorCode.PASSWORD_REQUIRED:
2035
+ case VaultImportErrorCode.INVALID_PASSWORD:
2036
+ return new AuthRequiredError(err.message);
2037
+ default:
2038
+ return new UsageError(err.message);
2039
+ }
2040
+ }
2041
+ const msg = err.message.toLowerCase();
2042
+ if (msg.includes("unsupported chain") || msg.includes("invalid chain") || msg.includes("unknown chain")) {
2043
+ const chainMatch = err.message.match(/chain[:\s]*"([^"]+)"/i) || err.message.match(/chain[:\s]+(\S+)/i);
2044
+ return new InvalidChainError(err.message, void 0, void 0, chainMatch ? { chain: chainMatch[1] } : void 0);
2045
+ }
2046
+ if (msg.includes("invalid address") || msg.includes("bad address") || msg.includes("malformed address")) {
2047
+ const addrMatch = err.message.match(/(0x[a-fA-F0-9]+|bc1[a-z0-9]+|[13][a-km-zA-HJ-NP-Z1-9]+)/i);
2048
+ return new InvalidAddressError(err.message, void 0, void 0, addrMatch ? { address: addrMatch[1] } : void 0);
2049
+ }
2050
+ if (msg.includes("insufficient") && msg.includes("balance")) {
2051
+ return new InsufficientBalanceError(err.message);
2052
+ }
2053
+ if (msg.includes("no route") || msg.includes("no swap") || msg.includes("no provider")) {
2054
+ return new NoRouteError(err.message);
2055
+ }
2056
+ if (msg.includes("token not found") || msg.includes("unknown token")) {
2057
+ return new TokenNotFoundError(err.message);
2058
+ }
2059
+ if (msg.includes("pricing") || msg.includes("price unavailable") || msg.includes("price service")) {
2060
+ return new PricingUnavailableError(err.message);
2061
+ }
2062
+ if (msg.includes("econnrefused") || msg.includes("etimedout") || msg.includes("enotfound") || msg.includes("econnreset") || msg.includes("fetch failed") || msg.includes("network") || msg.includes("socket hang up") || msg.includes("dns")) {
2063
+ return new NetworkError(err.message);
2064
+ }
2065
+ return new UnknownError(err.message);
2066
+ }
2067
+ function toErrorJson(err) {
2068
+ if (err instanceof VsigError) {
2069
+ const json = {
2070
+ success: false,
2071
+ v: 1,
2072
+ error: {
2073
+ code: err.code,
2074
+ exitCode: err.exitCode,
2075
+ message: err.message,
2076
+ hint: err.hint,
2077
+ retryable: err.retryable
2078
+ }
2079
+ };
2080
+ if (err.suggestions?.length) json.error.suggestions = err.suggestions;
2081
+ if (err.context) json.error.context = err.context;
2082
+ return json;
2083
+ }
2084
+ return {
2085
+ success: false,
2086
+ v: 1,
2087
+ error: {
2088
+ code: "UNKNOWN_ERROR",
2089
+ exitCode: 7 /* UNKNOWN */,
2090
+ message: err.message,
2091
+ retryable: false
2092
+ }
2093
+ };
2094
+ }
2095
+
1771
2096
  // src/adapters/cli-runner.ts
1772
2097
  function withExit(handler) {
1773
2098
  return async (...args) => {
@@ -1775,17 +2100,18 @@ function withExit(handler) {
1775
2100
  await handler(...args);
1776
2101
  process.exit(0);
1777
2102
  } catch (err) {
1778
- const exitCode = err.exitCode ?? 1;
2103
+ const classified = err instanceof VsigError ? err : err instanceof Error ? classifyError(err) : classifyError(new Error(String(err)));
1779
2104
  if (isJsonOutput()) {
1780
- outputJsonError(err.message, err.code ?? "GENERAL_ERROR");
1781
- process.exit(exitCode);
1782
- }
1783
- if (err.exitCode !== void 0) {
1784
- process.exit(err.exitCode);
2105
+ outputErrorJson(toErrorJson(classified));
2106
+ process.exit(classified.exitCode);
1785
2107
  }
1786
2108
  printError(`
1787
- x ${err.message}`);
1788
- process.exit(1);
2109
+ x ${classified.message}`);
2110
+ if (classified.hint) printError(` hint: ${classified.hint}`);
2111
+ if (classified.suggestions?.length) {
2112
+ for (const s of classified.suggestions) printError(` - ${s}`);
2113
+ }
2114
+ process.exit(classified.exitCode);
1789
2115
  }
1790
2116
  };
1791
2117
  }
@@ -1914,6 +2240,7 @@ function displayVaultsList(vaults, activeVault) {
1914
2240
  printTable(table);
1915
2241
  }
1916
2242
  async function confirmTransaction() {
2243
+ requireInteractive("Use --yes to skip confirmation, or --password to provide password non-interactively.");
1917
2244
  const { confirmed } = await inquirer2.prompt([
1918
2245
  {
1919
2246
  type: "confirm",
@@ -2033,6 +2360,7 @@ function displaySwapChains(chains) {
2033
2360
  Total: ${chains.length} chains`);
2034
2361
  }
2035
2362
  async function confirmSwap() {
2363
+ requireInteractive("Use --yes to skip confirmation, or --password to provide password non-interactively.");
2036
2364
  const { confirmed } = await inquirer2.prompt([
2037
2365
  {
2038
2366
  type: "confirm",
@@ -2121,13 +2449,25 @@ async function executeChains(ctx2, options = {}) {
2121
2449
  return;
2122
2450
  }
2123
2451
  if (options.add) {
2124
- await vault.addChain(options.add);
2125
- success(`
2126
- + Added chain: ${options.add}`);
2452
+ const alreadyActive = vault.chains.includes(options.add);
2453
+ if (!alreadyActive) {
2454
+ await vault.addChain(options.add);
2455
+ }
2127
2456
  const address = await vault.address(options.add);
2457
+ if (isJsonOutput()) {
2458
+ outputJson({ chain: options.add, alreadyActive, address, chains: [...vault.chains] });
2459
+ return;
2460
+ }
2461
+ success(alreadyActive ? `
2462
+ Chain already active: ${options.add}` : `
2463
+ + Added chain: ${options.add}`);
2128
2464
  info(`Address: ${address}`);
2129
2465
  } else if (options.remove) {
2130
2466
  await vault.removeChain(options.remove);
2467
+ if (isJsonOutput()) {
2468
+ outputJson({ chain: options.remove, removed: true, chains: [...vault.chains] });
2469
+ return;
2470
+ }
2131
2471
  success(`
2132
2472
  + Removed chain: ${options.remove}`);
2133
2473
  } else {
@@ -2162,6 +2502,10 @@ import chalk4 from "chalk";
2162
2502
  import inquirer3 from "inquirer";
2163
2503
  async function executeTokens(ctx2, options) {
2164
2504
  await ctx2.ensureActiveVault();
2505
+ if (options.discover) {
2506
+ await discoverTokens(ctx2, options.chain);
2507
+ return;
2508
+ }
2165
2509
  if (options.add) {
2166
2510
  let symbol = options.symbol;
2167
2511
  let name = options.name;
@@ -2193,6 +2537,7 @@ async function executeTokens(ctx2, options) {
2193
2537
  });
2194
2538
  }
2195
2539
  if (prompts.length > 0) {
2540
+ requireInteractive("Provide --symbol, --name, and --decimals flags for non-interactive token addition.");
2196
2541
  const answers = await inquirer3.prompt(prompts);
2197
2542
  symbol = symbol || answers.symbol?.trim();
2198
2543
  name = name || answers.name?.trim();
@@ -2231,6 +2576,57 @@ async function removeToken(ctx2, chain, tokenId) {
2231
2576
  success(`
2232
2577
  + Removed token ${tokenId} from ${chain}`);
2233
2578
  }
2579
+ async function discoverTokens(ctx2, chain) {
2580
+ const vault = await ctx2.ensureActiveVault();
2581
+ const spinner = createSpinner(`Discovering tokens on ${chain}...`);
2582
+ let discovered;
2583
+ try {
2584
+ discovered = await vault.discoverTokens(chain);
2585
+ } catch (err) {
2586
+ spinner.fail(`Token discovery failed for ${chain}`);
2587
+ throw err;
2588
+ }
2589
+ if (discovered.length === 0) {
2590
+ spinner.succeed(`No new tokens found on ${chain}`);
2591
+ if (isJsonOutput()) {
2592
+ outputJson({ chain, discovered: [], count: 0 });
2593
+ }
2594
+ return;
2595
+ }
2596
+ const existingTokens = vault.getTokens(chain);
2597
+ const existingAddresses = new Set(existingTokens.map((t) => t.contractAddress ?? t.id));
2598
+ const newTokens = discovered.filter((d) => d.contractAddress && !existingAddresses.has(d.contractAddress));
2599
+ for (const d of newTokens) {
2600
+ await vault.addToken(chain, {
2601
+ id: d.contractAddress,
2602
+ symbol: d.ticker,
2603
+ name: d.ticker,
2604
+ decimals: d.decimals,
2605
+ contractAddress: d.contractAddress,
2606
+ chainId: chain,
2607
+ isNative: false
2608
+ });
2609
+ }
2610
+ const allTokens = vault.getTokens(chain);
2611
+ spinner.succeed(`Discovered ${newTokens.length} new token(s) on ${chain}`);
2612
+ if (isJsonOutput()) {
2613
+ outputJson({
2614
+ chain,
2615
+ discovered: newTokens.map((d) => ({
2616
+ symbol: d.ticker,
2617
+ contractAddress: d.contractAddress,
2618
+ decimals: d.decimals
2619
+ })),
2620
+ count: newTokens.length
2621
+ });
2622
+ return;
2623
+ }
2624
+ for (const d of newTokens) {
2625
+ printResult(` ${d.ticker} (${d.contractAddress})`);
2626
+ }
2627
+ info(chalk4.gray(`
2628
+ ${allTokens.length} total token(s) tracked on ${chain}`));
2629
+ }
2234
2630
  async function listTokens(ctx2, chain) {
2235
2631
  const vault = await ctx2.ensureActiveVault();
2236
2632
  const spinner = createSpinner(`Loading tokens for ${chain}...`);
@@ -2263,8 +2659,15 @@ Use --add <contractAddress> to add or --remove <tokenId> to remove`));
2263
2659
  }
2264
2660
 
2265
2661
  // src/commands/transaction.ts
2266
- var import_qrcode_terminal = __toESM(require_main(), 1);
2267
2662
  import { Chain, Vultisig as Vultisig2 } from "@vultisig/sdk";
2663
+
2664
+ // src/core/config-store.ts
2665
+ import { getConfigPath, loadConfig, saveConfig } from "@vultisig/client-shared";
2666
+
2667
+ // src/core/vault-discovery.ts
2668
+ import { discoverVaultFiles, SEARCH_DIRS } from "@vultisig/client-shared";
2669
+
2670
+ // src/commands/transaction.ts
2268
2671
  async function executeSend(ctx2, params) {
2269
2672
  const vault = await ctx2.ensureActiveVault();
2270
2673
  if (!Object.values(Chain).includes(params.chain)) {
@@ -2278,59 +2681,59 @@ async function executeSend(ctx2, params) {
2278
2681
  }
2279
2682
  async function sendTransaction(vault, params) {
2280
2683
  const prepareSpinner = createSpinner("Preparing transaction...");
2281
- const address = await vault.address(params.chain);
2282
- const balance = await vault.balance(params.chain, params.tokenId);
2283
- const coin = {
2684
+ const dryResult = await vault.send({
2284
2685
  chain: params.chain,
2285
- address,
2286
- decimals: balance.decimals,
2287
- ticker: balance.symbol,
2288
- id: params.tokenId
2289
- };
2290
- const isMax = params.amount === "max";
2291
- let amount;
2292
- let displayAmount;
2293
- if (isMax) {
2294
- const maxInfo = await vault.getMaxSendAmount({ coin, receiver: params.to, memo: params.memo });
2295
- amount = maxInfo.maxSendable;
2296
- if (amount === 0n) {
2297
- throw new Error("Insufficient balance to cover network fees");
2298
- }
2299
- displayAmount = formatBigintAmount(amount, balance.decimals);
2300
- } else {
2301
- const [whole, frac = ""] = params.amount.split(".");
2302
- if (frac.length > balance.decimals) {
2303
- throw new Error(`Amount has more than ${balance.decimals} decimal places`);
2304
- }
2305
- const paddedFrac = frac.padEnd(balance.decimals, "0");
2306
- amount = BigInt(whole || "0") * 10n ** BigInt(balance.decimals) + BigInt(paddedFrac || "0");
2307
- displayAmount = params.amount;
2308
- }
2309
- const payload = await vault.prepareSendTx({
2310
- coin,
2311
- receiver: params.to,
2312
- amount,
2313
- memo: params.memo
2686
+ to: params.to,
2687
+ amount: params.amount,
2688
+ symbol: params.tokenId,
2689
+ memo: params.memo,
2690
+ dryRun: true
2314
2691
  });
2315
2692
  prepareSpinner.succeed("Transaction prepared");
2693
+ if (!dryResult.dryRun) throw new Error("unreachable");
2694
+ if (params.dryRun) {
2695
+ const balance2 = await vault.balance(params.chain, params.tokenId);
2696
+ const hasInsufficientBalance = parseFloat(dryResult.total) > parseFloat(balance2.formattedAmount);
2697
+ const result = {
2698
+ dryRun: true,
2699
+ chain: params.chain,
2700
+ to: params.to,
2701
+ amount: params.amount,
2702
+ symbol: balance2.symbol,
2703
+ balance: balance2.formattedAmount
2704
+ };
2705
+ if (hasInsufficientBalance) {
2706
+ result.warning = `Insufficient balance: you have ${balance2.formattedAmount} ${balance2.symbol}`;
2707
+ }
2708
+ if (isJsonOutput()) {
2709
+ outputJson(result);
2710
+ } else {
2711
+ info(`
2712
+ Dry-run preview:`);
2713
+ info(` Chain: ${result.chain}`);
2714
+ info(` To: ${result.to}`);
2715
+ info(` Amount: ${result.amount} ${result.symbol}`);
2716
+ info(` Fee: ${dryResult.fee} ${result.symbol}`);
2717
+ info(` Balance: ${result.balance} ${result.symbol}`);
2718
+ if (result.warning) warn(` Warning: ${result.warning}`);
2719
+ }
2720
+ return result;
2721
+ }
2316
2722
  let gas;
2317
2723
  try {
2318
2724
  gas = await vault.gas(params.chain);
2319
2725
  } catch {
2320
2726
  warn("\nGas estimation unavailable");
2321
2727
  }
2728
+ const balance = await vault.balance(params.chain, params.tokenId);
2322
2729
  if (!isJsonOutput()) {
2323
- displayTransactionPreview(
2324
- payload.coin.address,
2325
- params.to,
2326
- displayAmount,
2327
- payload.coin.ticker,
2328
- params.chain,
2329
- params.memo,
2330
- gas
2331
- );
2730
+ const address = await vault.address(params.chain);
2731
+ displayTransactionPreview(address, params.to, dryResult.total, balance.symbol, params.chain, params.memo, gas);
2332
2732
  }
2333
- if (!params.yes && !isJsonOutput()) {
2733
+ if (!params.yes) {
2734
+ if (isNonInteractive()) {
2735
+ throw new Error("Transaction requires confirmation. Use --yes to skip, or --dry-run to preview.");
2736
+ }
2334
2737
  const confirmed = await confirmTransaction();
2335
2738
  if (!confirmed) {
2336
2739
  warn("Transaction cancelled");
@@ -2338,90 +2741,55 @@ async function sendTransaction(vault, params) {
2338
2741
  }
2339
2742
  }
2340
2743
  await ensureVaultUnlocked(vault, params.password);
2341
- const isSecureVault = vault.type === "secure";
2342
- const signSpinner = createSpinner(isSecureVault ? "Preparing secure signing session..." : "Signing transaction...");
2744
+ const signSpinner = createSpinner(
2745
+ vault.type === "secure" ? "Preparing secure signing session..." : "Signing transaction..."
2746
+ );
2343
2747
  vault.on("signingProgress", ({ step }) => {
2344
2748
  signSpinner.text = `${step.message} (${step.progress}%)`;
2345
2749
  });
2346
- if (isSecureVault) {
2347
- vault.on("qrCodeReady", ({ qrPayload }) => {
2348
- if (isJsonOutput()) {
2349
- printResult(qrPayload);
2350
- } else if (isSilent()) {
2351
- printResult(`QR Payload: ${qrPayload}`);
2352
- } else {
2353
- signSpinner.stop();
2354
- info("\nScan this QR code with your Vultisig mobile app to sign:");
2355
- import_qrcode_terminal.default.generate(qrPayload, { small: true });
2356
- info(`
2357
- Or use this URL: ${qrPayload}
2358
- `);
2359
- signSpinner.start("Waiting for devices to join signing session...");
2360
- }
2361
- });
2362
- vault.on(
2363
- "deviceJoined",
2364
- ({ deviceId, totalJoined, required }) => {
2365
- if (!isSilent()) {
2366
- signSpinner.text = `Device joined: ${totalJoined}/${required} (${deviceId})`;
2367
- } else if (!isJsonOutput()) {
2368
- printResult(`Device joined: ${totalJoined}/${required}`);
2369
- }
2370
- }
2371
- );
2372
- }
2373
2750
  try {
2374
- const messageHashes = await vault.extractMessageHashes(payload);
2375
- const signature = await vault.sign(
2376
- {
2377
- transaction: payload,
2378
- chain: payload.coin.chain,
2379
- messageHashes
2380
- },
2381
- { signal: params.signal }
2382
- );
2383
- signSpinner.succeed("Transaction signed");
2384
- const broadcastSpinner = createSpinner("Broadcasting transaction...");
2385
- const txHash = await vault.broadcastTx({
2751
+ const result = await vault.send({
2386
2752
  chain: params.chain,
2387
- keysignPayload: payload,
2388
- signature
2753
+ to: params.to,
2754
+ amount: params.amount,
2755
+ symbol: params.tokenId,
2756
+ memo: params.memo
2389
2757
  });
2390
- broadcastSpinner.succeed(`Transaction broadcast: ${txHash}`);
2391
- const result = {
2392
- txHash,
2758
+ if (result.dryRun) throw new Error("unreachable");
2759
+ const broadcast = result;
2760
+ signSpinner.succeed(`Transaction broadcast: ${broadcast.txHash}`);
2761
+ const txResult = {
2762
+ txHash: broadcast.txHash,
2393
2763
  chain: params.chain,
2394
- explorerUrl: Vultisig2.getTxExplorerUrl(params.chain, txHash)
2764
+ explorerUrl: Vultisig2.getTxExplorerUrl(params.chain, broadcast.txHash)
2395
2765
  };
2396
2766
  if (isJsonOutput()) {
2397
- outputJson(result);
2767
+ outputJson(txResult);
2398
2768
  } else {
2399
- displayTransactionResult(params.chain, txHash);
2769
+ displayTransactionResult(params.chain, broadcast.txHash);
2400
2770
  }
2401
- return result;
2771
+ return txResult;
2402
2772
  } finally {
2403
2773
  vault.removeAllListeners("signingProgress");
2404
- if (isSecureVault) {
2405
- vault.removeAllListeners("qrCodeReady");
2406
- vault.removeAllListeners("deviceJoined");
2407
- }
2408
2774
  }
2409
2775
  }
2410
2776
 
2411
2777
  // src/commands/execute.ts
2412
- var import_qrcode_terminal2 = __toESM(require_main(), 1);
2778
+ var import_qrcode_terminal = __toESM(require_main(), 1);
2413
2779
  import { Vultisig as Vultisig3 } from "@vultisig/sdk";
2414
2780
  var COSMOS_CHAIN_CONFIG = {
2415
2781
  THORChain: {
2416
2782
  chainId: "thorchain-1",
2417
2783
  prefix: "thor",
2418
2784
  denom: "rune",
2785
+ decimals: 8,
2419
2786
  gasLimit: "500000"
2420
2787
  },
2421
2788
  MayaChain: {
2422
2789
  chainId: "mayachain-mainnet-v1",
2423
2790
  prefix: "maya",
2424
2791
  denom: "cacao",
2792
+ decimals: 10,
2425
2793
  gasLimit: "500000"
2426
2794
  }
2427
2795
  };
@@ -2456,8 +2824,42 @@ async function executeContractTransaction(vault, params, chainConfig, msg, funds
2456
2824
  const prepareSpinner = createSpinner("Preparing contract execution...");
2457
2825
  const address = await vault.address(params.chain);
2458
2826
  prepareSpinner.succeed("Transaction prepared");
2827
+ if (params.dryRun) {
2828
+ if (isJsonOutput()) {
2829
+ outputJson({
2830
+ dryRun: true,
2831
+ chain: params.chain,
2832
+ contract: params.contract,
2833
+ msg,
2834
+ funds,
2835
+ address,
2836
+ memo: params.memo
2837
+ });
2838
+ } else {
2839
+ info("\nContract Execution Preview (dry-run)");
2840
+ info("\u2501".repeat(50));
2841
+ info(`Chain: ${params.chain}`);
2842
+ info(`From: ${address}`);
2843
+ info(`Contract: ${params.contract}`);
2844
+ info(
2845
+ `Message: ${JSON.stringify(msg, null, 2).substring(0, 200)}${JSON.stringify(msg).length > 200 ? "..." : ""}`
2846
+ );
2847
+ if (funds.length > 0) {
2848
+ info(`Funds: ${funds.map((f) => `${f.amount} ${f.denom}`).join(", ")}`);
2849
+ }
2850
+ if (params.memo) {
2851
+ info(`Memo: ${params.memo}`);
2852
+ }
2853
+ info("\u2501".repeat(50));
2854
+ }
2855
+ return {
2856
+ txHash: "",
2857
+ chain: params.chain,
2858
+ explorerUrl: ""
2859
+ };
2860
+ }
2459
2861
  if (!isJsonOutput()) {
2460
- info("\n\u{1F4DD} Contract Execution Preview");
2862
+ info("\nContract Execution Preview");
2461
2863
  info("\u2501".repeat(50));
2462
2864
  info(`Chain: ${params.chain}`);
2463
2865
  info(`From: ${address}`);
@@ -2473,7 +2875,10 @@ async function executeContractTransaction(vault, params, chainConfig, msg, funds
2473
2875
  }
2474
2876
  info("\u2501".repeat(50));
2475
2877
  }
2476
- if (!params.yes && !isJsonOutput()) {
2878
+ if (!params.yes) {
2879
+ if (isNonInteractive()) {
2880
+ throw new Error("Transaction requires confirmation. Use --yes to skip, or --dry-run to preview.");
2881
+ }
2477
2882
  const confirmed = await confirmTransaction();
2478
2883
  if (!confirmed) {
2479
2884
  warn("Transaction cancelled");
@@ -2495,7 +2900,7 @@ async function executeContractTransaction(vault, params, chainConfig, msg, funds
2495
2900
  } else {
2496
2901
  signSpinner.stop();
2497
2902
  info("\nScan this QR code with your Vultisig mobile app to sign:");
2498
- import_qrcode_terminal2.default.generate(qrPayload, { small: true });
2903
+ import_qrcode_terminal.default.generate(qrPayload, { small: true });
2499
2904
  info(`
2500
2905
  Or use this URL: ${qrPayload}
2501
2906
  `);
@@ -2518,8 +2923,7 @@ Or use this URL: ${qrPayload}
2518
2923
  const coin = {
2519
2924
  chain: cosmosChain,
2520
2925
  address,
2521
- decimals: 8,
2522
- // THORChain uses 8 decimals
2926
+ decimals: chainConfig.decimals,
2523
2927
  ticker: chainConfig.denom.toUpperCase()
2524
2928
  };
2525
2929
  const executeContractMsg = {
@@ -2580,7 +2984,7 @@ Or use this URL: ${qrPayload}
2580
2984
  }
2581
2985
 
2582
2986
  // src/commands/sign.ts
2583
- var import_qrcode_terminal3 = __toESM(require_main(), 1);
2987
+ var import_qrcode_terminal2 = __toESM(require_main(), 1);
2584
2988
  import { Chain as Chain3 } from "@vultisig/sdk";
2585
2989
  async function executeSignBytes(ctx2, params) {
2586
2990
  const vault = await ctx2.ensureActiveVault();
@@ -2606,7 +3010,7 @@ async function signBytes(vault, params) {
2606
3010
  } else {
2607
3011
  signSpinner.stop();
2608
3012
  info("\nScan this QR code with your Vultisig mobile app to sign:");
2609
- import_qrcode_terminal3.default.generate(qrPayload, { small: true });
3013
+ import_qrcode_terminal2.default.generate(qrPayload, { small: true });
2610
3014
  info(`
2611
3015
  Or use this URL: ${qrPayload}
2612
3016
  `);
@@ -2756,7 +3160,7 @@ function sleep(ms) {
2756
3160
  }
2757
3161
 
2758
3162
  // src/commands/vault-management.ts
2759
- var import_qrcode_terminal4 = __toESM(require_main(), 1);
3163
+ var import_qrcode_terminal3 = __toESM(require_main(), 1);
2760
3164
  import { FastVault } from "@vultisig/sdk";
2761
3165
  import chalk5 from "chalk";
2762
3166
  import { promises as fs } from "fs";
@@ -2904,7 +3308,7 @@ async function executeCreateSecure(ctx2, options) {
2904
3308
  } else {
2905
3309
  spinner.stop();
2906
3310
  info("\nScan this QR code with your Vultisig mobile app:");
2907
- import_qrcode_terminal4.default.generate(qrPayload, { small: true });
3311
+ import_qrcode_terminal3.default.generate(qrPayload, { small: true });
2908
3312
  info(`
2909
3313
  Or use this URL: ${qrPayload}
2910
3314
  `);
@@ -3427,7 +3831,7 @@ async function executeCreateFromSeedphraseSecure(ctx2, options) {
3427
3831
  } else {
3428
3832
  importSpinner.stop();
3429
3833
  info("\nScan this QR code with your Vultisig mobile app:");
3430
- import_qrcode_terminal4.default.generate(qrPayload, { small: true });
3834
+ import_qrcode_terminal3.default.generate(qrPayload, { small: true });
3431
3835
  info(`
3432
3836
  Or use this URL: ${qrPayload}
3433
3837
  `);
@@ -3616,35 +4020,24 @@ async function executeSwapQuote(ctx2, options) {
3616
4020
  if (!isMax && (isNaN(options.amount) || options.amount <= 0)) {
3617
4021
  throw new Error("Invalid amount");
3618
4022
  }
3619
- const isSupported = await vault.isSwapSupported(options.fromChain, options.toChain);
3620
- if (!isSupported) {
3621
- throw new Error(`Swaps from ${options.fromChain} to ${options.toChain} are not supported`);
3622
- }
3623
- let resolvedAmount;
3624
- if (isMax) {
3625
- const bal = await vault.balance(options.fromChain, options.fromToken);
3626
- resolvedAmount = parseFloat(bal.formattedAmount);
3627
- if (resolvedAmount <= 0) {
3628
- throw new Error("Zero balance \u2014 nothing to swap");
3629
- }
3630
- } else {
3631
- resolvedAmount = options.amount;
3632
- }
3633
4023
  const spinner = createSpinner("Getting swap quote...");
3634
- const quote = await vault.getSwapQuote({
3635
- fromCoin: { chain: options.fromChain, token: options.fromToken },
3636
- toCoin: { chain: options.toChain, token: options.toToken },
3637
- amount: resolvedAmount,
3638
- fiatCurrency: "usd"
3639
- // Request fiat conversion
4024
+ const result = await vault.swap({
4025
+ fromChain: options.fromChain,
4026
+ fromSymbol: options.fromToken || "",
4027
+ toChain: options.toChain,
4028
+ toSymbol: options.toToken || "",
4029
+ amount: isMax ? "max" : String(options.amount),
4030
+ dryRun: true
3640
4031
  });
4032
+ if (!result.dryRun) throw new Error("unreachable");
3641
4033
  spinner.succeed("Quote received");
3642
- const fromAmountDisplay = isMax ? `${formatBigintAmount(quote.maxSwapable, quote.fromCoin.decimals)} (max)` : String(resolvedAmount);
4034
+ const quote = result.quote;
4035
+ const fromAmountDisplay = isMax ? `${formatBigintAmount(quote.maxSwapable, quote.fromCoin.decimals)} (max)` : String(options.amount);
3643
4036
  if (isJsonOutput()) {
3644
4037
  outputJson({
3645
4038
  fromChain: options.fromChain,
3646
4039
  toChain: options.toChain,
3647
- amount: resolvedAmount,
4040
+ amount: isMax ? "max" : options.amount,
3648
4041
  isMax,
3649
4042
  quote
3650
4043
  });
@@ -3668,30 +4061,57 @@ async function executeSwap(ctx2, options) {
3668
4061
  if (!isMax && (isNaN(options.amount) || options.amount <= 0)) {
3669
4062
  throw new Error("Invalid amount");
3670
4063
  }
3671
- const isSupported = await vault.isSwapSupported(options.fromChain, options.toChain);
3672
- if (!isSupported) {
3673
- throw new Error(`Swaps from ${options.fromChain} to ${options.toChain} are not supported`);
3674
- }
3675
- let resolvedAmount;
3676
- if (isMax) {
3677
- const bal = await vault.balance(options.fromChain, options.fromToken);
3678
- resolvedAmount = parseFloat(bal.formattedAmount);
3679
- if (resolvedAmount <= 0) {
3680
- throw new Error("Zero balance \u2014 nothing to swap");
3681
- }
3682
- } else {
3683
- resolvedAmount = options.amount;
3684
- }
4064
+ const amountStr = isMax ? "max" : String(options.amount);
3685
4065
  const quoteSpinner = createSpinner("Getting swap quote...");
3686
- const quote = await vault.getSwapQuote({
3687
- fromCoin: { chain: options.fromChain, token: options.fromToken },
3688
- toCoin: { chain: options.toChain, token: options.toToken },
3689
- amount: resolvedAmount,
3690
- fiatCurrency: "usd"
3691
- // Request fiat conversion
4066
+ const dryResult = await vault.swap({
4067
+ fromChain: options.fromChain,
4068
+ fromSymbol: options.fromToken || "",
4069
+ toChain: options.toChain,
4070
+ toSymbol: options.toToken || "",
4071
+ amount: amountStr,
4072
+ dryRun: true
3692
4073
  });
4074
+ if (!dryResult.dryRun) throw new Error("unreachable");
3693
4075
  quoteSpinner.succeed("Quote received");
3694
- const fromAmountDisplay = isMax ? `${formatBigintAmount(quote.maxSwapable, quote.fromCoin.decimals)} (max)` : String(resolvedAmount);
4076
+ const quote = dryResult.quote;
4077
+ const fromAmountRaw = isMax ? formatBigintAmount(quote.maxSwapable, quote.fromCoin.decimals) : String(options.amount);
4078
+ const fromAmountDisplay = isMax ? `${fromAmountRaw} (max)` : fromAmountRaw;
4079
+ if (options.dryRun) {
4080
+ const estimatedOutput = formatBigintAmount(quote.estimatedOutput, quote.toCoin.decimals);
4081
+ const result = {
4082
+ dryRun: true,
4083
+ fromChain: String(options.fromChain),
4084
+ fromToken: quote.fromCoin.ticker,
4085
+ toChain: String(options.toChain),
4086
+ toToken: quote.toCoin.ticker,
4087
+ inputAmount: fromAmountRaw,
4088
+ ...isMax && { isMax: true },
4089
+ estimatedOutput,
4090
+ provider: quote.provider
4091
+ };
4092
+ if (quote.estimatedOutputFiat != null) {
4093
+ result.estimatedOutputFiat = parseFloat(quote.estimatedOutputFiat.toFixed(2));
4094
+ }
4095
+ if (quote.requiresApproval) {
4096
+ result.requiresApproval = true;
4097
+ }
4098
+ if (quote.warnings && quote.warnings.length > 0) {
4099
+ result.warnings = [...quote.warnings];
4100
+ }
4101
+ if (isJsonOutput()) {
4102
+ outputJson(result);
4103
+ } else {
4104
+ info(`
4105
+ Dry-run preview:`);
4106
+ info(` From: ${result.inputAmount} ${result.fromToken} (${result.fromChain})`);
4107
+ info(` To: ${result.estimatedOutput} ${result.toToken} (${result.toChain})`);
4108
+ info(` Provider: ${result.provider}`);
4109
+ if (result.estimatedOutputFiat != null) info(` Est. value (USD): $${result.estimatedOutputFiat}`);
4110
+ if (result.requiresApproval) info(` Requires approval: yes`);
4111
+ if (result.warnings?.length) result.warnings.forEach((w) => warn(` Warning: ${w}`));
4112
+ }
4113
+ return result;
4114
+ }
3695
4115
  const feeBalance = await vault.balance(options.fromChain);
3696
4116
  const discountTier = await vault.getDiscountTier();
3697
4117
  if (!isJsonOutput()) {
@@ -3703,86 +4123,43 @@ async function executeSwap(ctx2, options) {
3703
4123
  discountTier
3704
4124
  });
3705
4125
  }
3706
- if (!options.yes && !isJsonOutput()) {
4126
+ if (!options.yes) {
4127
+ if (isNonInteractive()) {
4128
+ throw new Error("Swap requires confirmation. Use --yes to skip, or --dry-run to preview.");
4129
+ }
3707
4130
  const confirmed = await confirmSwap();
3708
4131
  if (!confirmed) {
3709
4132
  warn("Swap cancelled");
3710
4133
  throw new Error("Swap cancelled by user");
3711
4134
  }
3712
4135
  }
3713
- const prepSpinner = createSpinner("Preparing swap transaction...");
3714
- const { keysignPayload, approvalPayload } = await vault.prepareSwapTx({
3715
- fromCoin: { chain: options.fromChain, token: options.fromToken },
3716
- toCoin: { chain: options.toChain, token: options.toToken },
3717
- amount: resolvedAmount,
3718
- swapQuote: quote,
3719
- autoApprove: false
3720
- });
3721
- prepSpinner.succeed("Swap prepared");
3722
4136
  await ensureVaultUnlocked(vault, options.password);
3723
- if (approvalPayload) {
3724
- info("\nToken approval required before swap...");
3725
- const approvalSpinner = createSpinner("Signing approval transaction...");
3726
- vault.on("signingProgress", ({ step }) => {
3727
- approvalSpinner.text = `Approval: ${step.message} (${step.progress}%)`;
3728
- });
3729
- try {
3730
- const approvalHashes = await vault.extractMessageHashes(approvalPayload);
3731
- const approvalSig = await vault.sign(
3732
- {
3733
- transaction: approvalPayload,
3734
- chain: options.fromChain,
3735
- messageHashes: approvalHashes
3736
- },
3737
- { signal: options.signal }
3738
- );
3739
- approvalSpinner.succeed("Approval signed");
3740
- const broadcastApprovalSpinner = createSpinner("Broadcasting approval...");
3741
- const approvalTxHash = await vault.broadcastTx({
3742
- chain: options.fromChain,
3743
- keysignPayload: approvalPayload,
3744
- signature: approvalSig
3745
- });
3746
- broadcastApprovalSpinner.succeed(`Approval broadcast: ${approvalTxHash}`);
3747
- info("Waiting for approval to confirm...");
3748
- await new Promise((resolve) => setTimeout(resolve, 5e3));
3749
- } finally {
3750
- vault.removeAllListeners("signingProgress");
3751
- }
3752
- }
3753
4137
  const signSpinner = createSpinner("Signing swap transaction...");
3754
4138
  vault.on("signingProgress", ({ step }) => {
3755
4139
  signSpinner.text = `${step.message} (${step.progress}%)`;
3756
4140
  });
3757
4141
  try {
3758
- const messageHashes = await vault.extractMessageHashes(keysignPayload);
3759
- const signature = await vault.sign(
3760
- {
3761
- transaction: keysignPayload,
3762
- chain: options.fromChain,
3763
- messageHashes
3764
- },
3765
- { signal: options.signal }
3766
- );
3767
- signSpinner.succeed("Swap transaction signed");
3768
- const broadcastSpinner = createSpinner("Broadcasting swap transaction...");
3769
- const txHash = await vault.broadcastTx({
3770
- chain: options.fromChain,
3771
- keysignPayload,
3772
- signature
4142
+ const result = await vault.swap({
4143
+ fromChain: options.fromChain,
4144
+ fromSymbol: options.fromToken || "",
4145
+ toChain: options.toChain,
4146
+ toSymbol: options.toToken || "",
4147
+ amount: amountStr
3773
4148
  });
3774
- broadcastSpinner.succeed(`Swap broadcast: ${txHash}`);
4149
+ if (result.dryRun) throw new Error("unreachable");
4150
+ const broadcast = result;
4151
+ signSpinner.succeed(`Swap broadcast: ${broadcast.txHash}`);
3775
4152
  if (isJsonOutput()) {
3776
4153
  outputJson({
3777
- txHash,
4154
+ txHash: broadcast.txHash,
3778
4155
  fromChain: options.fromChain,
3779
4156
  toChain: options.toChain,
3780
4157
  quote
3781
4158
  });
3782
4159
  } else {
3783
- displaySwapResult(options.fromChain, options.toChain, txHash, quote, quote.toCoin.decimals);
4160
+ displaySwapResult(options.fromChain, options.toChain, broadcast.txHash, quote, quote.toCoin.decimals);
3784
4161
  }
3785
- return { txHash, quote };
4162
+ return { txHash: broadcast.txHash, quote };
3786
4163
  } finally {
3787
4164
  vault.removeAllListeners("signingProgress");
3788
4165
  }
@@ -4067,20 +4444,31 @@ async function executeRujiraSwap(ctx2, options) {
4067
4444
  slippageBps: options.slippageBps
4068
4445
  });
4069
4446
  quoteSpinner.succeed("Quote received");
4070
- if (isJsonOutput()) {
4071
- const result2 = await client.swap.execute(quote, { slippageBps: options.slippageBps });
4072
- outputJson({ quote, result: result2 });
4447
+ if (options.dryRun) {
4448
+ if (isJsonOutput()) {
4449
+ outputJson({ dryRun: true, quote });
4450
+ } else {
4451
+ printResult("FIN Swap Preview (dry-run)");
4452
+ printResult(` From: ${options.fromAsset}`);
4453
+ printResult(` To: ${options.toAsset}`);
4454
+ printResult(` Amount (in): ${options.amount}`);
4455
+ printResult(` Expected out:${quote.expectedOutput}`);
4456
+ printResult(` Min out: ${quote.minimumOutput}`);
4457
+ printResult(` Contract: ${quote.contractAddress}`);
4458
+ }
4073
4459
  return;
4074
4460
  }
4075
- printResult("FIN Swap Preview");
4076
- printResult(` From: ${options.fromAsset}`);
4077
- printResult(` To: ${options.toAsset}`);
4078
- printResult(` Amount (in): ${options.amount}`);
4079
- printResult(` Expected out:${quote.expectedOutput}`);
4080
- printResult(` Min out: ${quote.minimumOutput}`);
4081
- printResult(` Contract: ${quote.contractAddress}`);
4082
- if (quote.warning) {
4083
- warn(quote.warning);
4461
+ if (!isJsonOutput()) {
4462
+ printResult("FIN Swap Preview");
4463
+ printResult(` From: ${options.fromAsset}`);
4464
+ printResult(` To: ${options.toAsset}`);
4465
+ printResult(` Amount (in): ${options.amount}`);
4466
+ printResult(` Expected out:${quote.expectedOutput}`);
4467
+ printResult(` Min out: ${quote.minimumOutput}`);
4468
+ printResult(` Contract: ${quote.contractAddress}`);
4469
+ if (quote.warning) {
4470
+ warn(quote.warning);
4471
+ }
4084
4472
  }
4085
4473
  if (!options.yes) {
4086
4474
  warn("This command will execute a swap. Re-run with -y/--yes to skip this warning.");
@@ -4089,7 +4477,11 @@ async function executeRujiraSwap(ctx2, options) {
4089
4477
  const execSpinner = createSpinner("Executing FIN swap...");
4090
4478
  const result = await client.swap.execute(quote, { slippageBps: options.slippageBps });
4091
4479
  execSpinner.succeed("Swap submitted");
4092
- printResult(`Tx Hash: ${result.txHash}`);
4480
+ if (isJsonOutput()) {
4481
+ outputJson({ quote, result });
4482
+ } else {
4483
+ printResult(`Tx Hash: ${result.txHash}`);
4484
+ }
4093
4485
  }
4094
4486
  async function executeRujiraWithdraw(ctx2, options) {
4095
4487
  const vault = await ctx2.ensureActiveVault();
@@ -4103,17 +4495,27 @@ async function executeRujiraWithdraw(ctx2, options) {
4103
4495
  maxFeeBps: options.maxFeeBps
4104
4496
  });
4105
4497
  prepSpinner.succeed("Withdrawal prepared");
4106
- if (isJsonOutput()) {
4107
- const result2 = await client.withdraw.execute(prepared);
4108
- outputJson({ prepared, result: result2 });
4498
+ if (options.dryRun) {
4499
+ if (isJsonOutput()) {
4500
+ outputJson({ dryRun: true, prepared });
4501
+ } else {
4502
+ printResult("Withdraw Preview (dry-run)");
4503
+ printResult(` Asset: ${prepared.asset}`);
4504
+ printResult(` Amount: ${prepared.amount}`);
4505
+ printResult(` Destination: ${prepared.destination}`);
4506
+ printResult(` Memo: ${prepared.memo}`);
4507
+ printResult(` Est. fee: ${prepared.estimatedFee}`);
4508
+ }
4109
4509
  return;
4110
4510
  }
4111
- printResult("Withdraw Preview");
4112
- printResult(` Asset: ${prepared.asset}`);
4113
- printResult(` Amount: ${prepared.amount}`);
4114
- printResult(` Destination: ${prepared.destination}`);
4115
- printResult(` Memo: ${prepared.memo}`);
4116
- printResult(` Est. fee: ${prepared.estimatedFee}`);
4511
+ if (!isJsonOutput()) {
4512
+ printResult("Withdraw Preview");
4513
+ printResult(` Asset: ${prepared.asset}`);
4514
+ printResult(` Amount: ${prepared.amount}`);
4515
+ printResult(` Destination: ${prepared.destination}`);
4516
+ printResult(` Memo: ${prepared.memo}`);
4517
+ printResult(` Est. fee: ${prepared.estimatedFee}`);
4518
+ }
4117
4519
  if (!options.yes) {
4118
4520
  warn("This command will broadcast a THORChain MsgDeposit withdrawal. Re-run with -y/--yes to proceed.");
4119
4521
  throw new Error("Confirmation required (use --yes)");
@@ -4121,7 +4523,11 @@ async function executeRujiraWithdraw(ctx2, options) {
4121
4523
  const execSpinner = createSpinner("Broadcasting withdrawal...");
4122
4524
  const result = await client.withdraw.execute(prepared);
4123
4525
  execSpinner.succeed("Withdrawal submitted");
4124
- printResult(`Tx Hash: ${result.txHash}`);
4526
+ if (isJsonOutput()) {
4527
+ outputJson({ prepared, result });
4528
+ } else {
4529
+ printResult(`Tx Hash: ${result.txHash}`);
4530
+ }
4125
4531
  }
4126
4532
 
4127
4533
  // src/commands/discount.ts
@@ -4222,10 +4628,129 @@ function displayDiscountTier(tierInfo) {
4222
4628
  printResult("");
4223
4629
  }
4224
4630
 
4631
+ // src/commands/auth.ts
4632
+ import { executeAuthLogout, executeAuthSetup, executeAuthStatus } from "@vultisig/client-shared";
4633
+
4225
4634
  // src/commands/agent.ts
4226
4635
  import chalk9 from "chalk";
4227
4636
  import Table from "cli-table3";
4228
4637
 
4638
+ // src/agent/agentErrors.ts
4639
+ import { VaultError as VaultError2, VaultErrorCode as VaultErrorCode2, VaultImportError as VaultImportError2, VaultImportErrorCode as VaultImportErrorCode2 } from "@vultisig/sdk";
4640
+ var AgentErrorCode = /* @__PURE__ */ ((AgentErrorCode3) => {
4641
+ AgentErrorCode3["BACKEND_UNREACHABLE"] = "BACKEND_UNREACHABLE";
4642
+ AgentErrorCode3["AUTH_FAILED"] = "AUTH_FAILED";
4643
+ AgentErrorCode3["VAULT_LOCKED"] = "VAULT_LOCKED";
4644
+ AgentErrorCode3["PASSWORD_REQUIRED"] = "PASSWORD_REQUIRED";
4645
+ AgentErrorCode3["CONFIRMATION_REQUIRED"] = "CONFIRMATION_REQUIRED";
4646
+ AgentErrorCode3["ACTION_NOT_IMPLEMENTED"] = "ACTION_NOT_IMPLEMENTED";
4647
+ AgentErrorCode3["INVALID_INPUT"] = "INVALID_INPUT";
4648
+ AgentErrorCode3["NETWORK_ERROR"] = "NETWORK_ERROR";
4649
+ AgentErrorCode3["TIMEOUT"] = "TIMEOUT";
4650
+ AgentErrorCode3["TRANSACTION_FAILED"] = "TRANSACTION_FAILED";
4651
+ AgentErrorCode3["SIGNING_FAILED"] = "SIGNING_FAILED";
4652
+ AgentErrorCode3["SESSION_NOT_INITIALIZED"] = "SESSION_NOT_INITIALIZED";
4653
+ AgentErrorCode3["UNKNOWN_ERROR"] = "UNKNOWN_ERROR";
4654
+ return AgentErrorCode3;
4655
+ })(AgentErrorCode || {});
4656
+ var AGENT_ERROR_CODE_VALUES = new Set(Object.values(AgentErrorCode));
4657
+ function isAgentErrorCode(value) {
4658
+ return AGENT_ERROR_CODE_VALUES.has(value);
4659
+ }
4660
+ function mapVaultError(err) {
4661
+ if (err.code === VaultErrorCode2.InvalidConfig && /failed to unlock vault/i.test(err.message)) {
4662
+ return "AUTH_FAILED" /* AUTH_FAILED */;
4663
+ }
4664
+ switch (err.code) {
4665
+ case VaultErrorCode2.Timeout:
4666
+ return "TIMEOUT" /* TIMEOUT */;
4667
+ case VaultErrorCode2.NetworkError:
4668
+ case VaultErrorCode2.BalanceFetchFailed:
4669
+ return "NETWORK_ERROR" /* NETWORK_ERROR */;
4670
+ case VaultErrorCode2.SigningFailed:
4671
+ return "SIGNING_FAILED" /* SIGNING_FAILED */;
4672
+ case VaultErrorCode2.BroadcastFailed:
4673
+ case VaultErrorCode2.GasEstimationFailed:
4674
+ return "TRANSACTION_FAILED" /* TRANSACTION_FAILED */;
4675
+ case VaultErrorCode2.NotImplemented:
4676
+ return "ACTION_NOT_IMPLEMENTED" /* ACTION_NOT_IMPLEMENTED */;
4677
+ case VaultErrorCode2.InvalidAmount:
4678
+ case VaultErrorCode2.UnsupportedChain:
4679
+ case VaultErrorCode2.UnsupportedToken:
4680
+ case VaultErrorCode2.ChainNotSupported:
4681
+ case VaultErrorCode2.InvalidVault:
4682
+ case VaultErrorCode2.InvalidPublicKey:
4683
+ case VaultErrorCode2.InvalidChainCode:
4684
+ case VaultErrorCode2.AddressDerivationFailed:
4685
+ return "INVALID_INPUT" /* INVALID_INPUT */;
4686
+ case VaultErrorCode2.InvalidConfig:
4687
+ return "INVALID_INPUT" /* INVALID_INPUT */;
4688
+ default:
4689
+ return "UNKNOWN_ERROR" /* UNKNOWN_ERROR */;
4690
+ }
4691
+ }
4692
+ function mapVaultImportError(err) {
4693
+ switch (err.code) {
4694
+ case VaultImportErrorCode2.PASSWORD_REQUIRED:
4695
+ return "PASSWORD_REQUIRED" /* PASSWORD_REQUIRED */;
4696
+ case VaultImportErrorCode2.INVALID_PASSWORD:
4697
+ return "AUTH_FAILED" /* AUTH_FAILED */;
4698
+ default:
4699
+ return "INVALID_INPUT" /* INVALID_INPUT */;
4700
+ }
4701
+ }
4702
+ function networkishMessage(msg) {
4703
+ return /ECONNREFUSED|ENOTFOUND|ETIMEDOUT|network|fetch failed|socket/i.test(msg) || /getaddrinfo|certificate|TLS|SSL/i.test(msg);
4704
+ }
4705
+ function inferAgentErrorCodeFromMessage(message) {
4706
+ const m = message.trim();
4707
+ if (!m) return "UNKNOWN_ERROR" /* UNKNOWN_ERROR */;
4708
+ if (/agent backend unreachable/i.test(m)) return "BACKEND_UNREACHABLE" /* BACKEND_UNREACHABLE */;
4709
+ if (/authentication failed|^auth failed/i.test(m)) return "AUTH_FAILED" /* AUTH_FAILED */;
4710
+ if (m === "PASSWORD_REQUIRED") return "PASSWORD_REQUIRED" /* PASSWORD_REQUIRED */;
4711
+ if (/^CONFIRMATION_REQUIRED:/i.test(m)) return "CONFIRMATION_REQUIRED" /* CONFIRMATION_REQUIRED */;
4712
+ if (/password required|password not provided|use --password/i.test(m)) return "PASSWORD_REQUIRED" /* PASSWORD_REQUIRED */;
4713
+ if (/session not initialized/i.test(m)) return "SESSION_NOT_INITIALIZED" /* SESSION_NOT_INITIALIZED */;
4714
+ if (/not implemented locally|is not yet implemented|is not implemented locally|action type .*not implemented/i.test(m)) {
4715
+ return "ACTION_NOT_IMPLEMENTED" /* ACTION_NOT_IMPLEMENTED */;
4716
+ }
4717
+ if (/\(401\)|\(403\)|\b401\b|\b403\b|unauthorized|forbidden/i.test(m)) return "AUTH_FAILED" /* AUTH_FAILED */;
4718
+ if (/failed to unlock vault/i.test(m)) return "AUTH_FAILED" /* AUTH_FAILED */;
4719
+ if (/vault.*locked|must unlock|unlock.*vault/i.test(m)) return "VAULT_LOCKED" /* VAULT_LOCKED */;
4720
+ if (/timed out|timeout/i.test(m)) return "TIMEOUT" /* TIMEOUT */;
4721
+ if (networkishMessage(m)) return "NETWORK_ERROR" /* NETWORK_ERROR */;
4722
+ if (/unknown chain|unknown from_chain|unknown to_chain/i.test(m) || /\bis required\b|\bmissing\b|\brequires\b/i.test(m) || /no pending transaction|invalid or empty tx|could not stage calldata|server transaction missing/i.test(m) || /build_custom_tx requires|incomplete for a contract call|invalid der:/i.test(m)) {
4723
+ return "INVALID_INPUT" /* INVALID_INPUT */;
4724
+ }
4725
+ return "UNKNOWN_ERROR" /* UNKNOWN_ERROR */;
4726
+ }
4727
+ function nodeErrCode(err) {
4728
+ if (err && typeof err === "object" && "code" in err && typeof err.code === "string") {
4729
+ return err.code;
4730
+ }
4731
+ return void 0;
4732
+ }
4733
+ function normalizeAgentError(err) {
4734
+ if (err instanceof VaultError2) {
4735
+ return { code: mapVaultError(err), message: err.message };
4736
+ }
4737
+ if (err instanceof VaultImportError2) {
4738
+ return { code: mapVaultImportError(err), message: err.message };
4739
+ }
4740
+ const name = err instanceof Error ? err.name : "";
4741
+ if (name === "AbortError") {
4742
+ const message2 = err instanceof Error ? err.message : "Aborted";
4743
+ const code = /timed out|timeout/i.test(message2) ? "TIMEOUT" /* TIMEOUT */ : "UNKNOWN_ERROR" /* UNKNOWN_ERROR */;
4744
+ return { code, message: message2 };
4745
+ }
4746
+ const message = err instanceof Error ? err.message : String(err);
4747
+ const nc = nodeErrCode(err);
4748
+ if (nc === "ECONNREFUSED" || nc === "ENOTFOUND" || nc === "ETIMEDOUT") {
4749
+ return { code: "NETWORK_ERROR" /* NETWORK_ERROR */, message };
4750
+ }
4751
+ return { code: inferAgentErrorCodeFromMessage(message), message };
4752
+ }
4753
+
4229
4754
  // src/agent/ask.ts
4230
4755
  var AskInterface = class {
4231
4756
  session;
@@ -4252,10 +4777,10 @@ var AskInterface = class {
4252
4777
  `);
4253
4778
  }
4254
4779
  },
4255
- onToolResult: (_id, action, success2, data, error2) => {
4256
- this.toolCalls.push({ action, success: success2, data, error: error2 });
4780
+ onToolResult: (_id, action, success2, data, error2, code) => {
4781
+ this.toolCalls.push({ action, success: success2, data, error: error2, code });
4257
4782
  if (this.verbose) {
4258
- const status = success2 ? "ok" : `error: ${error2}`;
4783
+ const status = success2 ? "ok" : `error: ${error2}${code ? ` [${code}]` : ""}`;
4259
4784
  process.stderr.write(`[tool] ${action}: ${status}
4260
4785
  `);
4261
4786
  }
@@ -4274,8 +4799,8 @@ var AskInterface = class {
4274
4799
  `);
4275
4800
  }
4276
4801
  },
4277
- onError: (message) => {
4278
- process.stderr.write(`[error] ${message}
4802
+ onError: (message, code) => {
4803
+ process.stderr.write(`[error] ${message} [${code}]
4279
4804
  `);
4280
4805
  },
4281
4806
  onDone: () => {
@@ -4406,6 +4931,17 @@ function padTo32Bytes(buf) {
4406
4931
  }
4407
4932
 
4408
4933
  // src/agent/client.ts
4934
+ function sseErrorToMessage(value) {
4935
+ if (value == null) return "";
4936
+ if (typeof value === "string") return value;
4937
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return String(value);
4938
+ if (value instanceof Error) return value.message;
4939
+ try {
4940
+ return JSON.stringify(value);
4941
+ } catch {
4942
+ return String(value);
4943
+ }
4944
+ }
4409
4945
  var AgentClient = class {
4410
4946
  baseUrl;
4411
4947
  authToken = null;
@@ -4460,7 +4996,9 @@ var AgentClient = class {
4460
4996
  return this.post(`/agent/conversations/${conversationId}`, req);
4461
4997
  }
4462
4998
  async deleteConversation(conversationId, publicKey) {
4463
- await this.delete(`/agent/conversations/${conversationId}`, { public_key: publicKey });
4999
+ await this.delete(`/agent/conversations/${conversationId}`, {
5000
+ public_key: publicKey
5001
+ });
4464
5002
  }
4465
5003
  // ============================================================================
4466
5004
  // Messages - JSON mode
@@ -4542,8 +5080,30 @@ var AgentClient = class {
4542
5080
  }
4543
5081
  handleSSEEvent(event, data, result, callbacks) {
4544
5082
  try {
4545
- const parsed = JSON.parse(data);
4546
- switch (event) {
5083
+ const rawParsed = JSON.parse(data);
5084
+ let resolvedEvent = event;
5085
+ let parsed = rawParsed;
5086
+ if (event === "message" && typeof rawParsed?.type === "string") {
5087
+ const v1Type = rawParsed.type;
5088
+ if (v1Type === "text-delta") {
5089
+ resolvedEvent = "text_delta";
5090
+ } else if (v1Type === "finish") {
5091
+ resolvedEvent = "done";
5092
+ } else if (v1Type === "error") {
5093
+ resolvedEvent = "error";
5094
+ parsed = { error: rawParsed.errorText ?? rawParsed.error };
5095
+ } else if (v1Type.startsWith("data-")) {
5096
+ resolvedEvent = v1Type.slice(5);
5097
+ if (rawParsed.data && typeof rawParsed.data === "object" && !Array.isArray(rawParsed.data)) {
5098
+ parsed = rawParsed.data;
5099
+ }
5100
+ } else {
5101
+ resolvedEvent = v1Type;
5102
+ if (this.verbose) process.stderr.write(`[SSE:unmapped v1 type: ${v1Type}]
5103
+ `);
5104
+ }
5105
+ }
5106
+ switch (resolvedEvent) {
4547
5107
  case "text_delta":
4548
5108
  if (typeof parsed.delta === "string") {
4549
5109
  result.fullText += parsed.delta;
@@ -4581,9 +5141,12 @@ var AgentClient = class {
4581
5141
  result.message = parsed.message || parsed;
4582
5142
  callbacks.onMessage?.(result.message);
4583
5143
  break;
4584
- case "error":
4585
- callbacks.onError?.(parsed.error);
5144
+ case "error": {
5145
+ const msg = sseErrorToMessage(parsed.error);
5146
+ const codeFromBackend = typeof parsed.code === "string" && isAgentErrorCode(parsed.code) ? parsed.code : inferAgentErrorCodeFromMessage(msg);
5147
+ callbacks.onError?.(msg, codeFromBackend);
4586
5148
  break;
5149
+ }
4587
5150
  case "done":
4588
5151
  break;
4589
5152
  }
@@ -5015,6 +5578,7 @@ var AgentExecutor = class {
5015
5578
  stateStore = null;
5016
5579
  /** Held chain lock release functions, keyed by chain name */
5017
5580
  chainLockReleases = /* @__PURE__ */ new Map();
5581
+ evmLastBroadcast = /* @__PURE__ */ new Map();
5018
5582
  /** Backend client for resolving calldata_id references. */
5019
5583
  backendClient = null;
5020
5584
  constructor(vault, verbose = false, vaultId, vultisig) {
@@ -5092,11 +5656,13 @@ var AgentExecutor = class {
5092
5656
  data
5093
5657
  };
5094
5658
  } catch (err) {
5659
+ const { code, message } = normalizeAgentError(err);
5095
5660
  return {
5096
5661
  action: action.type,
5097
5662
  action_id: action.id,
5098
5663
  success: false,
5099
- error: err.message || String(err)
5664
+ error: message,
5665
+ code
5100
5666
  };
5101
5667
  }
5102
5668
  }
@@ -5559,6 +6125,7 @@ var AgentExecutor = class {
5559
6125
  keysignPayload: payload,
5560
6126
  signature
5561
6127
  });
6128
+ this.evmLastBroadcast.set(chain.toString(), Date.now());
5562
6129
  try {
5563
6130
  this.recordEvmNonceFromPayload(chain, payload, messageHashes.length);
5564
6131
  } catch (nonceErr) {
@@ -5656,6 +6223,7 @@ var AgentExecutor = class {
5656
6223
  keysignPayload,
5657
6224
  signature
5658
6225
  });
6226
+ this.evmLastBroadcast.set(chain.toString(), Date.now());
5659
6227
  try {
5660
6228
  this.recordEvmNonceFromPayload(chain, keysignPayload, messageHashes.length);
5661
6229
  } catch (nonceErr) {
@@ -5814,6 +6382,16 @@ var AgentExecutor = class {
5814
6382
  const rpcNonce = bs.value.nonce;
5815
6383
  const nextNonce = this.stateStore.getNextEvmNonce(chain, rpcNonce);
5816
6384
  if (nextNonce !== rpcNonce) {
6385
+ const lastBroadcast = this.evmLastBroadcast.get(chain.toString()) ?? 0;
6386
+ if (Date.now() - lastBroadcast < 15e3) {
6387
+ if (this.verbose)
6388
+ process.stderr.write(
6389
+ `[nonce] Keeping local nonce ${nextNonce} for ${chain} (broadcast ${Date.now() - lastBroadcast}ms ago)
6390
+ `
6391
+ );
6392
+ bs.value.nonce = nextNonce;
6393
+ return;
6394
+ }
5817
6395
  const pendingNonce = await this.fetchEvmPendingNonce(chain);
5818
6396
  if (pendingNonce !== null && pendingNonce === rpcNonce) {
5819
6397
  if (this.verbose)
@@ -6449,7 +7027,12 @@ var PipeInterface = class {
6449
7027
  const cmd = JSON.parse(nextLine);
6450
7028
  await this.handleCommand(cmd);
6451
7029
  } catch (err) {
6452
- this.emit({ type: "error", message: `Invalid input: ${err.message}` });
7030
+ const { message, code } = normalizeAgentError(err);
7031
+ this.emit({
7032
+ type: "error",
7033
+ message: `Invalid input: ${message}`,
7034
+ code: code === "UNKNOWN_ERROR" /* UNKNOWN_ERROR */ ? "INVALID_INPUT" /* INVALID_INPUT */ : code
7035
+ });
6453
7036
  }
6454
7037
  }
6455
7038
  processing = false;
@@ -6490,8 +7073,16 @@ var PipeInterface = class {
6490
7073
  onToolCall: (id, action, params) => {
6491
7074
  this.emit({ type: "tool_call", id, action, params, status: "running" });
6492
7075
  },
6493
- onToolResult: (id, action, success2, data, error2) => {
6494
- this.emit({ type: "tool_result", id, action, success: success2, data, error: error2 });
7076
+ onToolResult: (id, action, success2, data, error2, code) => {
7077
+ this.emit({
7078
+ type: "tool_result",
7079
+ id,
7080
+ action,
7081
+ success: success2,
7082
+ data,
7083
+ error: error2,
7084
+ ...!success2 && code ? { code } : {}
7085
+ });
6495
7086
  },
6496
7087
  onAssistantMessage: (content) => {
6497
7088
  this.emit({ type: "assistant", content });
@@ -6508,8 +7099,8 @@ var PipeInterface = class {
6508
7099
  explorer_url: explorerUrl
6509
7100
  });
6510
7101
  },
6511
- onError: (message) => {
6512
- this.emit({ type: "error", message });
7102
+ onError: (message, code) => {
7103
+ this.emit({ type: "error", message, code });
6513
7104
  },
6514
7105
  onDone: () => {
6515
7106
  this.emit({ type: "done" });
@@ -6517,13 +7108,17 @@ var PipeInterface = class {
6517
7108
  requestPassword: async () => {
6518
7109
  return new Promise((resolve) => {
6519
7110
  this.pendingPasswordResolve = resolve;
6520
- this.emit({ type: "error", message: "PASSWORD_REQUIRED" });
7111
+ this.emit({ type: "error", message: "PASSWORD_REQUIRED", code: "PASSWORD_REQUIRED" /* PASSWORD_REQUIRED */ });
6521
7112
  });
6522
7113
  },
6523
7114
  requestConfirmation: async (message) => {
6524
7115
  return new Promise((resolve) => {
6525
7116
  this.pendingConfirmResolve = resolve;
6526
- this.emit({ type: "error", message: `CONFIRMATION_REQUIRED: ${message}` });
7117
+ this.emit({
7118
+ type: "error",
7119
+ message: `CONFIRMATION_REQUIRED: ${message}`,
7120
+ code: "CONFIRMATION_REQUIRED" /* CONFIRMATION_REQUIRED */
7121
+ });
6527
7122
  });
6528
7123
  }
6529
7124
  };
@@ -6535,7 +7130,8 @@ var PipeInterface = class {
6535
7130
  try {
6536
7131
  await this.session.sendMessage(cmd.content, callbacks);
6537
7132
  } catch (err) {
6538
- this.emit({ type: "error", message: err.message });
7133
+ const { message, code } = normalizeAgentError(err);
7134
+ this.emit({ type: "error", message, code });
6539
7135
  this.emit({ type: "done" });
6540
7136
  }
6541
7137
  break;
@@ -6555,7 +7151,11 @@ var PipeInterface = class {
6555
7151
  break;
6556
7152
  }
6557
7153
  default:
6558
- this.emit({ type: "error", message: `Unknown command type: ${cmd.type}` });
7154
+ this.emit({
7155
+ type: "error",
7156
+ message: `Unknown command type: ${cmd.type}`,
7157
+ code: "INVALID_INPUT" /* INVALID_INPUT */
7158
+ });
6559
7159
  }
6560
7160
  }
6561
7161
  emit(event) {
@@ -6738,7 +7338,8 @@ var AgentSession = class {
6738
7338
  action_id: result.action_id,
6739
7339
  success: result.success,
6740
7340
  data: result.data || {},
6741
- error: result.error || ""
7341
+ error: result.error || "",
7342
+ ...!result.success && result.code ? { code: result.code } : {}
6742
7343
  };
6743
7344
  }
6744
7345
  let serverTxStoredFromStream = 0;
@@ -6771,22 +7372,16 @@ var AgentSession = class {
6771
7372
  },
6772
7373
  onMessage: (_msg) => {
6773
7374
  },
6774
- onError: (error2) => {
6775
- ui.onError(error2);
7375
+ onError: (error2, code) => {
7376
+ ui.onError(error2, code);
6776
7377
  }
6777
7378
  },
6778
7379
  this.abortController?.signal
6779
7380
  );
6780
7381
  const responseText = streamResult.message?.content || streamResult.fullText || "";
6781
- const inlineActions = parseInlineToolCalls(responseText);
6782
- if (inlineActions.length > 0) {
6783
- const cleanText = responseText.replace(/<invoke\s+name="[^"]*">[\s\S]*?<\/invoke>/g, "").replace(/<\/?minimax:tool_call>/g, "").trim();
6784
- if (cleanText) {
6785
- ui.onAssistantMessage(cleanText);
6786
- }
6787
- streamResult.actions.push(...inlineActions);
6788
- } else if (responseText) {
6789
- ui.onAssistantMessage(responseText);
7382
+ const displayText = stripLeakedToolCallTags(responseText);
7383
+ if (displayText) {
7384
+ ui.onAssistantMessage(displayText);
6790
7385
  }
6791
7386
  const actions = streamResult.actions.filter((a) => a.type !== "sign_tx");
6792
7387
  if (actions.length > 0) {
@@ -6860,7 +7455,8 @@ var AgentSession = class {
6860
7455
  action: action.type,
6861
7456
  action_id: action.id,
6862
7457
  success: false,
6863
- error: "Password not provided"
7458
+ error: "Password not provided",
7459
+ code: "PASSWORD_REQUIRED" /* PASSWORD_REQUIRED */
6864
7460
  });
6865
7461
  continue;
6866
7462
  }
@@ -6869,7 +7465,7 @@ var AgentSession = class {
6869
7465
  ui.onToolCall(action.id, action.type, action.params);
6870
7466
  const result = await this.executor.executeAction(action);
6871
7467
  results.push(result);
6872
- ui.onToolResult(action.id, action.type, result.success, result.data, result.error);
7468
+ ui.onToolResult(action.id, action.type, result.success, result.data, result.error, result.code);
6873
7469
  if (action.type === "sign_tx" && result.success && result.data) {
6874
7470
  const txHash = result.data.tx_hash;
6875
7471
  const chain = result.data.chain;
@@ -6899,34 +7495,12 @@ var AgentSession = class {
6899
7495
  this.historyMessages = [];
6900
7496
  }
6901
7497
  };
6902
- function parseInlineToolCalls(text) {
6903
- const actions = [];
6904
- const invokeRegex = /<invoke\s+name="([^"]+)">([\s\S]*?)<\/invoke>/g;
6905
- let match;
6906
- while ((match = invokeRegex.exec(text)) !== null) {
6907
- const actionType = match[1];
6908
- const body = match[2];
6909
- const params = {};
6910
- const paramRegex = /<parameter\s+name="([^"]+)">([\s\S]*?)<\/parameter>/g;
6911
- let paramMatch;
6912
- while ((paramMatch = paramRegex.exec(body)) !== null) {
6913
- const key = paramMatch[1];
6914
- const value = paramMatch[2];
6915
- try {
6916
- params[key] = JSON.parse(value);
6917
- } catch {
6918
- params[key] = value;
6919
- }
6920
- }
6921
- actions.push({
6922
- id: `inline_${actionType}_${Date.now()}`,
6923
- type: actionType,
6924
- title: actionType,
6925
- params,
6926
- auto_execute: true
6927
- });
7498
+ function stripLeakedToolCallTags(text) {
7499
+ if (!text) return "";
7500
+ if (!/<invoke\s+name="[^"]*">/.test(text) && !text.includes("minimax:tool_call")) {
7501
+ return text;
6928
7502
  }
6929
- return actions;
7503
+ return text.replace(/<invoke\s+name="[^"]*">[\s\S]*?<\/invoke>/g, "").replace(/<\/?minimax:tool_call>/g, "").replace(/\n{3,}/g, "\n\n").trim();
6930
7504
  }
6931
7505
  function getTokenCachePath() {
6932
7506
  const dir = process.env.VULTISIG_CONFIG_DIR ?? join2(homedir2(), ".vultisig");
@@ -7102,7 +7676,7 @@ var ChatTUI = class {
7102
7676
  console.log(` ${chalk8.yellow("\u26A1")} ${chalk8.yellow(action)} ${chalk8.gray("...")}`);
7103
7677
  }
7104
7678
  },
7105
- onToolResult: (_id, action, success2, data, error2) => {
7679
+ onToolResult: (_id, action, success2, data, error2, code) => {
7106
7680
  if (success2) {
7107
7681
  if (this.verbose) {
7108
7682
  const summary = data ? summarizeData(data) : "";
@@ -7111,7 +7685,8 @@ var ChatTUI = class {
7111
7685
  console.log(` ${chalk8.green("\u2713")} ${chalk8.green(action)}`);
7112
7686
  }
7113
7687
  } else {
7114
- console.log(` ${chalk8.red("\u2717")} ${chalk8.red(action)}: ${chalk8.red(error2 || "failed")}`);
7688
+ const suffix = code && this.verbose ? chalk8.gray(` (${code})`) : "";
7689
+ console.log(` ${chalk8.red("\u2717")} ${chalk8.red(action)}: ${chalk8.red(error2 || "failed")}${suffix}`);
7115
7690
  }
7116
7691
  },
7117
7692
  onAssistantMessage: (content) => {
@@ -7139,12 +7714,13 @@ var ChatTUI = class {
7139
7714
  console.log(` ${chalk8.blue.underline(explorerUrl)}`);
7140
7715
  }
7141
7716
  },
7142
- onError: (message) => {
7717
+ onError: (message, code) => {
7143
7718
  if (this.isStreaming) {
7144
7719
  process.stdout.write("\n");
7145
7720
  this.isStreaming = false;
7146
7721
  }
7147
- console.log(` ${chalk8.red("Error")}: ${message}`);
7722
+ const suffix = this.verbose ? chalk8.gray(` (${code})`) : "";
7723
+ console.log(` ${chalk8.red("Error")}: ${message}${suffix}`);
7148
7724
  },
7149
7725
  onDone: () => {
7150
7726
  if (this.isStreaming) {
@@ -7353,7 +7929,8 @@ async function executeAgent(ctx2, options) {
7353
7929
  const addresses = session.getVaultAddresses();
7354
7930
  await pipe.start(vault.name, addresses);
7355
7931
  } catch (err) {
7356
- process.stdout.write(JSON.stringify({ type: "error", message: err.message }) + "\n");
7932
+ const { code, message } = normalizeAgentError(err);
7933
+ process.stdout.write(JSON.stringify({ type: "error", message, code }) + "\n");
7357
7934
  process.exit(1);
7358
7935
  }
7359
7936
  } else {
@@ -7363,7 +7940,8 @@ async function executeAgent(ctx2, options) {
7363
7940
  await session.initialize(callbacks);
7364
7941
  await tui.start();
7365
7942
  } catch (err) {
7366
- console.error(`Agent error: ${err.message}`);
7943
+ const { message } = normalizeAgentError(err);
7944
+ console.error(`Agent error: ${message}`);
7367
7945
  process.exit(1);
7368
7946
  }
7369
7947
  }
@@ -7390,15 +7968,13 @@ async function executeAgentAsk(ctx2, message, options) {
7390
7968
  const callbacks = ask.getCallbacks();
7391
7969
  await session.initialize(callbacks);
7392
7970
  const result = await ask.ask(message);
7393
- if (options.json) {
7394
- process.stdout.write(
7395
- JSON.stringify({
7396
- session_id: result.sessionId,
7397
- response: result.response,
7398
- tool_calls: result.toolCalls,
7399
- transactions: result.transactions
7400
- }) + "\n"
7401
- );
7971
+ if (options.json || isJsonOutput()) {
7972
+ outputJson({
7973
+ session_id: result.sessionId,
7974
+ response: result.response,
7975
+ tool_calls: result.toolCalls,
7976
+ transactions: result.transactions
7977
+ });
7402
7978
  } else {
7403
7979
  process.stdout.write(`session:${result.sessionId}
7404
7980
  `);
@@ -7418,10 +7994,11 @@ tx:${tx.chain}:${tx.hash}
7418
7994
  }
7419
7995
  }
7420
7996
  } catch (err) {
7997
+ const { code, message: message2 } = normalizeAgentError(err);
7421
7998
  if (options.json) {
7422
- process.stdout.write(JSON.stringify({ error: err.message }) + "\n");
7999
+ process.stdout.write(JSON.stringify({ error: message2, code }) + "\n");
7423
8000
  } else {
7424
- process.stderr.write(`Error: ${err.message}
8001
+ process.stderr.write(`Error: ${message2} [${code}]
7425
8002
  `);
7426
8003
  }
7427
8004
  process.exit(1);
@@ -7463,46 +8040,294 @@ async function executeAgentSessionsList(ctx2, options) {
7463
8040
  printResult("No sessions found.");
7464
8041
  return;
7465
8042
  }
7466
- const table = new Table({
7467
- head: [chalk9.cyan("ID"), chalk9.cyan("Title"), chalk9.cyan("Created"), chalk9.cyan("Updated")]
7468
- });
7469
- for (const conv of allConversations) {
7470
- table.push([
7471
- conv.id,
7472
- conv.title || chalk9.gray("(untitled)"),
7473
- formatDate(conv.created_at),
7474
- formatDate(conv.updated_at)
7475
- ]);
8043
+ const table = new Table({
8044
+ head: [chalk9.cyan("ID"), chalk9.cyan("Title"), chalk9.cyan("Created"), chalk9.cyan("Updated")]
8045
+ });
8046
+ for (const conv of allConversations) {
8047
+ table.push([
8048
+ conv.id,
8049
+ conv.title || chalk9.gray("(untitled)"),
8050
+ formatDate(conv.created_at),
8051
+ formatDate(conv.updated_at)
8052
+ ]);
8053
+ }
8054
+ printResult(table.toString());
8055
+ printResult(chalk9.gray(`
8056
+ ${totalCount} session(s) total`));
8057
+ }
8058
+ async function executeAgentSessionsDelete(ctx2, sessionId, options) {
8059
+ const vault = await ctx2.ensureActiveVault();
8060
+ const backendUrl = options.backendUrl || process.env.VULTISIG_AGENT_URL || "https://abe.vultisig.com";
8061
+ const client = await createAuthenticatedClient(backendUrl, vault, options.password);
8062
+ const publicKey = vault.publicKeys.ecdsa;
8063
+ await client.deleteConversation(sessionId, publicKey);
8064
+ if (isJsonOutput()) {
8065
+ outputJson({ deleted: sessionId });
8066
+ return;
8067
+ }
8068
+ printResult(chalk9.green(`Session ${sessionId} deleted.`));
8069
+ }
8070
+ async function createAuthenticatedClient(backendUrl, vault, password) {
8071
+ const client = new AgentClient(backendUrl);
8072
+ const auth = await authenticateVault(client, vault, password);
8073
+ client.setAuthToken(auth.token);
8074
+ return client;
8075
+ }
8076
+ function formatDate(iso) {
8077
+ try {
8078
+ const d = new Date(iso);
8079
+ return d.toLocaleDateString() + " " + d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
8080
+ } catch {
8081
+ return iso;
8082
+ }
8083
+ }
8084
+
8085
+ // src/lib/version.ts
8086
+ import chalk10 from "chalk";
8087
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync2 } from "fs";
8088
+ import { homedir as homedir3 } from "os";
8089
+ import { join as join3 } from "path";
8090
+ var cachedVersion = null;
8091
+ function getVersion() {
8092
+ if (cachedVersion) return cachedVersion;
8093
+ if (true) {
8094
+ cachedVersion = "0.16.0";
8095
+ return cachedVersion;
8096
+ }
8097
+ try {
8098
+ const packagePath = new URL("../../package.json", import.meta.url);
8099
+ const pkg = JSON.parse(readFileSync3(packagePath, "utf-8"));
8100
+ cachedVersion = pkg.version;
8101
+ return cachedVersion;
8102
+ } catch {
8103
+ cachedVersion = "0.0.0-unknown";
8104
+ return cachedVersion;
8105
+ }
8106
+ }
8107
+ var CACHE_DIR = join3(homedir3(), ".vultisig", "cache");
8108
+ var VERSION_CACHE_FILE = join3(CACHE_DIR, "version-check.json");
8109
+ var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
8110
+ function readVersionCache() {
8111
+ try {
8112
+ if (!existsSync2(VERSION_CACHE_FILE)) return null;
8113
+ const data = readFileSync3(VERSION_CACHE_FILE, "utf-8");
8114
+ return JSON.parse(data);
8115
+ } catch {
8116
+ return null;
8117
+ }
8118
+ }
8119
+ function writeVersionCache(cache) {
8120
+ try {
8121
+ if (!existsSync2(CACHE_DIR)) {
8122
+ mkdirSync3(CACHE_DIR, { recursive: true });
8123
+ }
8124
+ writeFileSync3(VERSION_CACHE_FILE, JSON.stringify(cache, null, 2));
8125
+ } catch {
8126
+ }
8127
+ }
8128
+ async function fetchLatestVersion() {
8129
+ try {
8130
+ const controller = new AbortController();
8131
+ const timeout = setTimeout(() => controller.abort(), 5e3);
8132
+ const response = await fetch("https://registry.npmjs.org/@vultisig/cli/latest", {
8133
+ signal: controller.signal,
8134
+ headers: {
8135
+ Accept: "application/json"
8136
+ }
8137
+ });
8138
+ clearTimeout(timeout);
8139
+ if (!response.ok) return null;
8140
+ const data = await response.json();
8141
+ return data.version ?? null;
8142
+ } catch {
8143
+ return null;
8144
+ }
8145
+ }
8146
+ function isNewerVersion(v1, v2) {
8147
+ const parse = (v) => {
8148
+ const clean2 = v.replace(/^v/, "").replace(/-.*$/, "");
8149
+ return clean2.split(".").map((n) => parseInt(n, 10) || 0);
8150
+ };
8151
+ const p1 = parse(v1);
8152
+ const p2 = parse(v2);
8153
+ for (let i = 0; i < 3; i++) {
8154
+ const n1 = p1[i] ?? 0;
8155
+ const n2 = p2[i] ?? 0;
8156
+ if (n2 > n1) return true;
8157
+ if (n2 < n1) return false;
8158
+ }
8159
+ if (v1.includes("-") && !v2.includes("-")) return true;
8160
+ return false;
8161
+ }
8162
+ async function checkForUpdates() {
8163
+ if (process.env.VULTISIG_NO_UPDATE_CHECK === "1") {
8164
+ return null;
8165
+ }
8166
+ const currentVersion = getVersion();
8167
+ const cache = readVersionCache();
8168
+ if (cache && Date.now() - cache.lastCheck < CACHE_TTL_MS) {
8169
+ return {
8170
+ currentVersion,
8171
+ latestVersion: cache.latestVersion,
8172
+ updateAvailable: cache.latestVersion ? isNewerVersion(currentVersion, cache.latestVersion) : false
8173
+ };
8174
+ }
8175
+ const latestVersion = await fetchLatestVersion();
8176
+ writeVersionCache({
8177
+ lastCheck: Date.now(),
8178
+ latestVersion
8179
+ });
8180
+ return {
8181
+ currentVersion,
8182
+ latestVersion,
8183
+ updateAvailable: latestVersion ? isNewerVersion(currentVersion, latestVersion) : false
8184
+ };
8185
+ }
8186
+ function formatVersionShort() {
8187
+ return `vultisig/${getVersion()}`;
8188
+ }
8189
+ function formatVersionDetailed() {
8190
+ const lines = [];
8191
+ lines.push(chalk10.bold(`Vultisig CLI v${getVersion()}`));
8192
+ lines.push("");
8193
+ lines.push(` Node.js: ${process.version}`);
8194
+ lines.push(` Platform: ${process.platform}-${process.arch}`);
8195
+ lines.push(` Config: ~/.vultisig/`);
8196
+ return lines.join("\n");
8197
+ }
8198
+ function detectInstallMethod() {
8199
+ const execPath = process.execPath;
8200
+ if (execPath.includes("homebrew") || execPath.includes("Cellar")) {
8201
+ return "homebrew";
8202
+ }
8203
+ if (process.env.npm_execpath?.includes("yarn")) {
8204
+ return "yarn";
8205
+ }
8206
+ if (process.env.npm_config_user_agent?.includes("npm")) {
8207
+ return "npm";
8208
+ }
8209
+ if (execPath.includes("node_modules")) {
8210
+ return "npm";
8211
+ }
8212
+ return "unknown";
8213
+ }
8214
+ function getUpdateCommand() {
8215
+ const method = detectInstallMethod();
8216
+ switch (method) {
8217
+ case "npm":
8218
+ return "npm update -g @vultisig/cli";
8219
+ case "yarn":
8220
+ return "yarn global upgrade @vultisig/cli";
8221
+ case "homebrew":
8222
+ return "brew upgrade vultisig";
8223
+ default:
8224
+ return "npm update -g @vultisig/cli";
7476
8225
  }
7477
- printResult(table.toString());
7478
- printResult(chalk9.gray(`
7479
- ${totalCount} session(s) total`));
7480
8226
  }
7481
- async function executeAgentSessionsDelete(ctx2, sessionId, options) {
7482
- const vault = await ctx2.ensureActiveVault();
7483
- const backendUrl = options.backendUrl || process.env.VULTISIG_AGENT_URL || "https://abe.vultisig.com";
7484
- const client = await createAuthenticatedClient(backendUrl, vault, options.password);
7485
- const publicKey = vault.publicKeys.ecdsa;
7486
- await client.deleteConversation(sessionId, publicKey);
7487
- if (isJsonOutput()) {
7488
- outputJson({ deleted: sessionId });
7489
- return;
8227
+
8228
+ // src/commands/schema.ts
8229
+ var COMMAND_EXAMPLES = {
8230
+ balance: {
8231
+ examples: [
8232
+ "vultisig balance",
8233
+ "vultisig balance Ethereum --tokens",
8234
+ "vultisig balance --output json --fields amount,symbol"
8235
+ ]
8236
+ },
8237
+ addresses: {
8238
+ examples: ["vultisig addresses", "vultisig addresses --output json"]
8239
+ },
8240
+ send: {
8241
+ examples: [
8242
+ "vultisig send Ethereum 0x... 0.1",
8243
+ "vultisig send Bitcoin bc1q... --max --yes",
8244
+ "vultisig send Ethereum 0x... 0.5 --dry-run --output json"
8245
+ ]
8246
+ },
8247
+ execute: {
8248
+ examples: [`vultisig execute THORChain <contract> '{"swap":{}}'`]
8249
+ },
8250
+ "swap-quote": {
8251
+ examples: ["vultisig swap-quote Ethereum Bitcoin 0.1 --output json"]
8252
+ },
8253
+ vaults: {
8254
+ examples: ["vultisig vaults", "vultisig vaults --output json"]
8255
+ },
8256
+ chains: {
8257
+ examples: ["vultisig chains", "vultisig chains --add Solana"]
8258
+ },
8259
+ import: {
8260
+ examples: ["vultisig import ~/vault.vult", "vultisig import ~/vault.vult --password secret"]
8261
+ },
8262
+ export: {
8263
+ examples: ["vultisig export ~/backup.vult"]
8264
+ },
8265
+ "create.fast": {
8266
+ examples: ["vultisig create fast --name mywallet --password secret --email me@example.com"]
8267
+ },
8268
+ "agent.ask": {
8269
+ examples: [
8270
+ 'vultisig agent ask "What is my ETH balance?" --output json',
8271
+ 'vultisig agent ask "Send 0.1 ETH to 0x..." --session abc123'
8272
+ ]
7490
8273
  }
7491
- printResult(chalk9.green(`Session ${sessionId} deleted.`));
8274
+ };
8275
+ var GLOBAL_ENUM_VALUES = {
8276
+ "--output": ["json", "table"]
8277
+ };
8278
+ function mapOption(o, enumValues) {
8279
+ return {
8280
+ flags: o.flags,
8281
+ description: o.description,
8282
+ required: !!o.mandatory,
8283
+ defaultValue: o.defaultValue,
8284
+ ...enumValues?.[o.long] ? { enumValues: enumValues[o.long] } : {}
8285
+ };
7492
8286
  }
7493
- async function createAuthenticatedClient(backendUrl, vault, password) {
7494
- const client = new AgentClient(backendUrl);
7495
- const auth = await authenticateVault(client, vault, password);
7496
- client.setAuthToken(auth.token);
7497
- return client;
8287
+ function mapArguments(cmd) {
8288
+ const args = cmd.registeredArguments;
8289
+ if (!args?.length) return void 0;
8290
+ return args.map((a) => ({
8291
+ name: a._name ?? a.name?.(),
8292
+ required: a.required,
8293
+ description: a.description
8294
+ }));
7498
8295
  }
7499
- function formatDate(iso) {
7500
- try {
7501
- const d = new Date(iso);
7502
- return d.toLocaleDateString() + " " + d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
7503
- } catch {
7504
- return iso;
7505
- }
8296
+ function executeSchema(prog) {
8297
+ const schema = {
8298
+ name: prog.name(),
8299
+ version: getVersion(),
8300
+ exitCodes: Object.fromEntries(Object.entries(EXIT_CODE_DESCRIPTIONS).map(([k, v]) => [String(k), v])),
8301
+ globalOptions: prog.options.filter((o) => !o.hidden).map((o) => mapOption(o, GLOBAL_ENUM_VALUES)),
8302
+ commands: prog.commands.filter((c) => !c._hidden).map((c) => {
8303
+ const meta = COMMAND_EXAMPLES[c.name()];
8304
+ const args = mapArguments(c);
8305
+ return {
8306
+ name: c.name(),
8307
+ description: c.description(),
8308
+ ...args ? { arguments: args } : {},
8309
+ options: c.options.filter((o) => !o.hidden && o.long !== "--help").map((o) => mapOption(o, meta?.enumValues)),
8310
+ ...meta?.examples ? { examples: meta.examples } : {},
8311
+ subcommands: c.commands.length ? c.commands.map((sub) => {
8312
+ const subMeta = COMMAND_EXAMPLES[`${c.name()}.${sub.name()}`];
8313
+ const subArgs = mapArguments(sub);
8314
+ return {
8315
+ name: sub.name(),
8316
+ description: sub.description(),
8317
+ ...subArgs ? { arguments: subArgs } : {},
8318
+ options: sub.options.filter((o) => !o.hidden && o.long !== "--help").map((o) => ({
8319
+ flags: o.flags,
8320
+ description: o.description,
8321
+ required: !!o.mandatory
8322
+ })),
8323
+ ...subMeta?.examples ? { examples: subMeta.examples } : {}
8324
+ };
8325
+ }) : void 0
8326
+ };
8327
+ })
8328
+ };
8329
+ process.stdout.write(`${JSON.stringify(schema, null, 2)}
8330
+ `);
7506
8331
  }
7507
8332
 
7508
8333
  // src/core/server-endpoints.ts
@@ -7708,7 +8533,7 @@ function findChainByName(name) {
7708
8533
  }
7709
8534
 
7710
8535
  // src/interactive/event-buffer.ts
7711
- import chalk10 from "chalk";
8536
+ import chalk11 from "chalk";
7712
8537
  var EventBuffer = class {
7713
8538
  eventBuffer = [];
7714
8539
  isCommandRunning = false;
@@ -7748,17 +8573,17 @@ var EventBuffer = class {
7748
8573
  displayEvent(message, type) {
7749
8574
  switch (type) {
7750
8575
  case "success":
7751
- console.log(chalk10.green(message));
8576
+ console.log(chalk11.green(message));
7752
8577
  break;
7753
8578
  case "warning":
7754
- console.log(chalk10.yellow(message));
8579
+ console.log(chalk11.yellow(message));
7755
8580
  break;
7756
8581
  case "error":
7757
- console.error(chalk10.red(message));
8582
+ console.error(chalk11.red(message));
7758
8583
  break;
7759
8584
  case "info":
7760
8585
  default:
7761
- console.log(chalk10.blue(message));
8586
+ console.log(chalk11.blue(message));
7762
8587
  break;
7763
8588
  }
7764
8589
  }
@@ -7769,13 +8594,13 @@ var EventBuffer = class {
7769
8594
  if (this.eventBuffer.length === 0) {
7770
8595
  return;
7771
8596
  }
7772
- console.log(chalk10.gray("\n--- Background Events ---"));
8597
+ console.log(chalk11.gray("\n--- Background Events ---"));
7773
8598
  this.eventBuffer.forEach((event) => {
7774
8599
  const timeStr = event.timestamp.toLocaleTimeString();
7775
8600
  const message = `[${timeStr}] ${event.message}`;
7776
8601
  this.displayEvent(message, event.type);
7777
8602
  });
7778
- console.log(chalk10.gray("--- End Events ---\n"));
8603
+ console.log(chalk11.gray("--- End Events ---\n"));
7779
8604
  }
7780
8605
  /**
7781
8606
  * Setup all vault event listeners
@@ -7885,12 +8710,12 @@ var EventBuffer = class {
7885
8710
 
7886
8711
  // src/interactive/session.ts
7887
8712
  import { fiatCurrencies as fiatCurrencies4 } from "@vultisig/sdk";
7888
- import chalk12 from "chalk";
8713
+ import chalk13 from "chalk";
7889
8714
  import ora3 from "ora";
7890
8715
  import * as readline3 from "readline";
7891
8716
 
7892
8717
  // src/interactive/shell-commands.ts
7893
- import chalk11 from "chalk";
8718
+ import chalk12 from "chalk";
7894
8719
  import Table2 from "cli-table3";
7895
8720
  import inquirer6 from "inquirer";
7896
8721
  import ora2 from "ora";
@@ -7907,25 +8732,25 @@ function formatTimeRemaining(ms) {
7907
8732
  async function executeLock(ctx2) {
7908
8733
  const vault = ctx2.getActiveVault();
7909
8734
  if (!vault) {
7910
- console.log(chalk11.red("No active vault."));
7911
- console.log(chalk11.yellow('Use "vault <name>" to switch to a vault first.'));
8735
+ console.log(chalk12.red("No active vault."));
8736
+ console.log(chalk12.yellow('Use "vault <name>" to switch to a vault first.'));
7912
8737
  return;
7913
8738
  }
7914
8739
  ctx2.lockVault(vault.id);
7915
- console.log(chalk11.green("\n+ Vault locked"));
7916
- console.log(chalk11.gray("Password cache cleared. You will need to enter the password again."));
8740
+ console.log(chalk12.green("\n+ Vault locked"));
8741
+ console.log(chalk12.gray("Password cache cleared. You will need to enter the password again."));
7917
8742
  }
7918
8743
  async function executeUnlock(ctx2) {
7919
8744
  const vault = ctx2.getActiveVault();
7920
8745
  if (!vault) {
7921
- console.log(chalk11.red("No active vault."));
7922
- console.log(chalk11.yellow('Use "vault <name>" to switch to a vault first.'));
8746
+ console.log(chalk12.red("No active vault."));
8747
+ console.log(chalk12.yellow('Use "vault <name>" to switch to a vault first.'));
7923
8748
  return;
7924
8749
  }
7925
8750
  if (ctx2.isVaultUnlocked(vault.id)) {
7926
8751
  const timeRemaining = ctx2.getUnlockTimeRemaining(vault.id);
7927
- console.log(chalk11.yellow("\nVault is already unlocked."));
7928
- console.log(chalk11.gray(`Time remaining: ${formatTimeRemaining(timeRemaining)}`));
8752
+ console.log(chalk12.yellow("\nVault is already unlocked."));
8753
+ console.log(chalk12.gray(`Time remaining: ${formatTimeRemaining(timeRemaining)}`));
7929
8754
  return;
7930
8755
  }
7931
8756
  const { password } = await inquirer6.prompt([
@@ -7942,19 +8767,19 @@ async function executeUnlock(ctx2) {
7942
8767
  ctx2.cachePassword(vault.id, password);
7943
8768
  const timeRemaining = ctx2.getUnlockTimeRemaining(vault.id);
7944
8769
  spinner.succeed("Vault unlocked");
7945
- console.log(chalk11.green(`
8770
+ console.log(chalk12.green(`
7946
8771
  + Vault unlocked for ${formatTimeRemaining(timeRemaining)}`));
7947
8772
  } catch (err) {
7948
8773
  spinner.fail("Failed to unlock vault");
7949
- console.error(chalk11.red(`
8774
+ console.error(chalk12.red(`
7950
8775
  x ${err.message}`));
7951
8776
  }
7952
8777
  }
7953
8778
  async function executeStatus(ctx2) {
7954
8779
  const vault = ctx2.getActiveVault();
7955
8780
  if (!vault) {
7956
- console.log(chalk11.red("No active vault."));
7957
- console.log(chalk11.yellow('Use "vault <name>" to switch to a vault first.'));
8781
+ console.log(chalk12.red("No active vault."));
8782
+ console.log(chalk12.yellow('Use "vault <name>" to switch to a vault first.'));
7958
8783
  return;
7959
8784
  }
7960
8785
  const isUnlocked = ctx2.isVaultUnlocked(vault.id);
@@ -7985,30 +8810,30 @@ async function executeStatus(ctx2) {
7985
8810
  displayStatus(status);
7986
8811
  }
7987
8812
  function displayStatus(status) {
7988
- console.log(chalk11.cyan("\n+----------------------------------------+"));
7989
- console.log(chalk11.cyan("| Vault Status |"));
7990
- console.log(chalk11.cyan("+----------------------------------------+\n"));
7991
- console.log(chalk11.bold("Vault:"));
7992
- console.log(` Name: ${chalk11.green(status.name)}`);
8813
+ console.log(chalk12.cyan("\n+----------------------------------------+"));
8814
+ console.log(chalk12.cyan("| Vault Status |"));
8815
+ console.log(chalk12.cyan("+----------------------------------------+\n"));
8816
+ console.log(chalk12.bold("Vault:"));
8817
+ console.log(` Name: ${chalk12.green(status.name)}`);
7993
8818
  console.log(` ID: ${status.id}`);
7994
- console.log(` Type: ${chalk11.yellow(status.type)}`);
7995
- console.log(chalk11.bold("\nSecurity:"));
8819
+ console.log(` Type: ${chalk12.yellow(status.type)}`);
8820
+ console.log(chalk12.bold("\nSecurity:"));
7996
8821
  if (status.isUnlocked) {
7997
- console.log(` Status: ${chalk11.green("Unlocked")} ${chalk11.green("\u{1F513}")}`);
8822
+ console.log(` Status: ${chalk12.green("Unlocked")} ${chalk12.green("\u{1F513}")}`);
7998
8823
  console.log(` Expires: ${status.timeRemainingFormatted}`);
7999
8824
  } else {
8000
- console.log(` Status: ${chalk11.yellow("Locked")} ${chalk11.yellow("\u{1F512}")}`);
8825
+ console.log(` Status: ${chalk12.yellow("Locked")} ${chalk12.yellow("\u{1F512}")}`);
8001
8826
  }
8002
- console.log(` Encrypted: ${status.isEncrypted ? chalk11.green("Yes") : chalk11.gray("No")}`);
8003
- console.log(` Backed Up: ${status.isBackedUp ? chalk11.green("Yes") : chalk11.yellow("No")}`);
8004
- console.log(chalk11.bold("\nMPC Configuration:"));
8827
+ console.log(` Encrypted: ${status.isEncrypted ? chalk12.green("Yes") : chalk12.gray("No")}`);
8828
+ console.log(` Backed Up: ${status.isBackedUp ? chalk12.green("Yes") : chalk12.yellow("No")}`);
8829
+ console.log(chalk12.bold("\nMPC Configuration:"));
8005
8830
  console.log(` Library: ${status.libType}`);
8006
- console.log(` Threshold: ${chalk11.cyan(status.threshold)} of ${chalk11.cyan(status.totalSigners)}`);
8007
- console.log(chalk11.bold("\nSigning Modes:"));
8831
+ console.log(` Threshold: ${chalk12.cyan(status.threshold)} of ${chalk12.cyan(status.totalSigners)}`);
8832
+ console.log(chalk12.bold("\nSigning Modes:"));
8008
8833
  status.availableSigningModes.forEach((mode) => {
8009
8834
  console.log(` - ${mode}`);
8010
8835
  });
8011
- console.log(chalk11.bold("\nDetails:"));
8836
+ console.log(chalk12.bold("\nDetails:"));
8012
8837
  console.log(` Chains: ${status.chains}`);
8013
8838
  console.log(` Currency: ${status.currency.toUpperCase()}`);
8014
8839
  console.log(` Created: ${new Date(status.createdAt).toLocaleString()}`);
@@ -8017,7 +8842,7 @@ function displayStatus(status) {
8017
8842
  }
8018
8843
  function showHelp() {
8019
8844
  const table = new Table2({
8020
- head: [chalk11.bold("Available Commands")],
8845
+ head: [chalk12.bold("Available Commands")],
8021
8846
  colWidths: [50],
8022
8847
  chars: {
8023
8848
  mid: "",
@@ -8031,7 +8856,7 @@ function showHelp() {
8031
8856
  }
8032
8857
  });
8033
8858
  table.push(
8034
- [chalk11.bold("Vault Management:")],
8859
+ [chalk12.bold("Vault Management:")],
8035
8860
  [" vaults - List all vaults"],
8036
8861
  [" vault <name> - Switch to vault"],
8037
8862
  [" import <file> - Import vault from file"],
@@ -8040,7 +8865,7 @@ function showHelp() {
8040
8865
  [" info - Show vault details"],
8041
8866
  [" export [path] - Export vault to file"],
8042
8867
  [""],
8043
- [chalk11.bold("Wallet Operations:")],
8868
+ [chalk12.bold("Wallet Operations:")],
8044
8869
  [" balance [chain] - Show balances"],
8045
8870
  [" send <chain> <to> <amount> - Send transaction"],
8046
8871
  [" tx-status <chain> <txHash> - Check transaction status"],
@@ -8049,22 +8874,22 @@ function showHelp() {
8049
8874
  [" chains [--add/--remove/--add-all] - Manage chains"],
8050
8875
  [" tokens <chain> - Manage tokens"],
8051
8876
  [""],
8052
- [chalk11.bold("Swap Operations:")],
8877
+ [chalk12.bold("Swap Operations:")],
8053
8878
  [" swap-chains - List swap-enabled chains"],
8054
8879
  [" swap-quote <from> <to> <amount> - Get quote"],
8055
8880
  [" swap <from> <to> <amount> - Execute swap"],
8056
8881
  [""],
8057
- [chalk11.bold("Session Commands (shell only):")],
8882
+ [chalk12.bold("Session Commands (shell only):")],
8058
8883
  [" lock - Lock vault"],
8059
8884
  [" unlock - Unlock vault"],
8060
8885
  [" status - Show vault status"],
8061
8886
  [""],
8062
- [chalk11.bold("Settings:")],
8887
+ [chalk12.bold("Settings:")],
8063
8888
  [" currency [code] - View/set currency"],
8064
8889
  [" server - Check server status"],
8065
8890
  [" address-book - Manage saved addresses"],
8066
8891
  [""],
8067
- [chalk11.bold("Help & Navigation:")],
8892
+ [chalk12.bold("Help & Navigation:")],
8068
8893
  [" help, ? - Show this help"],
8069
8894
  [" .clear - Clear screen"],
8070
8895
  [" .exit - Exit shell"]
@@ -8202,12 +9027,12 @@ var ShellSession = class {
8202
9027
  */
8203
9028
  async start() {
8204
9029
  console.clear();
8205
- console.log(chalk12.cyan.bold("\n=============================================="));
8206
- console.log(chalk12.cyan.bold(" Vultisig Interactive Shell"));
8207
- console.log(chalk12.cyan.bold("==============================================\n"));
9030
+ console.log(chalk13.cyan.bold("\n=============================================="));
9031
+ console.log(chalk13.cyan.bold(" Vultisig Interactive Shell"));
9032
+ console.log(chalk13.cyan.bold("==============================================\n"));
8208
9033
  await this.loadAllVaults();
8209
9034
  this.displayVaultList();
8210
- console.log(chalk12.gray('Type "help" for available commands, "exit" to quit\n'));
9035
+ console.log(chalk13.gray('Type "help" for available commands, "exit" to quit\n'));
8211
9036
  this.promptLoop().catch(() => {
8212
9037
  });
8213
9038
  }
@@ -8241,12 +9066,12 @@ var ShellSession = class {
8241
9066
  const now = Date.now();
8242
9067
  if (now - this.lastSigintTime < this.DOUBLE_CTRL_C_TIMEOUT) {
8243
9068
  rl.close();
8244
- console.log(chalk12.yellow("\nGoodbye!"));
9069
+ console.log(chalk13.yellow("\nGoodbye!"));
8245
9070
  this.ctx.dispose();
8246
9071
  process.exit(0);
8247
9072
  }
8248
9073
  this.lastSigintTime = now;
8249
- console.log(chalk12.yellow("\n(Press Ctrl+C again to exit)"));
9074
+ console.log(chalk13.yellow("\n(Press Ctrl+C again to exit)"));
8250
9075
  rl.close();
8251
9076
  resolve("");
8252
9077
  });
@@ -8341,7 +9166,7 @@ var ShellSession = class {
8341
9166
  stopAllSpinners();
8342
9167
  process.stdout.write("\x1B[?25h");
8343
9168
  process.stdout.write("\r\x1B[K");
8344
- console.log(chalk12.yellow("\nCancelling operation..."));
9169
+ console.log(chalk13.yellow("\nCancelling operation..."));
8345
9170
  };
8346
9171
  const cleanup = () => {
8347
9172
  process.removeListener("SIGINT", onSigint);
@@ -8378,10 +9203,10 @@ var ShellSession = class {
8378
9203
  stopAllSpinners();
8379
9204
  process.stdout.write("\x1B[?25h");
8380
9205
  process.stdout.write("\r\x1B[K");
8381
- console.log(chalk12.yellow("Operation cancelled"));
9206
+ console.log(chalk13.yellow("Operation cancelled"));
8382
9207
  return;
8383
9208
  }
8384
- console.error(chalk12.red(`
9209
+ console.error(chalk13.red(`
8385
9210
  Error: ${error2.message}`));
8386
9211
  }
8387
9212
  }
@@ -8414,7 +9239,7 @@ Error: ${error2.message}`));
8414
9239
  break;
8415
9240
  case "rename":
8416
9241
  if (args.length === 0) {
8417
- console.log(chalk12.yellow("Usage: rename <newName>"));
9242
+ console.log(chalk13.yellow("Usage: rename <newName>"));
8418
9243
  return;
8419
9244
  }
8420
9245
  await executeRename(this.ctx, args.join(" "));
@@ -8490,41 +9315,41 @@ Error: ${error2.message}`));
8490
9315
  // Exit
8491
9316
  case "exit":
8492
9317
  case "quit":
8493
- console.log(chalk12.yellow("\nGoodbye!"));
9318
+ console.log(chalk13.yellow("\nGoodbye!"));
8494
9319
  this.ctx.dispose();
8495
9320
  process.exit(0);
8496
9321
  break;
8497
9322
  // eslint requires break even after process.exit
8498
9323
  default:
8499
- console.log(chalk12.yellow(`Unknown command: ${command}`));
8500
- console.log(chalk12.gray('Type "help" for available commands'));
9324
+ console.log(chalk13.yellow(`Unknown command: ${command}`));
9325
+ console.log(chalk13.gray('Type "help" for available commands'));
8501
9326
  break;
8502
9327
  }
8503
9328
  }
8504
9329
  // ===== Command Helpers =====
8505
9330
  async switchVault(args) {
8506
9331
  if (args.length === 0) {
8507
- console.log(chalk12.yellow("Usage: vault <name>"));
8508
- console.log(chalk12.gray('Run "vaults" to see available vaults'));
9332
+ console.log(chalk13.yellow("Usage: vault <name>"));
9333
+ console.log(chalk13.gray('Run "vaults" to see available vaults'));
8509
9334
  return;
8510
9335
  }
8511
9336
  const vaultName = args.join(" ");
8512
9337
  const vault = this.ctx.findVaultByName(vaultName);
8513
9338
  if (!vault) {
8514
- console.log(chalk12.red(`Vault not found: ${vaultName}`));
8515
- console.log(chalk12.gray('Run "vaults" to see available vaults'));
9339
+ console.log(chalk13.red(`Vault not found: ${vaultName}`));
9340
+ console.log(chalk13.gray('Run "vaults" to see available vaults'));
8516
9341
  return;
8517
9342
  }
8518
9343
  await this.ctx.setActiveVault(vault);
8519
- console.log(chalk12.green(`
9344
+ console.log(chalk13.green(`
8520
9345
  + Switched to: ${vault.name}`));
8521
9346
  const isUnlocked = this.ctx.isVaultUnlocked(vault.id);
8522
- const status = isUnlocked ? chalk12.green("Unlocked") : chalk12.yellow("Locked");
9347
+ const status = isUnlocked ? chalk13.green("Unlocked") : chalk13.yellow("Locked");
8523
9348
  console.log(`Status: ${status}`);
8524
9349
  }
8525
9350
  async importVault(args) {
8526
9351
  if (args.length === 0) {
8527
- console.log(chalk12.yellow("Usage: import <file>"));
9352
+ console.log(chalk13.yellow("Usage: import <file>"));
8528
9353
  return;
8529
9354
  }
8530
9355
  const filePath = args.join(" ");
@@ -8539,45 +9364,45 @@ Error: ${error2.message}`));
8539
9364
  async createVault(args) {
8540
9365
  const type = args[0]?.toLowerCase();
8541
9366
  if (!type || type !== "fast" && type !== "secure") {
8542
- console.log(chalk12.yellow("Usage: create <fast|secure>"));
8543
- console.log(chalk12.gray(" create fast - Create a fast vault (server-assisted 2-of-2)"));
8544
- console.log(chalk12.gray(" create secure - Create a secure vault (multi-device MPC)"));
9367
+ console.log(chalk13.yellow("Usage: create <fast|secure>"));
9368
+ console.log(chalk13.gray(" create fast - Create a fast vault (server-assisted 2-of-2)"));
9369
+ console.log(chalk13.gray(" create secure - Create a secure vault (multi-device MPC)"));
8545
9370
  return;
8546
9371
  }
8547
9372
  let vault;
8548
9373
  if (type === "fast") {
8549
9374
  const name = await this.prompt("Vault name");
8550
9375
  if (!name) {
8551
- console.log(chalk12.red("Name is required"));
9376
+ console.log(chalk13.red("Name is required"));
8552
9377
  return;
8553
9378
  }
8554
9379
  const password = await this.promptPassword("Vault password");
8555
9380
  if (!password) {
8556
- console.log(chalk12.red("Password is required"));
9381
+ console.log(chalk13.red("Password is required"));
8557
9382
  return;
8558
9383
  }
8559
9384
  const email = await this.prompt("Email for verification");
8560
9385
  if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
8561
- console.log(chalk12.red("Valid email is required"));
9386
+ console.log(chalk13.red("Valid email is required"));
8562
9387
  return;
8563
9388
  }
8564
9389
  vault = await this.withCancellation((signal) => executeCreateFast(this.ctx, { name, password, email, signal }));
8565
9390
  } else {
8566
9391
  const name = await this.prompt("Vault name");
8567
9392
  if (!name) {
8568
- console.log(chalk12.red("Name is required"));
9393
+ console.log(chalk13.red("Name is required"));
8569
9394
  return;
8570
9395
  }
8571
9396
  const sharesStr = await this.prompt("Total shares (devices)", "3");
8572
9397
  const shares = parseInt(sharesStr, 10);
8573
9398
  if (isNaN(shares) || shares < 2) {
8574
- console.log(chalk12.red("Must have at least 2 shares"));
9399
+ console.log(chalk13.red("Must have at least 2 shares"));
8575
9400
  return;
8576
9401
  }
8577
9402
  const thresholdStr = await this.prompt("Signing threshold", "2");
8578
9403
  const threshold = parseInt(thresholdStr, 10);
8579
9404
  if (isNaN(threshold) || threshold < 1 || threshold > shares) {
8580
- console.log(chalk12.red(`Threshold must be between 1 and ${shares}`));
9405
+ console.log(chalk13.red(`Threshold must be between 1 and ${shares}`));
8581
9406
  return;
8582
9407
  }
8583
9408
  const password = await this.promptPassword("Vault password (optional, press Enter to skip)");
@@ -8599,37 +9424,37 @@ Error: ${error2.message}`));
8599
9424
  async importSeedphrase(args) {
8600
9425
  const type = args[0]?.toLowerCase();
8601
9426
  if (!type || type !== "fast" && type !== "secure") {
8602
- console.log(chalk12.cyan("Usage: create-from-seedphrase <fast|secure>"));
8603
- console.log(chalk12.gray(" fast - Import with VultiServer (2-of-2)"));
8604
- console.log(chalk12.gray(" secure - Import with device coordination (N-of-M)"));
9427
+ console.log(chalk13.cyan("Usage: create-from-seedphrase <fast|secure>"));
9428
+ console.log(chalk13.gray(" fast - Import with VultiServer (2-of-2)"));
9429
+ console.log(chalk13.gray(" secure - Import with device coordination (N-of-M)"));
8605
9430
  return;
8606
9431
  }
8607
- console.log(chalk12.cyan("\nEnter your recovery phrase (words separated by spaces):"));
9432
+ console.log(chalk13.cyan("\nEnter your recovery phrase (words separated by spaces):"));
8608
9433
  const mnemonic = await this.promptPassword("Seedphrase");
8609
9434
  const validation = await this.ctx.sdk.validateSeedphrase(mnemonic);
8610
9435
  if (!validation.valid) {
8611
- console.log(chalk12.red(`Invalid seedphrase: ${validation.error}`));
9436
+ console.log(chalk13.red(`Invalid seedphrase: ${validation.error}`));
8612
9437
  if (validation.invalidWords?.length) {
8613
- console.log(chalk12.yellow(`Invalid words: ${validation.invalidWords.join(", ")}`));
9438
+ console.log(chalk13.yellow(`Invalid words: ${validation.invalidWords.join(", ")}`));
8614
9439
  }
8615
9440
  return;
8616
9441
  }
8617
- console.log(chalk12.green(`\u2713 Valid ${validation.wordCount}-word seedphrase`));
9442
+ console.log(chalk13.green(`\u2713 Valid ${validation.wordCount}-word seedphrase`));
8618
9443
  let vault;
8619
9444
  if (type === "fast") {
8620
9445
  const name = await this.prompt("Vault name");
8621
9446
  if (!name) {
8622
- console.log(chalk12.red("Name is required"));
9447
+ console.log(chalk13.red("Name is required"));
8623
9448
  return;
8624
9449
  }
8625
9450
  const password = await this.promptPassword("Vault password");
8626
9451
  if (!password) {
8627
- console.log(chalk12.red("Password is required"));
9452
+ console.log(chalk13.red("Password is required"));
8628
9453
  return;
8629
9454
  }
8630
9455
  const email = await this.prompt("Email for verification");
8631
9456
  if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
8632
- console.log(chalk12.red("Valid email is required"));
9457
+ console.log(chalk13.red("Valid email is required"));
8633
9458
  return;
8634
9459
  }
8635
9460
  const discoverStr = await this.prompt("Discover chains with balances? (y/n)", "y");
@@ -8647,19 +9472,19 @@ Error: ${error2.message}`));
8647
9472
  } else {
8648
9473
  const name = await this.prompt("Vault name");
8649
9474
  if (!name) {
8650
- console.log(chalk12.red("Name is required"));
9475
+ console.log(chalk13.red("Name is required"));
8651
9476
  return;
8652
9477
  }
8653
9478
  const sharesStr = await this.prompt("Total shares (devices)", "3");
8654
9479
  const shares = parseInt(sharesStr, 10);
8655
9480
  if (isNaN(shares) || shares < 2) {
8656
- console.log(chalk12.red("Must have at least 2 shares"));
9481
+ console.log(chalk13.red("Must have at least 2 shares"));
8657
9482
  return;
8658
9483
  }
8659
9484
  const thresholdStr = await this.prompt("Signing threshold", "2");
8660
9485
  const threshold = parseInt(thresholdStr, 10);
8661
9486
  if (isNaN(threshold) || threshold < 1 || threshold > shares) {
8662
- console.log(chalk12.red(`Threshold must be between 1 and ${shares}`));
9487
+ console.log(chalk13.red(`Threshold must be between 1 and ${shares}`));
8663
9488
  return;
8664
9489
  }
8665
9490
  const password = await this.promptPassword("Vault password (optional, Enter to skip)");
@@ -8703,8 +9528,8 @@ Error: ${error2.message}`));
8703
9528
  }
8704
9529
  }
8705
9530
  if (!fiatCurrencies4.includes(currency)) {
8706
- console.log(chalk12.red(`Invalid currency: ${currency}`));
8707
- console.log(chalk12.yellow(`Supported currencies: ${fiatCurrencies4.join(", ")}`));
9531
+ console.log(chalk13.red(`Invalid currency: ${currency}`));
9532
+ console.log(chalk13.yellow(`Supported currencies: ${fiatCurrencies4.join(", ")}`));
8708
9533
  return;
8709
9534
  }
8710
9535
  const raw = args.includes("--raw");
@@ -8712,7 +9537,7 @@ Error: ${error2.message}`));
8712
9537
  }
8713
9538
  async runSend(args) {
8714
9539
  if (args.length < 3) {
8715
- console.log(chalk12.yellow("Usage: send <chain> <to> <amount> [--token <tokenId>] [--memo <memo>]"));
9540
+ console.log(chalk13.yellow("Usage: send <chain> <to> <amount> [--token <tokenId>] [--memo <memo>]"));
8716
9541
  return;
8717
9542
  }
8718
9543
  const [chainStr, to, amount, ...rest] = args;
@@ -8732,7 +9557,7 @@ Error: ${error2.message}`));
8732
9557
  await this.withAbortHandler((signal) => executeSend(this.ctx, { chain, to, amount, tokenId, memo, signal }));
8733
9558
  } catch (err) {
8734
9559
  if (err.message === "Transaction cancelled by user" || err.message === "Operation cancelled" || err.message === "Operation aborted") {
8735
- console.log(chalk12.yellow("\nTransaction cancelled"));
9560
+ console.log(chalk13.yellow("\nTransaction cancelled"));
8736
9561
  return;
8737
9562
  }
8738
9563
  throw err;
@@ -8740,7 +9565,7 @@ Error: ${error2.message}`));
8740
9565
  }
8741
9566
  async runTxStatus(args) {
8742
9567
  if (args.length < 2) {
8743
- console.log(chalk12.yellow("Usage: tx-status <chain> <txHash> [--no-wait]"));
9568
+ console.log(chalk13.yellow("Usage: tx-status <chain> <txHash> [--no-wait]"));
8744
9569
  return;
8745
9570
  }
8746
9571
  const [chainStr, txHash, ...rest] = args;
@@ -8758,8 +9583,8 @@ Error: ${error2.message}`));
8758
9583
  } else if (args[i] === "--add" && i + 1 < args.length) {
8759
9584
  const chain = findChainByName(args[i + 1]);
8760
9585
  if (!chain) {
8761
- console.log(chalk12.red(`Unknown chain: ${args[i + 1]}`));
8762
- console.log(chalk12.gray("Use tab completion to see available chains"));
9586
+ console.log(chalk13.red(`Unknown chain: ${args[i + 1]}`));
9587
+ console.log(chalk13.gray("Use tab completion to see available chains"));
8763
9588
  return;
8764
9589
  }
8765
9590
  addChain = chain;
@@ -8767,8 +9592,8 @@ Error: ${error2.message}`));
8767
9592
  } else if (args[i] === "--remove" && i + 1 < args.length) {
8768
9593
  const chain = findChainByName(args[i + 1]);
8769
9594
  if (!chain) {
8770
- console.log(chalk12.red(`Unknown chain: ${args[i + 1]}`));
8771
- console.log(chalk12.gray("Use tab completion to see available chains"));
9595
+ console.log(chalk13.red(`Unknown chain: ${args[i + 1]}`));
9596
+ console.log(chalk13.gray("Use tab completion to see available chains"));
8772
9597
  return;
8773
9598
  }
8774
9599
  removeChain = chain;
@@ -8779,7 +9604,7 @@ Error: ${error2.message}`));
8779
9604
  }
8780
9605
  async runTokens(args) {
8781
9606
  if (args.length === 0) {
8782
- console.log(chalk12.yellow("Usage: tokens <chain> [--add <address>] [--remove <tokenId>]"));
9607
+ console.log(chalk13.yellow("Usage: tokens <chain> [--add <address>] [--remove <tokenId>]"));
8783
9608
  return;
8784
9609
  }
8785
9610
  const chainStr = args[0];
@@ -8800,7 +9625,7 @@ Error: ${error2.message}`));
8800
9625
  async runSwapQuote(args) {
8801
9626
  if (args.length < 3) {
8802
9627
  console.log(
8803
- chalk12.yellow("Usage: swap-quote <fromChain> <toChain> <amount> [--from-token <addr>] [--to-token <addr>]")
9628
+ chalk13.yellow("Usage: swap-quote <fromChain> <toChain> <amount> [--from-token <addr>] [--to-token <addr>]")
8804
9629
  );
8805
9630
  return;
8806
9631
  }
@@ -8824,7 +9649,7 @@ Error: ${error2.message}`));
8824
9649
  async runSwap(args) {
8825
9650
  if (args.length < 3) {
8826
9651
  console.log(
8827
- chalk12.yellow(
9652
+ chalk13.yellow(
8828
9653
  "Usage: swap <fromChain> <toChain> <amount> [--from-token <addr>] [--to-token <addr>] [--slippage <pct>]"
8829
9654
  )
8830
9655
  );
@@ -8855,7 +9680,7 @@ Error: ${error2.message}`));
8855
9680
  );
8856
9681
  } catch (err) {
8857
9682
  if (err.message === "Swap cancelled by user" || err.message === "Operation cancelled" || err.message === "Operation aborted") {
8858
- console.log(chalk12.yellow("\nSwap cancelled"));
9683
+ console.log(chalk13.yellow("\nSwap cancelled"));
8859
9684
  return;
8860
9685
  }
8861
9686
  throw err;
@@ -8917,24 +9742,24 @@ Error: ${error2.message}`));
8917
9742
  }
8918
9743
  getPrompt() {
8919
9744
  const vault = this.ctx.getActiveVault();
8920
- if (!vault) return chalk12.cyan("wallet> ");
9745
+ if (!vault) return chalk13.cyan("wallet> ");
8921
9746
  const isUnlocked = this.ctx.isVaultUnlocked(vault.id);
8922
- const status = isUnlocked ? chalk12.green("\u{1F513}") : chalk12.yellow("\u{1F512}");
8923
- return chalk12.cyan(`wallet[${vault.name}]${status}> `);
9747
+ const status = isUnlocked ? chalk13.green("\u{1F513}") : chalk13.yellow("\u{1F512}");
9748
+ return chalk13.cyan(`wallet[${vault.name}]${status}> `);
8924
9749
  }
8925
9750
  displayVaultList() {
8926
9751
  const vaults = Array.from(this.ctx.getVaults().values());
8927
9752
  const activeVault = this.ctx.getActiveVault();
8928
9753
  if (vaults.length === 0) {
8929
- console.log(chalk12.yellow('No vaults found. Use "create" or "import <file>" to add a vault.\n'));
9754
+ console.log(chalk13.yellow('No vaults found. Use "create" or "import <file>" to add a vault.\n'));
8930
9755
  return;
8931
9756
  }
8932
- console.log(chalk12.cyan("Loaded Vaults:\n"));
9757
+ console.log(chalk13.cyan("Loaded Vaults:\n"));
8933
9758
  vaults.forEach((vault) => {
8934
9759
  const isActive = vault.id === activeVault?.id;
8935
9760
  const isUnlocked = this.ctx.isVaultUnlocked(vault.id);
8936
- const activeMarker = isActive ? chalk12.green(" (active)") : "";
8937
- const lockIcon = isUnlocked ? chalk12.green("\u{1F513}") : chalk12.yellow("\u{1F512}");
9761
+ const activeMarker = isActive ? chalk13.green(" (active)") : "";
9762
+ const lockIcon = isUnlocked ? chalk13.green("\u{1F513}") : chalk13.yellow("\u{1F512}");
8938
9763
  console.log(` ${lockIcon} ${vault.name}${activeMarker} - ${vault.type}`);
8939
9764
  });
8940
9765
  console.log();
@@ -8942,150 +9767,7 @@ Error: ${error2.message}`));
8942
9767
  };
8943
9768
 
8944
9769
  // src/lib/errors.ts
8945
- import chalk13 from "chalk";
8946
-
8947
- // src/lib/version.ts
8948
9770
  import chalk14 from "chalk";
8949
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync2 } from "fs";
8950
- import { homedir as homedir3 } from "os";
8951
- import { join as join3 } from "path";
8952
- var cachedVersion = null;
8953
- function getVersion() {
8954
- if (cachedVersion) return cachedVersion;
8955
- if (true) {
8956
- cachedVersion = "0.15.2";
8957
- return cachedVersion;
8958
- }
8959
- try {
8960
- const packagePath = new URL("../../package.json", import.meta.url);
8961
- const pkg = JSON.parse(readFileSync3(packagePath, "utf-8"));
8962
- cachedVersion = pkg.version;
8963
- return cachedVersion;
8964
- } catch {
8965
- cachedVersion = "0.0.0-unknown";
8966
- return cachedVersion;
8967
- }
8968
- }
8969
- var CACHE_DIR = join3(homedir3(), ".vultisig", "cache");
8970
- var VERSION_CACHE_FILE = join3(CACHE_DIR, "version-check.json");
8971
- var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
8972
- function readVersionCache() {
8973
- try {
8974
- if (!existsSync2(VERSION_CACHE_FILE)) return null;
8975
- const data = readFileSync3(VERSION_CACHE_FILE, "utf-8");
8976
- return JSON.parse(data);
8977
- } catch {
8978
- return null;
8979
- }
8980
- }
8981
- function writeVersionCache(cache) {
8982
- try {
8983
- if (!existsSync2(CACHE_DIR)) {
8984
- mkdirSync3(CACHE_DIR, { recursive: true });
8985
- }
8986
- writeFileSync3(VERSION_CACHE_FILE, JSON.stringify(cache, null, 2));
8987
- } catch {
8988
- }
8989
- }
8990
- async function fetchLatestVersion() {
8991
- try {
8992
- const controller = new AbortController();
8993
- const timeout = setTimeout(() => controller.abort(), 5e3);
8994
- const response = await fetch("https://registry.npmjs.org/@vultisig/cli/latest", {
8995
- signal: controller.signal,
8996
- headers: {
8997
- Accept: "application/json"
8998
- }
8999
- });
9000
- clearTimeout(timeout);
9001
- if (!response.ok) return null;
9002
- const data = await response.json();
9003
- return data.version ?? null;
9004
- } catch {
9005
- return null;
9006
- }
9007
- }
9008
- function isNewerVersion(v1, v2) {
9009
- const parse = (v) => {
9010
- const clean2 = v.replace(/^v/, "").replace(/-.*$/, "");
9011
- return clean2.split(".").map((n) => parseInt(n, 10) || 0);
9012
- };
9013
- const p1 = parse(v1);
9014
- const p2 = parse(v2);
9015
- for (let i = 0; i < 3; i++) {
9016
- const n1 = p1[i] ?? 0;
9017
- const n2 = p2[i] ?? 0;
9018
- if (n2 > n1) return true;
9019
- if (n2 < n1) return false;
9020
- }
9021
- if (v1.includes("-") && !v2.includes("-")) return true;
9022
- return false;
9023
- }
9024
- async function checkForUpdates() {
9025
- if (process.env.VULTISIG_NO_UPDATE_CHECK === "1") {
9026
- return null;
9027
- }
9028
- const currentVersion = getVersion();
9029
- const cache = readVersionCache();
9030
- if (cache && Date.now() - cache.lastCheck < CACHE_TTL_MS) {
9031
- return {
9032
- currentVersion,
9033
- latestVersion: cache.latestVersion,
9034
- updateAvailable: cache.latestVersion ? isNewerVersion(currentVersion, cache.latestVersion) : false
9035
- };
9036
- }
9037
- const latestVersion = await fetchLatestVersion();
9038
- writeVersionCache({
9039
- lastCheck: Date.now(),
9040
- latestVersion
9041
- });
9042
- return {
9043
- currentVersion,
9044
- latestVersion,
9045
- updateAvailable: latestVersion ? isNewerVersion(currentVersion, latestVersion) : false
9046
- };
9047
- }
9048
- function formatVersionShort() {
9049
- return `vultisig/${getVersion()}`;
9050
- }
9051
- function formatVersionDetailed() {
9052
- const lines = [];
9053
- lines.push(chalk14.bold(`Vultisig CLI v${getVersion()}`));
9054
- lines.push("");
9055
- lines.push(` Node.js: ${process.version}`);
9056
- lines.push(` Platform: ${process.platform}-${process.arch}`);
9057
- lines.push(` Config: ~/.vultisig/`);
9058
- return lines.join("\n");
9059
- }
9060
- function detectInstallMethod() {
9061
- const execPath = process.execPath;
9062
- if (execPath.includes("homebrew") || execPath.includes("Cellar")) {
9063
- return "homebrew";
9064
- }
9065
- if (process.env.npm_execpath?.includes("yarn")) {
9066
- return "yarn";
9067
- }
9068
- if (process.env.npm_config_user_agent?.includes("npm")) {
9069
- return "npm";
9070
- }
9071
- if (execPath.includes("node_modules")) {
9072
- return "npm";
9073
- }
9074
- return "unknown";
9075
- }
9076
- function getUpdateCommand() {
9077
- const method = detectInstallMethod();
9078
- switch (method) {
9079
- case "npm":
9080
- return "npm update -g @vultisig/cli";
9081
- case "yarn":
9082
- return "yarn global upgrade @vultisig/cli";
9083
- case "homebrew":
9084
- return "brew upgrade vultisig";
9085
- default:
9086
- return "npm update -g @vultisig/cli";
9087
- }
9088
- }
9089
9771
 
9090
9772
  // src/lib/completion.ts
9091
9773
  import { homedir as homedir4 } from "os";
@@ -9426,9 +10108,32 @@ setupUserAgent();
9426
10108
  if (handled) process.exit(0);
9427
10109
  })();
9428
10110
  var ctx;
9429
- 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) => {
10111
+ 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(
10112
+ "-o, --output <format>",
10113
+ "Output format: json, table (defaults to json when piped)",
10114
+ (val) => {
10115
+ if (!["json", "table"].includes(val)) throw new InvalidArgumentError('Must be "json" or "table"');
10116
+ return val;
10117
+ },
10118
+ process.stdout.isTTY ? "table" : "json"
10119
+ ).option("-q, --quiet", "Strip empty/zero fields from output").option("--fields <fields>", "Comma-separated list of fields to include in output").option("--non-interactive", "Disable interactive prompts (fail instead of asking)").option("--ci", "CI/automation mode (equivalent to --output json --non-interactive --quiet)").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").addHelpText(
10120
+ "after",
10121
+ "\nExit codes:\n" + Object.entries(EXIT_CODE_DESCRIPTIONS).map(([k, v]) => ` ${k} ${v}`).join("\n") + "\n\nEnvironment variables:\n VAULT_PASSWORD Vault password for signing (bypasses prompt)\n VULTISIG_PASSWORD Alias for VAULT_PASSWORD\n VAULT_PASSWORDS Space-separated VaultName:password pairs\n VULTISIG_VAULT Default vault name or ID\n VULTISIG_CONFIG_DIR Override config directory (~/.vultisig)\n VULTISIG_SILENT Set to 1 for silent mode\n NO_COLOR Disable colored output"
10122
+ ).hook("preAction", (thisCommand) => {
9430
10123
  const opts = thisCommand.opts();
10124
+ if (opts.ci) {
10125
+ const outputExplicit = process.argv.some((a) => a === "--output" || a === "-o" || a.startsWith("--output="));
10126
+ opts.output = opts.output === "table" && !outputExplicit ? "json" : opts.output;
10127
+ opts.quiet = true;
10128
+ opts.nonInteractive = true;
10129
+ }
9431
10130
  initOutputMode({ silent: opts.silent, output: opts.output });
10131
+ setQuiet(!!opts.quiet);
10132
+ setNonInteractive(!!opts.nonInteractive);
10133
+ const fields = opts.fields;
10134
+ setFields(
10135
+ fields ? fields.split(",").map((f) => f.trim()).filter(Boolean) : void 0
10136
+ );
9432
10137
  });
9433
10138
  async function findVaultByNameOrId(sdk, nameOrId) {
9434
10139
  const vaults = await sdk.listVaults();
@@ -9472,7 +10177,15 @@ async function init(vaultOverride, unlockPassword, passwordTTL) {
9472
10177
  return ctx;
9473
10178
  }
9474
10179
  var createCmd = program.command("create").description("Create a vault");
9475
- createCmd.command("fast").description("Create a fast vault (server-assisted 2-of-2)").requiredOption("--name <name>", "Vault name").requiredOption("--password <password>", "Vault password").requiredOption("--email <email>", "Email for verification").option("--two-step", 'Create vault without verifying OTP (verify later with "vultisig verify")').action(
10180
+ createCmd.command("fast").description("Create a fast vault (server-assisted 2-of-2)").requiredOption("--name <name>", "Vault name").requiredOption("--password <password>", "Vault password").requiredOption("--email <email>", "Email for verification").option("--two-step", 'Create vault without verifying OTP (verify later with "vultisig verify")').addHelpText(
10181
+ "after",
10182
+ `
10183
+ Examples:
10184
+ vultisig create fast --name mywallet --password secret --email me@example.com
10185
+ vultisig create fast --name mywallet --password secret --email me@example.com --two-step
10186
+
10187
+ See also: verify, auth setup`
10188
+ ).action(
9476
10189
  withExit(async (options) => {
9477
10190
  const context = await init(program.opts().vault);
9478
10191
  await executeCreateFast(context, { ...options, twoStep: options.twoStep });
@@ -9498,7 +10211,13 @@ program.command("add-mldsa").description("Add ML-DSA (post-quantum) keys to the
9498
10211
  });
9499
10212
  })
9500
10213
  );
9501
- program.command("import <file>").description("Import vault from .vult file").option("--password <password>", "Password to decrypt the vault file").action(
10214
+ program.command("import <file>").description("Import vault from .vult file").option("--password <password>", "Password to decrypt the vault file").addHelpText(
10215
+ "after",
10216
+ `
10217
+ Examples:
10218
+ vultisig import ~/vault-backup.vult
10219
+ vultisig import ~/vault-backup.vult --password mypassword`
10220
+ ).action(
9502
10221
  withExit(async (file, options) => {
9503
10222
  const context = await init(program.opts().vault);
9504
10223
  await executeImport(context, file, options.password);
@@ -9506,6 +10225,7 @@ program.command("import <file>").description("Import vault from .vult file").opt
9506
10225
  );
9507
10226
  var createFromSeedphraseCmd = program.command("create-from-seedphrase").description("Create vault from BIP39 seedphrase");
9508
10227
  async function promptSeedphrase() {
10228
+ requireInteractive("Use --mnemonic flag to provide seedphrase non-interactively.");
9509
10229
  info("\nEnter your 12 or 24-word recovery phrase.");
9510
10230
  info("Words will be hidden as you type.\n");
9511
10231
  const answer = await inquirer8.prompt([
@@ -9526,6 +10246,7 @@ async function promptSeedphrase() {
9526
10246
  return answer.mnemonic.trim().toLowerCase();
9527
10247
  }
9528
10248
  async function promptQrPayload() {
10249
+ requireInteractive("Use --qr or --qr-file flag to provide QR payload non-interactively.");
9529
10250
  info("\nEnter the QR code payload from the initiator device.");
9530
10251
  info('The payload starts with "vultisig://".\n');
9531
10252
  const answer = await inquirer8.prompt([
@@ -9651,7 +10372,15 @@ program.command("verify <vaultId>").description("Verify vault with email verific
9651
10372
  }
9652
10373
  )
9653
10374
  );
9654
- program.command("balance [chain]").description("Show balance for a chain or all chains").option("-t, --tokens", "Include token balances").option("--raw", "Show raw values (wei/satoshis) for programmatic use").action(
10375
+ program.command("balance [chain]").description(descriptions.balance.description).option("-t, --tokens", descriptions.balance.params.includeTokens).option("--raw", "Show raw values (wei/satoshis) for programmatic use").addHelpText(
10376
+ "after",
10377
+ `
10378
+ Examples:
10379
+ vultisig balance
10380
+ vultisig balance Ethereum --tokens
10381
+ vultisig balance --output json --fields chain,amount
10382
+ vultisig balance --output json -q`
10383
+ ).action(
9655
10384
  withExit(async (chainStr, options) => {
9656
10385
  const context = await init(program.opts().vault);
9657
10386
  await executeBalance(context, {
@@ -9661,7 +10390,20 @@ program.command("balance [chain]").description("Show balance for a chain or all
9661
10390
  });
9662
10391
  })
9663
10392
  );
9664
- program.command("send <chain> <to> [amount]").description("Send tokens to an address").option("--max", "Send maximum amount (balance minus fees)").option("--token <tokenId>", "Token to send (default: native)").option("--memo <memo>", "Transaction memo").option("-y, --yes", "Skip confirmation prompt").option("--password <password>", "Vault password for signing").action(
10393
+ program.command("send <chain> <to> [amount]").description(descriptions.send.description).option("--max", "Send maximum amount (balance minus fees)").option("--token <tokenId>", "Token to send (default: native)").option("--memo <memo>", "Transaction memo").option("--dry-run", "Preview transaction without signing or broadcasting").option("--confirm", "Confirm and broadcast (without this flag, runs as a preview)").option("-y, --yes", "Alias for --confirm").option("--password <password>", "Vault password for signing").addHelpText(
10394
+ "after",
10395
+ `
10396
+ Examples:
10397
+ vultisig send Ethereum 0x1234...abcd 0.1
10398
+ vultisig send Bitcoin bc1q... --max --confirm
10399
+ vultisig send Ethereum 0x... 0.5 --dry-run --output json
10400
+
10401
+ Environment variables:
10402
+ VAULT_PASSWORD Vault password (bypasses prompt)
10403
+ VAULT_PASSWORDS Space-separated VaultName:password pairs
10404
+
10405
+ See also: balance, tx-status`
10406
+ ).action(
9665
10407
  withExit(
9666
10408
  async (chainStr, to, amount, options) => {
9667
10409
  if (!amount && !options.max) throw new Error("Provide an amount or use --max");
@@ -9674,7 +10416,8 @@ program.command("send <chain> <to> [amount]").description("Send tokens to an add
9674
10416
  amount: amount ?? "max",
9675
10417
  tokenId: options.token,
9676
10418
  memo: options.memo,
9677
- yes: options.yes,
10419
+ dryRun: options.dryRun,
10420
+ yes: options.yes || options.confirm,
9678
10421
  password: options.password
9679
10422
  });
9680
10423
  } catch (err) {
@@ -9687,7 +10430,13 @@ program.command("send <chain> <to> [amount]").description("Send tokens to an add
9687
10430
  }
9688
10431
  )
9689
10432
  );
9690
- program.command("execute <chain> <contract> <msg>").description("Execute a CosmWasm smart contract (THORChain, MayaChain)").option("--funds <funds>", 'Funds to send with execution (format: "denom:amount" or "denom:amount,denom2:amount2")').option("--memo <memo>", "Transaction memo").option("-y, --yes", "Skip confirmation prompt").option("--password <password>", "Vault password for signing").action(
10433
+ program.command("execute <chain> <contract> <msg>").description("Execute a CosmWasm smart contract (THORChain, MayaChain)").option("--funds <funds>", 'Funds to send with execution (format: "denom:amount" or "denom:amount,denom2:amount2")').option("--memo <memo>", "Transaction memo").option("--dry-run", "Preview execution without signing or broadcasting").option("-y, --yes", "Skip confirmation prompt").option("--password <password>", "Vault password for signing").addHelpText(
10434
+ "after",
10435
+ `
10436
+ Examples:
10437
+ vultisig execute THORChain <contract> '{"swap":{}}' --yes
10438
+ vultisig execute THORChain <contract> '{"deposit":{}}' --funds rune:1000000 --output json`
10439
+ ).action(
9691
10440
  withExit(
9692
10441
  async (chainStr, contract, msg, options) => {
9693
10442
  const context = await init(program.opts().vault, options.password);
@@ -9698,6 +10447,7 @@ program.command("execute <chain> <contract> <msg>").description("Execute a CosmW
9698
10447
  msg,
9699
10448
  funds: options.funds,
9700
10449
  memo: options.memo,
10450
+ dryRun: options.dryRun,
9701
10451
  yes: options.yes,
9702
10452
  password: options.password
9703
10453
  });
@@ -9730,7 +10480,13 @@ program.command("broadcast").description("Broadcast a pre-signed raw transaction
9730
10480
  });
9731
10481
  })
9732
10482
  );
9733
- program.command("tx-status").description("Check the status of a transaction (polls until confirmed)").requiredOption("--chain <chain>", "Target blockchain").requiredOption("--tx-hash <hash>", "Transaction hash to check").option("--no-wait", "Return immediately without waiting for confirmation").action(
10483
+ program.command("tx-status").description("Check the status of a transaction (polls until confirmed)").requiredOption("--chain <chain>", "Target blockchain").requiredOption("--tx-hash <hash>", "Transaction hash to check").option("--no-wait", "Return immediately without waiting for confirmation").addHelpText(
10484
+ "after",
10485
+ `
10486
+ Examples:
10487
+ vultisig tx-status --chain Ethereum --tx-hash 0xabc...
10488
+ vultisig tx-status --chain Bitcoin --tx-hash abc... --no-wait --output json`
10489
+ ).action(
9734
10490
  withExit(async (options) => {
9735
10491
  const context = await init(program.opts().vault);
9736
10492
  await executeTxStatus(context, {
@@ -9740,7 +10496,13 @@ program.command("tx-status").description("Check the status of a transaction (pol
9740
10496
  });
9741
10497
  })
9742
10498
  );
9743
- program.command("portfolio").description("Show total portfolio value").option("-c, --currency <currency>", "Fiat currency (usd, eur, gbp, etc.)", "usd").option("--raw", "Show raw values (wei/satoshis) for programmatic use").action(
10499
+ program.command("portfolio").description(descriptions.portfolio.description).option("-c, --currency <currency>", "Fiat currency (usd, eur, gbp, etc.)", "usd").option("--raw", "Show raw values (wei/satoshis) for programmatic use").addHelpText(
10500
+ "after",
10501
+ `
10502
+ Examples:
10503
+ vultisig portfolio
10504
+ vultisig portfolio --currency eur --output json`
10505
+ ).action(
9744
10506
  withExit(async (options) => {
9745
10507
  const context = await init(program.opts().vault);
9746
10508
  await executePortfolio(context, {
@@ -9767,7 +10529,13 @@ program.command("discount").description("Show your VULT discount tier for swap f
9767
10529
  await executeDiscount(context, { refresh: options.refresh });
9768
10530
  })
9769
10531
  );
9770
- program.command("export [path]").description("Export vault to file").option("--password <password>", "Password to unlock the vault (for encrypted vaults)").option("--exportPassword <password>", "Password to encrypt the exported file (defaults to --password)").action(
10532
+ program.command("export [path]").description("Export vault to file").option("--password <password>", "Password to unlock the vault (for encrypted vaults)").option("--exportPassword <password>", "Password to encrypt the exported file (defaults to --password)").addHelpText(
10533
+ "after",
10534
+ `
10535
+ Examples:
10536
+ vultisig export ~/backup.vult
10537
+ vultisig export ~/backup.vult --password mypass --output json`
10538
+ ).action(
9771
10539
  withExit(async (path4, options) => {
9772
10540
  const context = await init(program.opts().vault, options.password);
9773
10541
  await executeExport(context, {
@@ -9777,7 +10545,13 @@ program.command("export [path]").description("Export vault to file").option("--p
9777
10545
  });
9778
10546
  })
9779
10547
  );
9780
- program.command("addresses").description("Show all vault addresses").action(
10548
+ program.command("addresses").description(descriptions.address.description).addHelpText(
10549
+ "after",
10550
+ `
10551
+ Examples:
10552
+ vultisig addresses
10553
+ vultisig addresses --output json --fields chain,address`
10554
+ ).action(
9781
10555
  withExit(async () => {
9782
10556
  const context = await init(program.opts().vault);
9783
10557
  await executeAddresses(context);
@@ -9795,7 +10569,14 @@ program.command("address-book").description("Manage address book entries").optio
9795
10569
  });
9796
10570
  })
9797
10571
  );
9798
- program.command("chains").description("List and manage chains").option("--add <chain>", "Add a chain").option("--add-all", "Add all supported chains").option("--remove <chain>", "Remove a chain").action(
10572
+ program.command("chains").description("List and manage chains").option("--add <chain>", "Add a chain").option("--add-all", "Add all supported chains").option("--remove <chain>", "Remove a chain").addHelpText(
10573
+ "after",
10574
+ `
10575
+ Examples:
10576
+ vultisig chains
10577
+ vultisig chains --add Solana
10578
+ vultisig chains --add-all --output json`
10579
+ ).action(
9799
10580
  withExit(async (options) => {
9800
10581
  const context = await init(program.opts().vault);
9801
10582
  await executeChains(context, {
@@ -9805,7 +10586,13 @@ program.command("chains").description("List and manage chains").option("--add <c
9805
10586
  });
9806
10587
  })
9807
10588
  );
9808
- program.command("vaults").description("List all stored vaults").action(
10589
+ program.command("vaults").description("List all stored vaults").addHelpText(
10590
+ "after",
10591
+ `
10592
+ Examples:
10593
+ vultisig vaults
10594
+ vultisig vaults --output json`
10595
+ ).action(
9809
10596
  withExit(async () => {
9810
10597
  const context = await init(program.opts().vault);
9811
10598
  await executeVaults(context);
@@ -9823,7 +10610,7 @@ program.command("rename <newName>").description("Rename the active vault").actio
9823
10610
  await executeRename(context, newName);
9824
10611
  })
9825
10612
  );
9826
- program.command("info").description("Show detailed vault information").action(
10613
+ program.command("info").description(descriptions.vaultInfo.description).action(
9827
10614
  withExit(async () => {
9828
10615
  const context = await init(program.opts().vault);
9829
10616
  await executeInfo(context);
@@ -9838,7 +10625,14 @@ program.command("delete [vault]").description("Delete a vault from local storage
9838
10625
  });
9839
10626
  })
9840
10627
  );
9841
- program.command("tokens <chain>").description("List and manage tokens for a chain").option("--add <contractAddress>", "Add a token by contract address").option("--remove <tokenId>", "Remove a token by ID").option("--symbol <symbol>", "Token symbol (for --add)").option("--name <name>", "Token name (for --add)").option("--decimals <decimals>", "Token decimals (for --add)", "18").action(
10628
+ program.command("tokens <chain>").description("List and manage tokens for a chain").option("--add <contractAddress>", "Add a token by contract address").option("--remove <tokenId>", "Remove a token by ID").option("--discover", "Auto-discover tokens with balances on the chain").addHelpText(
10629
+ "after",
10630
+ `
10631
+ Examples:
10632
+ vultisig tokens Ethereum
10633
+ vultisig tokens Ethereum --discover --output json
10634
+ vultisig tokens Ethereum --add 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 --symbol USDC --decimals 6`
10635
+ ).option("--symbol <symbol>", "Token symbol (for --add)").option("--name <name>", "Token name (for --add)").option("--decimals <decimals>", "Token decimals (for --add)", "18").action(
9842
10636
  withExit(
9843
10637
  async (chainStr, options) => {
9844
10638
  const context = await init(program.opts().vault);
@@ -9846,6 +10640,7 @@ program.command("tokens <chain>").description("List and manage tokens for a chai
9846
10640
  chain: findChainByName(chainStr) || chainStr,
9847
10641
  add: options.add,
9848
10642
  remove: options.remove,
10643
+ discover: options.discover,
9849
10644
  symbol: options.symbol,
9850
10645
  name: options.name,
9851
10646
  decimals: options.decimals ? parseInt(options.decimals, 10) : void 0
@@ -9853,13 +10648,19 @@ program.command("tokens <chain>").description("List and manage tokens for a chai
9853
10648
  }
9854
10649
  )
9855
10650
  );
9856
- program.command("swap-chains").description("List chains that support swaps").action(
10651
+ program.command("swap-chains").description(descriptions.supportedChains.description).action(
9857
10652
  withExit(async () => {
9858
10653
  const context = await init(program.opts().vault);
9859
10654
  await executeSwapChains(context);
9860
10655
  })
9861
10656
  );
9862
- program.command("swap-quote <fromChain> <toChain> [amount]").description("Get a swap quote without executing").option("--max", "Swap maximum amount (full balance minus fees for native)").option("--from-token <address>", "Token address to swap from (default: native)").option("--to-token <address>", "Token address to swap to (default: native)").action(
10657
+ program.command("swap-quote <fromChain> <toChain> [amount]").description(descriptions.swapQuote.description).option("--max", "Swap maximum amount (full balance minus fees for native)").option("--from-token <address>", "Token address to swap from (default: native)").option("--to-token <address>", "Token address to swap to (default: native)").addHelpText(
10658
+ "after",
10659
+ `
10660
+ Examples:
10661
+ vultisig swap-quote Ethereum Bitcoin 0.1
10662
+ vultisig swap-quote Ethereum Bitcoin --max --output json`
10663
+ ).action(
9863
10664
  withExit(
9864
10665
  async (fromChainStr, toChainStr, amountStr, options) => {
9865
10666
  if (!amountStr && !options.max) throw new Error("Provide an amount or use --max");
@@ -9875,7 +10676,16 @@ program.command("swap-quote <fromChain> <toChain> [amount]").description("Get a
9875
10676
  }
9876
10677
  )
9877
10678
  );
9878
- program.command("swap <fromChain> <toChain> [amount]").description("Swap tokens between chains").option("--max", "Swap maximum amount (full balance minus fees for native)").option("--from-token <address>", "Token address to swap from (default: native)").option("--to-token <address>", "Token address to swap to (default: native)").option("--slippage <percent>", "Slippage tolerance in percent", "1").option("-y, --yes", "Skip confirmation prompt").option("--password <password>", "Vault password for signing").action(
10679
+ program.command("swap <fromChain> <toChain> [amount]").description(descriptions.swap.description).option("--max", "Swap maximum amount (full balance minus fees for native)").option("--from-token <address>", "Token address to swap from (default: native)").option("--to-token <address>", "Token address to swap to (default: native)").option("--slippage <percent>", "Slippage tolerance in percent", "1").option("--dry-run", "Preview swap without signing or broadcasting").option("--confirm", "Confirm and broadcast (without this flag, runs as a preview)").option("-y, --yes", "Alias for --confirm").option("--password <password>", "Vault password for signing").addHelpText(
10680
+ "after",
10681
+ `
10682
+ Examples:
10683
+ vultisig swap Ethereum Bitcoin 0.1
10684
+ vultisig swap Ethereum Bitcoin --max --confirm
10685
+ vultisig swap Ethereum Bitcoin 0.5 --dry-run --output json
10686
+
10687
+ See also: swap-quote, swap-chains, balance`
10688
+ ).action(
9879
10689
  withExit(
9880
10690
  async (fromChainStr, toChainStr, amountStr, options) => {
9881
10691
  if (!amountStr && !options.max) throw new Error("Provide an amount or use --max");
@@ -9889,7 +10699,8 @@ program.command("swap <fromChain> <toChain> [amount]").description("Swap tokens
9889
10699
  fromToken: options.fromToken,
9890
10700
  toToken: options.toToken,
9891
10701
  slippage: options.slippage ? parseFloat(options.slippage) : void 0,
9892
- yes: options.yes,
10702
+ dryRun: options.dryRun,
10703
+ yes: options.yes || options.confirm,
9893
10704
  password: options.password
9894
10705
  });
9895
10706
  } catch (err) {
@@ -9933,7 +10744,7 @@ rujiraCmd.command("deposit").description("Show deposit instructions (inbound add
9933
10744
  }
9934
10745
  )
9935
10746
  );
9936
- rujiraCmd.command("swap <fromAsset> <toAsset> <amount>").description("Execute a FIN swap (amount in base units)").option("--slippage-bps <bps>", "Slippage tolerance in basis points (default: 100 = 1%)", "100").option("--destination <thorAddress>", "Destination THOR address (default: vault THORChain address)").option("-y, --yes", "Skip confirmation prompt").option("--password <password>", "Vault password for signing").option("--rpc <url>", "Override THORChain RPC endpoint").option("--rest <url>", "Override THORNode REST endpoint").action(
10747
+ rujiraCmd.command("swap <fromAsset> <toAsset> <amount>").description("Execute a FIN swap (amount in base units)").option("--slippage-bps <bps>", "Slippage tolerance in basis points (default: 100 = 1%)", "100").option("--destination <thorAddress>", "Destination THOR address (default: vault THORChain address)").option("--dry-run", "Preview swap quote without executing").option("-y, --yes", "Skip confirmation prompt").option("--password <password>", "Vault password for signing").option("--rpc <url>", "Override THORChain RPC endpoint").option("--rest <url>", "Override THORNode REST endpoint").action(
9937
10748
  withExit(
9938
10749
  async (fromAsset, toAsset, amount, options) => {
9939
10750
  const context = await init(program.opts().vault, options.password);
@@ -9943,6 +10754,7 @@ rujiraCmd.command("swap <fromAsset> <toAsset> <amount>").description("Execute a
9943
10754
  amount,
9944
10755
  slippageBps: options.slippageBps ? parseInt(options.slippageBps, 10) : void 0,
9945
10756
  destination: options.destination,
10757
+ dryRun: options.dryRun,
9946
10758
  yes: options.yes,
9947
10759
  password: options.password,
9948
10760
  rpcEndpoint: options.rpc,
@@ -9951,7 +10763,7 @@ rujiraCmd.command("swap <fromAsset> <toAsset> <amount>").description("Execute a
9951
10763
  }
9952
10764
  )
9953
10765
  );
9954
- rujiraCmd.command("withdraw <asset> <amount> <l1Address>").description("Withdraw secured assets to L1 (amount in base units)").option("--max-fee-bps <bps>", "Max outbound fee as bps of amount (optional)").option("-y, --yes", "Skip confirmation prompt").option("--password <password>", "Vault password for signing").option("--rpc <url>", "Override THORChain RPC endpoint").option("--rest <url>", "Override THORNode REST endpoint").action(
10766
+ rujiraCmd.command("withdraw <asset> <amount> <l1Address>").description("Withdraw secured assets to L1 (amount in base units)").option("--max-fee-bps <bps>", "Max outbound fee as bps of amount (optional)").option("--dry-run", "Preview withdrawal without executing").option("-y, --yes", "Skip confirmation prompt").option("--password <password>", "Vault password for signing").option("--rpc <url>", "Override THORChain RPC endpoint").option("--rest <url>", "Override THORNode REST endpoint").action(
9955
10767
  withExit(
9956
10768
  async (asset, amount, l1Address, options) => {
9957
10769
  const context = await init(program.opts().vault, options.password);
@@ -9960,6 +10772,7 @@ rujiraCmd.command("withdraw <asset> <amount> <l1Address>").description("Withdraw
9960
10772
  amount,
9961
10773
  l1Address,
9962
10774
  maxFeeBps: options.maxFeeBps ? parseInt(options.maxFeeBps, 10) : void 0,
10775
+ dryRun: options.dryRun,
9963
10776
  yes: options.yes,
9964
10777
  password: options.password,
9965
10778
  rpcEndpoint: options.rpc,
@@ -9994,7 +10807,13 @@ var agentCmd = program.command("agent").description("AI-powered chat interface f
9994
10807
  });
9995
10808
  }
9996
10809
  );
9997
- 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(
10810
+ 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)").addHelpText(
10811
+ "after",
10812
+ `
10813
+ Examples:
10814
+ vultisig agent ask "What is my ETH balance?" --output json
10815
+ vultisig agent ask "Send 0.1 ETH to 0x..." --session abc123 --yes`
10816
+ ).action(
9998
10817
  async (message, options) => {
9999
10818
  const parentOpts = agentCmd.opts();
10000
10819
  const context = await init(program.opts().vault, options.password || parentOpts.password);
@@ -10062,7 +10881,66 @@ program.command("update").description("Check for updates and show update command
10062
10881
  }
10063
10882
  })
10064
10883
  );
10884
+ var authCmd = program.command("auth").description("Manage keyring-stored vault credentials");
10885
+ authCmd.command("setup").description("Discover .vult files, prompt for passwords, and store credentials in the OS keyring").option("--vault-file <path>", "Path to a specific .vult file").option("--non-interactive", "Fail instead of prompting (use env vars)").addHelpText(
10886
+ "after",
10887
+ `
10888
+ Examples:
10889
+ vultisig auth setup
10890
+ vultisig auth setup --vault-file ~/vault.vult
10891
+ VAULT_PASSWORD=secret VAULT_DECRYPT_PASSWORD=pass vultisig auth setup --non-interactive`
10892
+ ).action(
10893
+ withExit(async (options) => {
10894
+ const result = await executeAuthSetup({
10895
+ vaultFile: options.vaultFile,
10896
+ nonInteractive: options.nonInteractive || isNonInteractive()
10897
+ });
10898
+ if (isJsonOutput()) {
10899
+ outputJson({
10900
+ stored: true,
10901
+ vaultId: result.vaultId,
10902
+ vaultName: result.vaultName,
10903
+ storageBackend: result.storageBackend
10904
+ });
10905
+ } else {
10906
+ printResult(
10907
+ chalk15.green(`Vault "${result.vaultName}" (${result.vaultId}) credentials stored in ${result.storageBackend}.`)
10908
+ );
10909
+ }
10910
+ })
10911
+ );
10912
+ authCmd.command("status").description("List configured vaults and their keyring credential status").action(
10913
+ withExit(async () => {
10914
+ const vaults = await executeAuthStatus();
10915
+ if (isJsonOutput()) {
10916
+ outputJson({
10917
+ vaults: vaults.map((v) => ({ id: v.id, name: v.name, filePath: v.filePath, hasCredentials: v.hasCredentials }))
10918
+ });
10919
+ return;
10920
+ }
10921
+ if (vaults.length === 0) {
10922
+ printResult("No vaults configured. Run: vsig auth setup");
10923
+ return;
10924
+ }
10925
+ for (const v of vaults) {
10926
+ const status = v.hasCredentials ? chalk15.green("authenticated") : chalk15.red("no credentials");
10927
+ printResult(` ${v.name} (${v.id}) - ${status}`);
10928
+ printResult(` File: ${v.filePath}`);
10929
+ }
10930
+ })
10931
+ );
10932
+ authCmd.command("logout").description("Clear keyring credentials for a vault").option("--vault-id <id>", "Specific vault ID to clear").option("--all", "Clear credentials for all configured vaults").action(
10933
+ withExit(async (options) => {
10934
+ await executeAuthLogout({ vaultId: options.vaultId, all: options.all });
10935
+ if (isJsonOutput()) {
10936
+ outputJson({ cleared: true, vaultId: options.vaultId ?? null, all: !!options.all });
10937
+ } else {
10938
+ printResult(chalk15.green("Credentials cleared."));
10939
+ }
10940
+ })
10941
+ );
10065
10942
  setupCompletionCommand(program);
10943
+ program.command("schema", { hidden: true }).description("Output machine-readable command schema (JSON introspection for agents)").helpOption(false).action(withExit(async () => executeSchema(program)));
10066
10944
  async function startInteractiveMode() {
10067
10945
  const serverEndpoints = resolveServerEndpoints(parseServerEndpointOverridesFromArgv(process.argv.slice(2)));
10068
10946
  const sdk = new Vultisig6({