@vultisig/cli 0.15.4 → 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.
Files changed (3) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/index.js +1347 -604
  3. package/package.json +8 -4
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,12 +4628,15 @@ 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
 
4229
4638
  // src/agent/agentErrors.ts
4230
- import { VaultError, VaultErrorCode, VaultImportError, VaultImportErrorCode } from "@vultisig/sdk";
4639
+ import { VaultError as VaultError2, VaultErrorCode as VaultErrorCode2, VaultImportError as VaultImportError2, VaultImportErrorCode as VaultImportErrorCode2 } from "@vultisig/sdk";
4231
4640
  var AgentErrorCode = /* @__PURE__ */ ((AgentErrorCode3) => {
4232
4641
  AgentErrorCode3["BACKEND_UNREACHABLE"] = "BACKEND_UNREACHABLE";
4233
4642
  AgentErrorCode3["AUTH_FAILED"] = "AUTH_FAILED";
@@ -4249,32 +4658,32 @@ function isAgentErrorCode(value) {
4249
4658
  return AGENT_ERROR_CODE_VALUES.has(value);
4250
4659
  }
4251
4660
  function mapVaultError(err) {
4252
- if (err.code === VaultErrorCode.InvalidConfig && /failed to unlock vault/i.test(err.message)) {
4661
+ if (err.code === VaultErrorCode2.InvalidConfig && /failed to unlock vault/i.test(err.message)) {
4253
4662
  return "AUTH_FAILED" /* AUTH_FAILED */;
4254
4663
  }
4255
4664
  switch (err.code) {
4256
- case VaultErrorCode.Timeout:
4665
+ case VaultErrorCode2.Timeout:
4257
4666
  return "TIMEOUT" /* TIMEOUT */;
4258
- case VaultErrorCode.NetworkError:
4259
- case VaultErrorCode.BalanceFetchFailed:
4667
+ case VaultErrorCode2.NetworkError:
4668
+ case VaultErrorCode2.BalanceFetchFailed:
4260
4669
  return "NETWORK_ERROR" /* NETWORK_ERROR */;
4261
- case VaultErrorCode.SigningFailed:
4670
+ case VaultErrorCode2.SigningFailed:
4262
4671
  return "SIGNING_FAILED" /* SIGNING_FAILED */;
4263
- case VaultErrorCode.BroadcastFailed:
4264
- case VaultErrorCode.GasEstimationFailed:
4672
+ case VaultErrorCode2.BroadcastFailed:
4673
+ case VaultErrorCode2.GasEstimationFailed:
4265
4674
  return "TRANSACTION_FAILED" /* TRANSACTION_FAILED */;
4266
- case VaultErrorCode.NotImplemented:
4675
+ case VaultErrorCode2.NotImplemented:
4267
4676
  return "ACTION_NOT_IMPLEMENTED" /* ACTION_NOT_IMPLEMENTED */;
4268
- case VaultErrorCode.InvalidAmount:
4269
- case VaultErrorCode.UnsupportedChain:
4270
- case VaultErrorCode.UnsupportedToken:
4271
- case VaultErrorCode.ChainNotSupported:
4272
- case VaultErrorCode.InvalidVault:
4273
- case VaultErrorCode.InvalidPublicKey:
4274
- case VaultErrorCode.InvalidChainCode:
4275
- case VaultErrorCode.AddressDerivationFailed:
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:
4276
4685
  return "INVALID_INPUT" /* INVALID_INPUT */;
4277
- case VaultErrorCode.InvalidConfig:
4686
+ case VaultErrorCode2.InvalidConfig:
4278
4687
  return "INVALID_INPUT" /* INVALID_INPUT */;
4279
4688
  default:
4280
4689
  return "UNKNOWN_ERROR" /* UNKNOWN_ERROR */;
@@ -4282,9 +4691,9 @@ function mapVaultError(err) {
4282
4691
  }
4283
4692
  function mapVaultImportError(err) {
4284
4693
  switch (err.code) {
4285
- case VaultImportErrorCode.PASSWORD_REQUIRED:
4694
+ case VaultImportErrorCode2.PASSWORD_REQUIRED:
4286
4695
  return "PASSWORD_REQUIRED" /* PASSWORD_REQUIRED */;
4287
- case VaultImportErrorCode.INVALID_PASSWORD:
4696
+ case VaultImportErrorCode2.INVALID_PASSWORD:
4288
4697
  return "AUTH_FAILED" /* AUTH_FAILED */;
4289
4698
  default:
4290
4699
  return "INVALID_INPUT" /* INVALID_INPUT */;
@@ -4322,10 +4731,10 @@ function nodeErrCode(err) {
4322
4731
  return void 0;
4323
4732
  }
4324
4733
  function normalizeAgentError(err) {
4325
- if (err instanceof VaultError) {
4734
+ if (err instanceof VaultError2) {
4326
4735
  return { code: mapVaultError(err), message: err.message };
4327
4736
  }
4328
- if (err instanceof VaultImportError) {
4737
+ if (err instanceof VaultImportError2) {
4329
4738
  return { code: mapVaultImportError(err), message: err.message };
4330
4739
  }
4331
4740
  const name = err instanceof Error ? err.name : "";
@@ -4671,8 +5080,30 @@ var AgentClient = class {
4671
5080
  }
4672
5081
  handleSSEEvent(event, data, result, callbacks) {
4673
5082
  try {
4674
- const parsed = JSON.parse(data);
4675
- 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) {
4676
5107
  case "text_delta":
4677
5108
  if (typeof parsed.delta === "string") {
4678
5109
  result.fullText += parsed.delta;
@@ -5147,6 +5578,7 @@ var AgentExecutor = class {
5147
5578
  stateStore = null;
5148
5579
  /** Held chain lock release functions, keyed by chain name */
5149
5580
  chainLockReleases = /* @__PURE__ */ new Map();
5581
+ evmLastBroadcast = /* @__PURE__ */ new Map();
5150
5582
  /** Backend client for resolving calldata_id references. */
5151
5583
  backendClient = null;
5152
5584
  constructor(vault, verbose = false, vaultId, vultisig) {
@@ -5693,6 +6125,7 @@ var AgentExecutor = class {
5693
6125
  keysignPayload: payload,
5694
6126
  signature
5695
6127
  });
6128
+ this.evmLastBroadcast.set(chain.toString(), Date.now());
5696
6129
  try {
5697
6130
  this.recordEvmNonceFromPayload(chain, payload, messageHashes.length);
5698
6131
  } catch (nonceErr) {
@@ -5790,6 +6223,7 @@ var AgentExecutor = class {
5790
6223
  keysignPayload,
5791
6224
  signature
5792
6225
  });
6226
+ this.evmLastBroadcast.set(chain.toString(), Date.now());
5793
6227
  try {
5794
6228
  this.recordEvmNonceFromPayload(chain, keysignPayload, messageHashes.length);
5795
6229
  } catch (nonceErr) {
@@ -5948,6 +6382,16 @@ var AgentExecutor = class {
5948
6382
  const rpcNonce = bs.value.nonce;
5949
6383
  const nextNonce = this.stateStore.getNextEvmNonce(chain, rpcNonce);
5950
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
+ }
5951
6395
  const pendingNonce = await this.fetchEvmPendingNonce(chain);
5952
6396
  if (pendingNonce !== null && pendingNonce === rpcNonce) {
5953
6397
  if (this.verbose)
@@ -7524,15 +7968,13 @@ async function executeAgentAsk(ctx2, message, options) {
7524
7968
  const callbacks = ask.getCallbacks();
7525
7969
  await session.initialize(callbacks);
7526
7970
  const result = await ask.ask(message);
7527
- if (options.json) {
7528
- process.stdout.write(
7529
- JSON.stringify({
7530
- session_id: result.sessionId,
7531
- response: result.response,
7532
- tool_calls: result.toolCalls,
7533
- transactions: result.transactions
7534
- }) + "\n"
7535
- );
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
+ });
7536
7978
  } else {
7537
7979
  process.stdout.write(`session:${result.sessionId}
7538
7980
  `);
@@ -7598,46 +8040,294 @@ async function executeAgentSessionsList(ctx2, options) {
7598
8040
  printResult("No sessions found.");
7599
8041
  return;
7600
8042
  }
7601
- const table = new Table({
7602
- head: [chalk9.cyan("ID"), chalk9.cyan("Title"), chalk9.cyan("Created"), chalk9.cyan("Updated")]
7603
- });
7604
- for (const conv of allConversations) {
7605
- table.push([
7606
- conv.id,
7607
- conv.title || chalk9.gray("(untitled)"),
7608
- formatDate(conv.created_at),
7609
- formatDate(conv.updated_at)
7610
- ]);
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";
7611
8225
  }
7612
- printResult(table.toString());
7613
- printResult(chalk9.gray(`
7614
- ${totalCount} session(s) total`));
7615
8226
  }
7616
- async function executeAgentSessionsDelete(ctx2, sessionId, options) {
7617
- const vault = await ctx2.ensureActiveVault();
7618
- const backendUrl = options.backendUrl || process.env.VULTISIG_AGENT_URL || "https://abe.vultisig.com";
7619
- const client = await createAuthenticatedClient(backendUrl, vault, options.password);
7620
- const publicKey = vault.publicKeys.ecdsa;
7621
- await client.deleteConversation(sessionId, publicKey);
7622
- if (isJsonOutput()) {
7623
- outputJson({ deleted: sessionId });
7624
- 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
+ ]
7625
8273
  }
7626
- 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
+ };
7627
8286
  }
7628
- async function createAuthenticatedClient(backendUrl, vault, password) {
7629
- const client = new AgentClient(backendUrl);
7630
- const auth = await authenticateVault(client, vault, password);
7631
- client.setAuthToken(auth.token);
7632
- 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
+ }));
7633
8295
  }
7634
- function formatDate(iso) {
7635
- try {
7636
- const d = new Date(iso);
7637
- return d.toLocaleDateString() + " " + d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
7638
- } catch {
7639
- return iso;
7640
- }
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
+ `);
7641
8331
  }
7642
8332
 
7643
8333
  // src/core/server-endpoints.ts
@@ -7843,7 +8533,7 @@ function findChainByName(name) {
7843
8533
  }
7844
8534
 
7845
8535
  // src/interactive/event-buffer.ts
7846
- import chalk10 from "chalk";
8536
+ import chalk11 from "chalk";
7847
8537
  var EventBuffer = class {
7848
8538
  eventBuffer = [];
7849
8539
  isCommandRunning = false;
@@ -7883,17 +8573,17 @@ var EventBuffer = class {
7883
8573
  displayEvent(message, type) {
7884
8574
  switch (type) {
7885
8575
  case "success":
7886
- console.log(chalk10.green(message));
8576
+ console.log(chalk11.green(message));
7887
8577
  break;
7888
8578
  case "warning":
7889
- console.log(chalk10.yellow(message));
8579
+ console.log(chalk11.yellow(message));
7890
8580
  break;
7891
8581
  case "error":
7892
- console.error(chalk10.red(message));
8582
+ console.error(chalk11.red(message));
7893
8583
  break;
7894
8584
  case "info":
7895
8585
  default:
7896
- console.log(chalk10.blue(message));
8586
+ console.log(chalk11.blue(message));
7897
8587
  break;
7898
8588
  }
7899
8589
  }
@@ -7904,13 +8594,13 @@ var EventBuffer = class {
7904
8594
  if (this.eventBuffer.length === 0) {
7905
8595
  return;
7906
8596
  }
7907
- console.log(chalk10.gray("\n--- Background Events ---"));
8597
+ console.log(chalk11.gray("\n--- Background Events ---"));
7908
8598
  this.eventBuffer.forEach((event) => {
7909
8599
  const timeStr = event.timestamp.toLocaleTimeString();
7910
8600
  const message = `[${timeStr}] ${event.message}`;
7911
8601
  this.displayEvent(message, event.type);
7912
8602
  });
7913
- console.log(chalk10.gray("--- End Events ---\n"));
8603
+ console.log(chalk11.gray("--- End Events ---\n"));
7914
8604
  }
7915
8605
  /**
7916
8606
  * Setup all vault event listeners
@@ -8020,12 +8710,12 @@ var EventBuffer = class {
8020
8710
 
8021
8711
  // src/interactive/session.ts
8022
8712
  import { fiatCurrencies as fiatCurrencies4 } from "@vultisig/sdk";
8023
- import chalk12 from "chalk";
8713
+ import chalk13 from "chalk";
8024
8714
  import ora3 from "ora";
8025
8715
  import * as readline3 from "readline";
8026
8716
 
8027
8717
  // src/interactive/shell-commands.ts
8028
- import chalk11 from "chalk";
8718
+ import chalk12 from "chalk";
8029
8719
  import Table2 from "cli-table3";
8030
8720
  import inquirer6 from "inquirer";
8031
8721
  import ora2 from "ora";
@@ -8042,25 +8732,25 @@ function formatTimeRemaining(ms) {
8042
8732
  async function executeLock(ctx2) {
8043
8733
  const vault = ctx2.getActiveVault();
8044
8734
  if (!vault) {
8045
- console.log(chalk11.red("No active vault."));
8046
- 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.'));
8047
8737
  return;
8048
8738
  }
8049
8739
  ctx2.lockVault(vault.id);
8050
- console.log(chalk11.green("\n+ Vault locked"));
8051
- 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."));
8052
8742
  }
8053
8743
  async function executeUnlock(ctx2) {
8054
8744
  const vault = ctx2.getActiveVault();
8055
8745
  if (!vault) {
8056
- console.log(chalk11.red("No active vault."));
8057
- 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.'));
8058
8748
  return;
8059
8749
  }
8060
8750
  if (ctx2.isVaultUnlocked(vault.id)) {
8061
8751
  const timeRemaining = ctx2.getUnlockTimeRemaining(vault.id);
8062
- console.log(chalk11.yellow("\nVault is already unlocked."));
8063
- 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)}`));
8064
8754
  return;
8065
8755
  }
8066
8756
  const { password } = await inquirer6.prompt([
@@ -8077,19 +8767,19 @@ async function executeUnlock(ctx2) {
8077
8767
  ctx2.cachePassword(vault.id, password);
8078
8768
  const timeRemaining = ctx2.getUnlockTimeRemaining(vault.id);
8079
8769
  spinner.succeed("Vault unlocked");
8080
- console.log(chalk11.green(`
8770
+ console.log(chalk12.green(`
8081
8771
  + Vault unlocked for ${formatTimeRemaining(timeRemaining)}`));
8082
8772
  } catch (err) {
8083
8773
  spinner.fail("Failed to unlock vault");
8084
- console.error(chalk11.red(`
8774
+ console.error(chalk12.red(`
8085
8775
  x ${err.message}`));
8086
8776
  }
8087
8777
  }
8088
8778
  async function executeStatus(ctx2) {
8089
8779
  const vault = ctx2.getActiveVault();
8090
8780
  if (!vault) {
8091
- console.log(chalk11.red("No active vault."));
8092
- 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.'));
8093
8783
  return;
8094
8784
  }
8095
8785
  const isUnlocked = ctx2.isVaultUnlocked(vault.id);
@@ -8120,30 +8810,30 @@ async function executeStatus(ctx2) {
8120
8810
  displayStatus(status);
8121
8811
  }
8122
8812
  function displayStatus(status) {
8123
- console.log(chalk11.cyan("\n+----------------------------------------+"));
8124
- console.log(chalk11.cyan("| Vault Status |"));
8125
- console.log(chalk11.cyan("+----------------------------------------+\n"));
8126
- console.log(chalk11.bold("Vault:"));
8127
- 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)}`);
8128
8818
  console.log(` ID: ${status.id}`);
8129
- console.log(` Type: ${chalk11.yellow(status.type)}`);
8130
- console.log(chalk11.bold("\nSecurity:"));
8819
+ console.log(` Type: ${chalk12.yellow(status.type)}`);
8820
+ console.log(chalk12.bold("\nSecurity:"));
8131
8821
  if (status.isUnlocked) {
8132
- console.log(` Status: ${chalk11.green("Unlocked")} ${chalk11.green("\u{1F513}")}`);
8822
+ console.log(` Status: ${chalk12.green("Unlocked")} ${chalk12.green("\u{1F513}")}`);
8133
8823
  console.log(` Expires: ${status.timeRemainingFormatted}`);
8134
8824
  } else {
8135
- console.log(` Status: ${chalk11.yellow("Locked")} ${chalk11.yellow("\u{1F512}")}`);
8825
+ console.log(` Status: ${chalk12.yellow("Locked")} ${chalk12.yellow("\u{1F512}")}`);
8136
8826
  }
8137
- console.log(` Encrypted: ${status.isEncrypted ? chalk11.green("Yes") : chalk11.gray("No")}`);
8138
- console.log(` Backed Up: ${status.isBackedUp ? chalk11.green("Yes") : chalk11.yellow("No")}`);
8139
- 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:"));
8140
8830
  console.log(` Library: ${status.libType}`);
8141
- console.log(` Threshold: ${chalk11.cyan(status.threshold)} of ${chalk11.cyan(status.totalSigners)}`);
8142
- 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:"));
8143
8833
  status.availableSigningModes.forEach((mode) => {
8144
8834
  console.log(` - ${mode}`);
8145
8835
  });
8146
- console.log(chalk11.bold("\nDetails:"));
8836
+ console.log(chalk12.bold("\nDetails:"));
8147
8837
  console.log(` Chains: ${status.chains}`);
8148
8838
  console.log(` Currency: ${status.currency.toUpperCase()}`);
8149
8839
  console.log(` Created: ${new Date(status.createdAt).toLocaleString()}`);
@@ -8152,7 +8842,7 @@ function displayStatus(status) {
8152
8842
  }
8153
8843
  function showHelp() {
8154
8844
  const table = new Table2({
8155
- head: [chalk11.bold("Available Commands")],
8845
+ head: [chalk12.bold("Available Commands")],
8156
8846
  colWidths: [50],
8157
8847
  chars: {
8158
8848
  mid: "",
@@ -8166,7 +8856,7 @@ function showHelp() {
8166
8856
  }
8167
8857
  });
8168
8858
  table.push(
8169
- [chalk11.bold("Vault Management:")],
8859
+ [chalk12.bold("Vault Management:")],
8170
8860
  [" vaults - List all vaults"],
8171
8861
  [" vault <name> - Switch to vault"],
8172
8862
  [" import <file> - Import vault from file"],
@@ -8175,7 +8865,7 @@ function showHelp() {
8175
8865
  [" info - Show vault details"],
8176
8866
  [" export [path] - Export vault to file"],
8177
8867
  [""],
8178
- [chalk11.bold("Wallet Operations:")],
8868
+ [chalk12.bold("Wallet Operations:")],
8179
8869
  [" balance [chain] - Show balances"],
8180
8870
  [" send <chain> <to> <amount> - Send transaction"],
8181
8871
  [" tx-status <chain> <txHash> - Check transaction status"],
@@ -8184,22 +8874,22 @@ function showHelp() {
8184
8874
  [" chains [--add/--remove/--add-all] - Manage chains"],
8185
8875
  [" tokens <chain> - Manage tokens"],
8186
8876
  [""],
8187
- [chalk11.bold("Swap Operations:")],
8877
+ [chalk12.bold("Swap Operations:")],
8188
8878
  [" swap-chains - List swap-enabled chains"],
8189
8879
  [" swap-quote <from> <to> <amount> - Get quote"],
8190
8880
  [" swap <from> <to> <amount> - Execute swap"],
8191
8881
  [""],
8192
- [chalk11.bold("Session Commands (shell only):")],
8882
+ [chalk12.bold("Session Commands (shell only):")],
8193
8883
  [" lock - Lock vault"],
8194
8884
  [" unlock - Unlock vault"],
8195
8885
  [" status - Show vault status"],
8196
8886
  [""],
8197
- [chalk11.bold("Settings:")],
8887
+ [chalk12.bold("Settings:")],
8198
8888
  [" currency [code] - View/set currency"],
8199
8889
  [" server - Check server status"],
8200
8890
  [" address-book - Manage saved addresses"],
8201
8891
  [""],
8202
- [chalk11.bold("Help & Navigation:")],
8892
+ [chalk12.bold("Help & Navigation:")],
8203
8893
  [" help, ? - Show this help"],
8204
8894
  [" .clear - Clear screen"],
8205
8895
  [" .exit - Exit shell"]
@@ -8337,12 +9027,12 @@ var ShellSession = class {
8337
9027
  */
8338
9028
  async start() {
8339
9029
  console.clear();
8340
- console.log(chalk12.cyan.bold("\n=============================================="));
8341
- console.log(chalk12.cyan.bold(" Vultisig Interactive Shell"));
8342
- 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"));
8343
9033
  await this.loadAllVaults();
8344
9034
  this.displayVaultList();
8345
- 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'));
8346
9036
  this.promptLoop().catch(() => {
8347
9037
  });
8348
9038
  }
@@ -8376,12 +9066,12 @@ var ShellSession = class {
8376
9066
  const now = Date.now();
8377
9067
  if (now - this.lastSigintTime < this.DOUBLE_CTRL_C_TIMEOUT) {
8378
9068
  rl.close();
8379
- console.log(chalk12.yellow("\nGoodbye!"));
9069
+ console.log(chalk13.yellow("\nGoodbye!"));
8380
9070
  this.ctx.dispose();
8381
9071
  process.exit(0);
8382
9072
  }
8383
9073
  this.lastSigintTime = now;
8384
- console.log(chalk12.yellow("\n(Press Ctrl+C again to exit)"));
9074
+ console.log(chalk13.yellow("\n(Press Ctrl+C again to exit)"));
8385
9075
  rl.close();
8386
9076
  resolve("");
8387
9077
  });
@@ -8476,7 +9166,7 @@ var ShellSession = class {
8476
9166
  stopAllSpinners();
8477
9167
  process.stdout.write("\x1B[?25h");
8478
9168
  process.stdout.write("\r\x1B[K");
8479
- console.log(chalk12.yellow("\nCancelling operation..."));
9169
+ console.log(chalk13.yellow("\nCancelling operation..."));
8480
9170
  };
8481
9171
  const cleanup = () => {
8482
9172
  process.removeListener("SIGINT", onSigint);
@@ -8513,10 +9203,10 @@ var ShellSession = class {
8513
9203
  stopAllSpinners();
8514
9204
  process.stdout.write("\x1B[?25h");
8515
9205
  process.stdout.write("\r\x1B[K");
8516
- console.log(chalk12.yellow("Operation cancelled"));
9206
+ console.log(chalk13.yellow("Operation cancelled"));
8517
9207
  return;
8518
9208
  }
8519
- console.error(chalk12.red(`
9209
+ console.error(chalk13.red(`
8520
9210
  Error: ${error2.message}`));
8521
9211
  }
8522
9212
  }
@@ -8549,7 +9239,7 @@ Error: ${error2.message}`));
8549
9239
  break;
8550
9240
  case "rename":
8551
9241
  if (args.length === 0) {
8552
- console.log(chalk12.yellow("Usage: rename <newName>"));
9242
+ console.log(chalk13.yellow("Usage: rename <newName>"));
8553
9243
  return;
8554
9244
  }
8555
9245
  await executeRename(this.ctx, args.join(" "));
@@ -8625,41 +9315,41 @@ Error: ${error2.message}`));
8625
9315
  // Exit
8626
9316
  case "exit":
8627
9317
  case "quit":
8628
- console.log(chalk12.yellow("\nGoodbye!"));
9318
+ console.log(chalk13.yellow("\nGoodbye!"));
8629
9319
  this.ctx.dispose();
8630
9320
  process.exit(0);
8631
9321
  break;
8632
9322
  // eslint requires break even after process.exit
8633
9323
  default:
8634
- console.log(chalk12.yellow(`Unknown command: ${command}`));
8635
- 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'));
8636
9326
  break;
8637
9327
  }
8638
9328
  }
8639
9329
  // ===== Command Helpers =====
8640
9330
  async switchVault(args) {
8641
9331
  if (args.length === 0) {
8642
- console.log(chalk12.yellow("Usage: vault <name>"));
8643
- 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'));
8644
9334
  return;
8645
9335
  }
8646
9336
  const vaultName = args.join(" ");
8647
9337
  const vault = this.ctx.findVaultByName(vaultName);
8648
9338
  if (!vault) {
8649
- console.log(chalk12.red(`Vault not found: ${vaultName}`));
8650
- 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'));
8651
9341
  return;
8652
9342
  }
8653
9343
  await this.ctx.setActiveVault(vault);
8654
- console.log(chalk12.green(`
9344
+ console.log(chalk13.green(`
8655
9345
  + Switched to: ${vault.name}`));
8656
9346
  const isUnlocked = this.ctx.isVaultUnlocked(vault.id);
8657
- const status = isUnlocked ? chalk12.green("Unlocked") : chalk12.yellow("Locked");
9347
+ const status = isUnlocked ? chalk13.green("Unlocked") : chalk13.yellow("Locked");
8658
9348
  console.log(`Status: ${status}`);
8659
9349
  }
8660
9350
  async importVault(args) {
8661
9351
  if (args.length === 0) {
8662
- console.log(chalk12.yellow("Usage: import <file>"));
9352
+ console.log(chalk13.yellow("Usage: import <file>"));
8663
9353
  return;
8664
9354
  }
8665
9355
  const filePath = args.join(" ");
@@ -8674,45 +9364,45 @@ Error: ${error2.message}`));
8674
9364
  async createVault(args) {
8675
9365
  const type = args[0]?.toLowerCase();
8676
9366
  if (!type || type !== "fast" && type !== "secure") {
8677
- console.log(chalk12.yellow("Usage: create <fast|secure>"));
8678
- console.log(chalk12.gray(" create fast - Create a fast vault (server-assisted 2-of-2)"));
8679
- 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)"));
8680
9370
  return;
8681
9371
  }
8682
9372
  let vault;
8683
9373
  if (type === "fast") {
8684
9374
  const name = await this.prompt("Vault name");
8685
9375
  if (!name) {
8686
- console.log(chalk12.red("Name is required"));
9376
+ console.log(chalk13.red("Name is required"));
8687
9377
  return;
8688
9378
  }
8689
9379
  const password = await this.promptPassword("Vault password");
8690
9380
  if (!password) {
8691
- console.log(chalk12.red("Password is required"));
9381
+ console.log(chalk13.red("Password is required"));
8692
9382
  return;
8693
9383
  }
8694
9384
  const email = await this.prompt("Email for verification");
8695
9385
  if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
8696
- console.log(chalk12.red("Valid email is required"));
9386
+ console.log(chalk13.red("Valid email is required"));
8697
9387
  return;
8698
9388
  }
8699
9389
  vault = await this.withCancellation((signal) => executeCreateFast(this.ctx, { name, password, email, signal }));
8700
9390
  } else {
8701
9391
  const name = await this.prompt("Vault name");
8702
9392
  if (!name) {
8703
- console.log(chalk12.red("Name is required"));
9393
+ console.log(chalk13.red("Name is required"));
8704
9394
  return;
8705
9395
  }
8706
9396
  const sharesStr = await this.prompt("Total shares (devices)", "3");
8707
9397
  const shares = parseInt(sharesStr, 10);
8708
9398
  if (isNaN(shares) || shares < 2) {
8709
- console.log(chalk12.red("Must have at least 2 shares"));
9399
+ console.log(chalk13.red("Must have at least 2 shares"));
8710
9400
  return;
8711
9401
  }
8712
9402
  const thresholdStr = await this.prompt("Signing threshold", "2");
8713
9403
  const threshold = parseInt(thresholdStr, 10);
8714
9404
  if (isNaN(threshold) || threshold < 1 || threshold > shares) {
8715
- console.log(chalk12.red(`Threshold must be between 1 and ${shares}`));
9405
+ console.log(chalk13.red(`Threshold must be between 1 and ${shares}`));
8716
9406
  return;
8717
9407
  }
8718
9408
  const password = await this.promptPassword("Vault password (optional, press Enter to skip)");
@@ -8734,37 +9424,37 @@ Error: ${error2.message}`));
8734
9424
  async importSeedphrase(args) {
8735
9425
  const type = args[0]?.toLowerCase();
8736
9426
  if (!type || type !== "fast" && type !== "secure") {
8737
- console.log(chalk12.cyan("Usage: create-from-seedphrase <fast|secure>"));
8738
- console.log(chalk12.gray(" fast - Import with VultiServer (2-of-2)"));
8739
- 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)"));
8740
9430
  return;
8741
9431
  }
8742
- 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):"));
8743
9433
  const mnemonic = await this.promptPassword("Seedphrase");
8744
9434
  const validation = await this.ctx.sdk.validateSeedphrase(mnemonic);
8745
9435
  if (!validation.valid) {
8746
- console.log(chalk12.red(`Invalid seedphrase: ${validation.error}`));
9436
+ console.log(chalk13.red(`Invalid seedphrase: ${validation.error}`));
8747
9437
  if (validation.invalidWords?.length) {
8748
- console.log(chalk12.yellow(`Invalid words: ${validation.invalidWords.join(", ")}`));
9438
+ console.log(chalk13.yellow(`Invalid words: ${validation.invalidWords.join(", ")}`));
8749
9439
  }
8750
9440
  return;
8751
9441
  }
8752
- console.log(chalk12.green(`\u2713 Valid ${validation.wordCount}-word seedphrase`));
9442
+ console.log(chalk13.green(`\u2713 Valid ${validation.wordCount}-word seedphrase`));
8753
9443
  let vault;
8754
9444
  if (type === "fast") {
8755
9445
  const name = await this.prompt("Vault name");
8756
9446
  if (!name) {
8757
- console.log(chalk12.red("Name is required"));
9447
+ console.log(chalk13.red("Name is required"));
8758
9448
  return;
8759
9449
  }
8760
9450
  const password = await this.promptPassword("Vault password");
8761
9451
  if (!password) {
8762
- console.log(chalk12.red("Password is required"));
9452
+ console.log(chalk13.red("Password is required"));
8763
9453
  return;
8764
9454
  }
8765
9455
  const email = await this.prompt("Email for verification");
8766
9456
  if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
8767
- console.log(chalk12.red("Valid email is required"));
9457
+ console.log(chalk13.red("Valid email is required"));
8768
9458
  return;
8769
9459
  }
8770
9460
  const discoverStr = await this.prompt("Discover chains with balances? (y/n)", "y");
@@ -8782,19 +9472,19 @@ Error: ${error2.message}`));
8782
9472
  } else {
8783
9473
  const name = await this.prompt("Vault name");
8784
9474
  if (!name) {
8785
- console.log(chalk12.red("Name is required"));
9475
+ console.log(chalk13.red("Name is required"));
8786
9476
  return;
8787
9477
  }
8788
9478
  const sharesStr = await this.prompt("Total shares (devices)", "3");
8789
9479
  const shares = parseInt(sharesStr, 10);
8790
9480
  if (isNaN(shares) || shares < 2) {
8791
- console.log(chalk12.red("Must have at least 2 shares"));
9481
+ console.log(chalk13.red("Must have at least 2 shares"));
8792
9482
  return;
8793
9483
  }
8794
9484
  const thresholdStr = await this.prompt("Signing threshold", "2");
8795
9485
  const threshold = parseInt(thresholdStr, 10);
8796
9486
  if (isNaN(threshold) || threshold < 1 || threshold > shares) {
8797
- console.log(chalk12.red(`Threshold must be between 1 and ${shares}`));
9487
+ console.log(chalk13.red(`Threshold must be between 1 and ${shares}`));
8798
9488
  return;
8799
9489
  }
8800
9490
  const password = await this.promptPassword("Vault password (optional, Enter to skip)");
@@ -8838,8 +9528,8 @@ Error: ${error2.message}`));
8838
9528
  }
8839
9529
  }
8840
9530
  if (!fiatCurrencies4.includes(currency)) {
8841
- console.log(chalk12.red(`Invalid currency: ${currency}`));
8842
- 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(", ")}`));
8843
9533
  return;
8844
9534
  }
8845
9535
  const raw = args.includes("--raw");
@@ -8847,7 +9537,7 @@ Error: ${error2.message}`));
8847
9537
  }
8848
9538
  async runSend(args) {
8849
9539
  if (args.length < 3) {
8850
- 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>]"));
8851
9541
  return;
8852
9542
  }
8853
9543
  const [chainStr, to, amount, ...rest] = args;
@@ -8867,7 +9557,7 @@ Error: ${error2.message}`));
8867
9557
  await this.withAbortHandler((signal) => executeSend(this.ctx, { chain, to, amount, tokenId, memo, signal }));
8868
9558
  } catch (err) {
8869
9559
  if (err.message === "Transaction cancelled by user" || err.message === "Operation cancelled" || err.message === "Operation aborted") {
8870
- console.log(chalk12.yellow("\nTransaction cancelled"));
9560
+ console.log(chalk13.yellow("\nTransaction cancelled"));
8871
9561
  return;
8872
9562
  }
8873
9563
  throw err;
@@ -8875,7 +9565,7 @@ Error: ${error2.message}`));
8875
9565
  }
8876
9566
  async runTxStatus(args) {
8877
9567
  if (args.length < 2) {
8878
- console.log(chalk12.yellow("Usage: tx-status <chain> <txHash> [--no-wait]"));
9568
+ console.log(chalk13.yellow("Usage: tx-status <chain> <txHash> [--no-wait]"));
8879
9569
  return;
8880
9570
  }
8881
9571
  const [chainStr, txHash, ...rest] = args;
@@ -8893,8 +9583,8 @@ Error: ${error2.message}`));
8893
9583
  } else if (args[i] === "--add" && i + 1 < args.length) {
8894
9584
  const chain = findChainByName(args[i + 1]);
8895
9585
  if (!chain) {
8896
- console.log(chalk12.red(`Unknown chain: ${args[i + 1]}`));
8897
- 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"));
8898
9588
  return;
8899
9589
  }
8900
9590
  addChain = chain;
@@ -8902,8 +9592,8 @@ Error: ${error2.message}`));
8902
9592
  } else if (args[i] === "--remove" && i + 1 < args.length) {
8903
9593
  const chain = findChainByName(args[i + 1]);
8904
9594
  if (!chain) {
8905
- console.log(chalk12.red(`Unknown chain: ${args[i + 1]}`));
8906
- 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"));
8907
9597
  return;
8908
9598
  }
8909
9599
  removeChain = chain;
@@ -8914,7 +9604,7 @@ Error: ${error2.message}`));
8914
9604
  }
8915
9605
  async runTokens(args) {
8916
9606
  if (args.length === 0) {
8917
- console.log(chalk12.yellow("Usage: tokens <chain> [--add <address>] [--remove <tokenId>]"));
9607
+ console.log(chalk13.yellow("Usage: tokens <chain> [--add <address>] [--remove <tokenId>]"));
8918
9608
  return;
8919
9609
  }
8920
9610
  const chainStr = args[0];
@@ -8935,7 +9625,7 @@ Error: ${error2.message}`));
8935
9625
  async runSwapQuote(args) {
8936
9626
  if (args.length < 3) {
8937
9627
  console.log(
8938
- 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>]")
8939
9629
  );
8940
9630
  return;
8941
9631
  }
@@ -8959,7 +9649,7 @@ Error: ${error2.message}`));
8959
9649
  async runSwap(args) {
8960
9650
  if (args.length < 3) {
8961
9651
  console.log(
8962
- chalk12.yellow(
9652
+ chalk13.yellow(
8963
9653
  "Usage: swap <fromChain> <toChain> <amount> [--from-token <addr>] [--to-token <addr>] [--slippage <pct>]"
8964
9654
  )
8965
9655
  );
@@ -8990,7 +9680,7 @@ Error: ${error2.message}`));
8990
9680
  );
8991
9681
  } catch (err) {
8992
9682
  if (err.message === "Swap cancelled by user" || err.message === "Operation cancelled" || err.message === "Operation aborted") {
8993
- console.log(chalk12.yellow("\nSwap cancelled"));
9683
+ console.log(chalk13.yellow("\nSwap cancelled"));
8994
9684
  return;
8995
9685
  }
8996
9686
  throw err;
@@ -9052,24 +9742,24 @@ Error: ${error2.message}`));
9052
9742
  }
9053
9743
  getPrompt() {
9054
9744
  const vault = this.ctx.getActiveVault();
9055
- if (!vault) return chalk12.cyan("wallet> ");
9745
+ if (!vault) return chalk13.cyan("wallet> ");
9056
9746
  const isUnlocked = this.ctx.isVaultUnlocked(vault.id);
9057
- const status = isUnlocked ? chalk12.green("\u{1F513}") : chalk12.yellow("\u{1F512}");
9058
- 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}> `);
9059
9749
  }
9060
9750
  displayVaultList() {
9061
9751
  const vaults = Array.from(this.ctx.getVaults().values());
9062
9752
  const activeVault = this.ctx.getActiveVault();
9063
9753
  if (vaults.length === 0) {
9064
- 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'));
9065
9755
  return;
9066
9756
  }
9067
- console.log(chalk12.cyan("Loaded Vaults:\n"));
9757
+ console.log(chalk13.cyan("Loaded Vaults:\n"));
9068
9758
  vaults.forEach((vault) => {
9069
9759
  const isActive = vault.id === activeVault?.id;
9070
9760
  const isUnlocked = this.ctx.isVaultUnlocked(vault.id);
9071
- const activeMarker = isActive ? chalk12.green(" (active)") : "";
9072
- 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}");
9073
9763
  console.log(` ${lockIcon} ${vault.name}${activeMarker} - ${vault.type}`);
9074
9764
  });
9075
9765
  console.log();
@@ -9077,150 +9767,7 @@ Error: ${error2.message}`));
9077
9767
  };
9078
9768
 
9079
9769
  // src/lib/errors.ts
9080
- import chalk13 from "chalk";
9081
-
9082
- // src/lib/version.ts
9083
9770
  import chalk14 from "chalk";
9084
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync2 } from "fs";
9085
- import { homedir as homedir3 } from "os";
9086
- import { join as join3 } from "path";
9087
- var cachedVersion = null;
9088
- function getVersion() {
9089
- if (cachedVersion) return cachedVersion;
9090
- if (true) {
9091
- cachedVersion = "0.15.4";
9092
- return cachedVersion;
9093
- }
9094
- try {
9095
- const packagePath = new URL("../../package.json", import.meta.url);
9096
- const pkg = JSON.parse(readFileSync3(packagePath, "utf-8"));
9097
- cachedVersion = pkg.version;
9098
- return cachedVersion;
9099
- } catch {
9100
- cachedVersion = "0.0.0-unknown";
9101
- return cachedVersion;
9102
- }
9103
- }
9104
- var CACHE_DIR = join3(homedir3(), ".vultisig", "cache");
9105
- var VERSION_CACHE_FILE = join3(CACHE_DIR, "version-check.json");
9106
- var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
9107
- function readVersionCache() {
9108
- try {
9109
- if (!existsSync2(VERSION_CACHE_FILE)) return null;
9110
- const data = readFileSync3(VERSION_CACHE_FILE, "utf-8");
9111
- return JSON.parse(data);
9112
- } catch {
9113
- return null;
9114
- }
9115
- }
9116
- function writeVersionCache(cache) {
9117
- try {
9118
- if (!existsSync2(CACHE_DIR)) {
9119
- mkdirSync3(CACHE_DIR, { recursive: true });
9120
- }
9121
- writeFileSync3(VERSION_CACHE_FILE, JSON.stringify(cache, null, 2));
9122
- } catch {
9123
- }
9124
- }
9125
- async function fetchLatestVersion() {
9126
- try {
9127
- const controller = new AbortController();
9128
- const timeout = setTimeout(() => controller.abort(), 5e3);
9129
- const response = await fetch("https://registry.npmjs.org/@vultisig/cli/latest", {
9130
- signal: controller.signal,
9131
- headers: {
9132
- Accept: "application/json"
9133
- }
9134
- });
9135
- clearTimeout(timeout);
9136
- if (!response.ok) return null;
9137
- const data = await response.json();
9138
- return data.version ?? null;
9139
- } catch {
9140
- return null;
9141
- }
9142
- }
9143
- function isNewerVersion(v1, v2) {
9144
- const parse = (v) => {
9145
- const clean2 = v.replace(/^v/, "").replace(/-.*$/, "");
9146
- return clean2.split(".").map((n) => parseInt(n, 10) || 0);
9147
- };
9148
- const p1 = parse(v1);
9149
- const p2 = parse(v2);
9150
- for (let i = 0; i < 3; i++) {
9151
- const n1 = p1[i] ?? 0;
9152
- const n2 = p2[i] ?? 0;
9153
- if (n2 > n1) return true;
9154
- if (n2 < n1) return false;
9155
- }
9156
- if (v1.includes("-") && !v2.includes("-")) return true;
9157
- return false;
9158
- }
9159
- async function checkForUpdates() {
9160
- if (process.env.VULTISIG_NO_UPDATE_CHECK === "1") {
9161
- return null;
9162
- }
9163
- const currentVersion = getVersion();
9164
- const cache = readVersionCache();
9165
- if (cache && Date.now() - cache.lastCheck < CACHE_TTL_MS) {
9166
- return {
9167
- currentVersion,
9168
- latestVersion: cache.latestVersion,
9169
- updateAvailable: cache.latestVersion ? isNewerVersion(currentVersion, cache.latestVersion) : false
9170
- };
9171
- }
9172
- const latestVersion = await fetchLatestVersion();
9173
- writeVersionCache({
9174
- lastCheck: Date.now(),
9175
- latestVersion
9176
- });
9177
- return {
9178
- currentVersion,
9179
- latestVersion,
9180
- updateAvailable: latestVersion ? isNewerVersion(currentVersion, latestVersion) : false
9181
- };
9182
- }
9183
- function formatVersionShort() {
9184
- return `vultisig/${getVersion()}`;
9185
- }
9186
- function formatVersionDetailed() {
9187
- const lines = [];
9188
- lines.push(chalk14.bold(`Vultisig CLI v${getVersion()}`));
9189
- lines.push("");
9190
- lines.push(` Node.js: ${process.version}`);
9191
- lines.push(` Platform: ${process.platform}-${process.arch}`);
9192
- lines.push(` Config: ~/.vultisig/`);
9193
- return lines.join("\n");
9194
- }
9195
- function detectInstallMethod() {
9196
- const execPath = process.execPath;
9197
- if (execPath.includes("homebrew") || execPath.includes("Cellar")) {
9198
- return "homebrew";
9199
- }
9200
- if (process.env.npm_execpath?.includes("yarn")) {
9201
- return "yarn";
9202
- }
9203
- if (process.env.npm_config_user_agent?.includes("npm")) {
9204
- return "npm";
9205
- }
9206
- if (execPath.includes("node_modules")) {
9207
- return "npm";
9208
- }
9209
- return "unknown";
9210
- }
9211
- function getUpdateCommand() {
9212
- const method = detectInstallMethod();
9213
- switch (method) {
9214
- case "npm":
9215
- return "npm update -g @vultisig/cli";
9216
- case "yarn":
9217
- return "yarn global upgrade @vultisig/cli";
9218
- case "homebrew":
9219
- return "brew upgrade vultisig";
9220
- default:
9221
- return "npm update -g @vultisig/cli";
9222
- }
9223
- }
9224
9771
 
9225
9772
  // src/lib/completion.ts
9226
9773
  import { homedir as homedir4 } from "os";
@@ -9561,9 +10108,32 @@ setupUserAgent();
9561
10108
  if (handled) process.exit(0);
9562
10109
  })();
9563
10110
  var ctx;
9564
- 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) => {
9565
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
+ }
9566
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
+ );
9567
10137
  });
9568
10138
  async function findVaultByNameOrId(sdk, nameOrId) {
9569
10139
  const vaults = await sdk.listVaults();
@@ -9607,7 +10177,15 @@ async function init(vaultOverride, unlockPassword, passwordTTL) {
9607
10177
  return ctx;
9608
10178
  }
9609
10179
  var createCmd = program.command("create").description("Create a vault");
9610
- 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(
9611
10189
  withExit(async (options) => {
9612
10190
  const context = await init(program.opts().vault);
9613
10191
  await executeCreateFast(context, { ...options, twoStep: options.twoStep });
@@ -9633,7 +10211,13 @@ program.command("add-mldsa").description("Add ML-DSA (post-quantum) keys to the
9633
10211
  });
9634
10212
  })
9635
10213
  );
9636
- 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(
9637
10221
  withExit(async (file, options) => {
9638
10222
  const context = await init(program.opts().vault);
9639
10223
  await executeImport(context, file, options.password);
@@ -9641,6 +10225,7 @@ program.command("import <file>").description("Import vault from .vult file").opt
9641
10225
  );
9642
10226
  var createFromSeedphraseCmd = program.command("create-from-seedphrase").description("Create vault from BIP39 seedphrase");
9643
10227
  async function promptSeedphrase() {
10228
+ requireInteractive("Use --mnemonic flag to provide seedphrase non-interactively.");
9644
10229
  info("\nEnter your 12 or 24-word recovery phrase.");
9645
10230
  info("Words will be hidden as you type.\n");
9646
10231
  const answer = await inquirer8.prompt([
@@ -9661,6 +10246,7 @@ async function promptSeedphrase() {
9661
10246
  return answer.mnemonic.trim().toLowerCase();
9662
10247
  }
9663
10248
  async function promptQrPayload() {
10249
+ requireInteractive("Use --qr or --qr-file flag to provide QR payload non-interactively.");
9664
10250
  info("\nEnter the QR code payload from the initiator device.");
9665
10251
  info('The payload starts with "vultisig://".\n');
9666
10252
  const answer = await inquirer8.prompt([
@@ -9786,7 +10372,15 @@ program.command("verify <vaultId>").description("Verify vault with email verific
9786
10372
  }
9787
10373
  )
9788
10374
  );
9789
- 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(
9790
10384
  withExit(async (chainStr, options) => {
9791
10385
  const context = await init(program.opts().vault);
9792
10386
  await executeBalance(context, {
@@ -9796,7 +10390,20 @@ program.command("balance [chain]").description("Show balance for a chain or all
9796
10390
  });
9797
10391
  })
9798
10392
  );
9799
- 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(
9800
10407
  withExit(
9801
10408
  async (chainStr, to, amount, options) => {
9802
10409
  if (!amount && !options.max) throw new Error("Provide an amount or use --max");
@@ -9809,7 +10416,8 @@ program.command("send <chain> <to> [amount]").description("Send tokens to an add
9809
10416
  amount: amount ?? "max",
9810
10417
  tokenId: options.token,
9811
10418
  memo: options.memo,
9812
- yes: options.yes,
10419
+ dryRun: options.dryRun,
10420
+ yes: options.yes || options.confirm,
9813
10421
  password: options.password
9814
10422
  });
9815
10423
  } catch (err) {
@@ -9822,7 +10430,13 @@ program.command("send <chain> <to> [amount]").description("Send tokens to an add
9822
10430
  }
9823
10431
  )
9824
10432
  );
9825
- 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(
9826
10440
  withExit(
9827
10441
  async (chainStr, contract, msg, options) => {
9828
10442
  const context = await init(program.opts().vault, options.password);
@@ -9833,6 +10447,7 @@ program.command("execute <chain> <contract> <msg>").description("Execute a CosmW
9833
10447
  msg,
9834
10448
  funds: options.funds,
9835
10449
  memo: options.memo,
10450
+ dryRun: options.dryRun,
9836
10451
  yes: options.yes,
9837
10452
  password: options.password
9838
10453
  });
@@ -9865,7 +10480,13 @@ program.command("broadcast").description("Broadcast a pre-signed raw transaction
9865
10480
  });
9866
10481
  })
9867
10482
  );
9868
- 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(
9869
10490
  withExit(async (options) => {
9870
10491
  const context = await init(program.opts().vault);
9871
10492
  await executeTxStatus(context, {
@@ -9875,7 +10496,13 @@ program.command("tx-status").description("Check the status of a transaction (pol
9875
10496
  });
9876
10497
  })
9877
10498
  );
9878
- 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(
9879
10506
  withExit(async (options) => {
9880
10507
  const context = await init(program.opts().vault);
9881
10508
  await executePortfolio(context, {
@@ -9902,7 +10529,13 @@ program.command("discount").description("Show your VULT discount tier for swap f
9902
10529
  await executeDiscount(context, { refresh: options.refresh });
9903
10530
  })
9904
10531
  );
9905
- 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(
9906
10539
  withExit(async (path4, options) => {
9907
10540
  const context = await init(program.opts().vault, options.password);
9908
10541
  await executeExport(context, {
@@ -9912,7 +10545,13 @@ program.command("export [path]").description("Export vault to file").option("--p
9912
10545
  });
9913
10546
  })
9914
10547
  );
9915
- 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(
9916
10555
  withExit(async () => {
9917
10556
  const context = await init(program.opts().vault);
9918
10557
  await executeAddresses(context);
@@ -9930,7 +10569,14 @@ program.command("address-book").description("Manage address book entries").optio
9930
10569
  });
9931
10570
  })
9932
10571
  );
9933
- 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(
9934
10580
  withExit(async (options) => {
9935
10581
  const context = await init(program.opts().vault);
9936
10582
  await executeChains(context, {
@@ -9940,7 +10586,13 @@ program.command("chains").description("List and manage chains").option("--add <c
9940
10586
  });
9941
10587
  })
9942
10588
  );
9943
- 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(
9944
10596
  withExit(async () => {
9945
10597
  const context = await init(program.opts().vault);
9946
10598
  await executeVaults(context);
@@ -9958,7 +10610,7 @@ program.command("rename <newName>").description("Rename the active vault").actio
9958
10610
  await executeRename(context, newName);
9959
10611
  })
9960
10612
  );
9961
- program.command("info").description("Show detailed vault information").action(
10613
+ program.command("info").description(descriptions.vaultInfo.description).action(
9962
10614
  withExit(async () => {
9963
10615
  const context = await init(program.opts().vault);
9964
10616
  await executeInfo(context);
@@ -9973,7 +10625,14 @@ program.command("delete [vault]").description("Delete a vault from local storage
9973
10625
  });
9974
10626
  })
9975
10627
  );
9976
- 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(
9977
10636
  withExit(
9978
10637
  async (chainStr, options) => {
9979
10638
  const context = await init(program.opts().vault);
@@ -9981,6 +10640,7 @@ program.command("tokens <chain>").description("List and manage tokens for a chai
9981
10640
  chain: findChainByName(chainStr) || chainStr,
9982
10641
  add: options.add,
9983
10642
  remove: options.remove,
10643
+ discover: options.discover,
9984
10644
  symbol: options.symbol,
9985
10645
  name: options.name,
9986
10646
  decimals: options.decimals ? parseInt(options.decimals, 10) : void 0
@@ -9988,13 +10648,19 @@ program.command("tokens <chain>").description("List and manage tokens for a chai
9988
10648
  }
9989
10649
  )
9990
10650
  );
9991
- program.command("swap-chains").description("List chains that support swaps").action(
10651
+ program.command("swap-chains").description(descriptions.supportedChains.description).action(
9992
10652
  withExit(async () => {
9993
10653
  const context = await init(program.opts().vault);
9994
10654
  await executeSwapChains(context);
9995
10655
  })
9996
10656
  );
9997
- 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(
9998
10664
  withExit(
9999
10665
  async (fromChainStr, toChainStr, amountStr, options) => {
10000
10666
  if (!amountStr && !options.max) throw new Error("Provide an amount or use --max");
@@ -10010,7 +10676,16 @@ program.command("swap-quote <fromChain> <toChain> [amount]").description("Get a
10010
10676
  }
10011
10677
  )
10012
10678
  );
10013
- 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(
10014
10689
  withExit(
10015
10690
  async (fromChainStr, toChainStr, amountStr, options) => {
10016
10691
  if (!amountStr && !options.max) throw new Error("Provide an amount or use --max");
@@ -10024,7 +10699,8 @@ program.command("swap <fromChain> <toChain> [amount]").description("Swap tokens
10024
10699
  fromToken: options.fromToken,
10025
10700
  toToken: options.toToken,
10026
10701
  slippage: options.slippage ? parseFloat(options.slippage) : void 0,
10027
- yes: options.yes,
10702
+ dryRun: options.dryRun,
10703
+ yes: options.yes || options.confirm,
10028
10704
  password: options.password
10029
10705
  });
10030
10706
  } catch (err) {
@@ -10068,7 +10744,7 @@ rujiraCmd.command("deposit").description("Show deposit instructions (inbound add
10068
10744
  }
10069
10745
  )
10070
10746
  );
10071
- 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(
10072
10748
  withExit(
10073
10749
  async (fromAsset, toAsset, amount, options) => {
10074
10750
  const context = await init(program.opts().vault, options.password);
@@ -10078,6 +10754,7 @@ rujiraCmd.command("swap <fromAsset> <toAsset> <amount>").description("Execute a
10078
10754
  amount,
10079
10755
  slippageBps: options.slippageBps ? parseInt(options.slippageBps, 10) : void 0,
10080
10756
  destination: options.destination,
10757
+ dryRun: options.dryRun,
10081
10758
  yes: options.yes,
10082
10759
  password: options.password,
10083
10760
  rpcEndpoint: options.rpc,
@@ -10086,7 +10763,7 @@ rujiraCmd.command("swap <fromAsset> <toAsset> <amount>").description("Execute a
10086
10763
  }
10087
10764
  )
10088
10765
  );
10089
- 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(
10090
10767
  withExit(
10091
10768
  async (asset, amount, l1Address, options) => {
10092
10769
  const context = await init(program.opts().vault, options.password);
@@ -10095,6 +10772,7 @@ rujiraCmd.command("withdraw <asset> <amount> <l1Address>").description("Withdraw
10095
10772
  amount,
10096
10773
  l1Address,
10097
10774
  maxFeeBps: options.maxFeeBps ? parseInt(options.maxFeeBps, 10) : void 0,
10775
+ dryRun: options.dryRun,
10098
10776
  yes: options.yes,
10099
10777
  password: options.password,
10100
10778
  rpcEndpoint: options.rpc,
@@ -10129,7 +10807,13 @@ var agentCmd = program.command("agent").description("AI-powered chat interface f
10129
10807
  });
10130
10808
  }
10131
10809
  );
10132
- 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(
10133
10817
  async (message, options) => {
10134
10818
  const parentOpts = agentCmd.opts();
10135
10819
  const context = await init(program.opts().vault, options.password || parentOpts.password);
@@ -10197,7 +10881,66 @@ program.command("update").description("Check for updates and show update command
10197
10881
  }
10198
10882
  })
10199
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
+ );
10200
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)));
10201
10944
  async function startInteractiveMode() {
10202
10945
  const serverEndpoints = resolveServerEndpoints(parseServerEndpointOverridesFromArgv(process.argv.slice(2)));
10203
10946
  const sdk = new Vultisig6({