@vultisig/cli 2.19.7 → 2.19.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -0
- package/README.md +37 -0
- package/dist/index.js +312 -229
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,61 @@
|
|
|
1
1
|
# @vultisig/cli
|
|
2
2
|
|
|
3
|
+
## 2.19.11
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#1129](https://github.com/vultisig/vultisig-sdk/pull/1129) [`5949742`](https://github.com/vultisig/vultisig-sdk/commit/59497426a238a75576e92d18023747d66c9d4e7a) Thanks [@neavra](https://github.com/neavra)! - fix(portfolio): report per-chain failures instead of silently swallowing them. The `portfolio` command now fetches each chain independently — one unreachable chain no longer fails the whole command, and a fiat-value lookup failure no longer silently drops the value. The `-o json` envelope always carries a `failures: [{ chain, stage, error }]` array (empty on full success), partial failures still exit 0, and an all-chains-failed run exits with a network error (code 3).
|
|
8
|
+
|
|
9
|
+
- Updated dependencies [[`3bf18a1`](https://github.com/vultisig/vultisig-sdk/commit/3bf18a18606fd1b45d50abb562eb6c3011182d48)]:
|
|
10
|
+
- @vultisig/sdk@2.19.11
|
|
11
|
+
|
|
12
|
+
## 2.19.10
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- [#1076](https://github.com/vultisig/vultisig-sdk/pull/1076) [`5179edd`](https://github.com/vultisig/vultisig-sdk/commit/5179edd9eb25b392941f9649a455e92bfd5bf8b5) Thanks [@neavra](https://github.com/neavra)! - fix(chains): validate `chains --add <chain>` against the registry before persisting
|
|
17
|
+
|
|
18
|
+
An invalid `chains --add fakechain` correctly reported INVALID_CHAIN but still wrote
|
|
19
|
+
"fakechain" to the vault's chain list (chain resolution falls back to the raw user
|
|
20
|
+
string when the name doesn't match the registry). Every subsequent address-deriving
|
|
21
|
+
command then re-derived the bogus chain and dumped a stack trace to stderr until it was
|
|
22
|
+
manually removed. The `--add` path now validates against the supported-chain registry
|
|
23
|
+
first and fails closed — nothing is persisted for an unsupported chain.
|
|
24
|
+
|
|
25
|
+
- [#1127](https://github.com/vultisig/vultisig-sdk/pull/1127) [`dea1efa`](https://github.com/vultisig/vultisig-sdk/commit/dea1efae6fbd2975de4ac5a26b2e8210999a0f4f) Thanks [@neavra](https://github.com/neavra)! - fix(cli): honor VULTISIG_CONFIG_DIR for vault storage
|
|
26
|
+
|
|
27
|
+
The documented `VULTISIG_CONFIG_DIR` env var was a no-op for vault storage: the
|
|
28
|
+
CLI constructed the SDK without a storage override, so vaults, the active-vault
|
|
29
|
+
pointer and cache always resolved to `~/.vultisig` even when the var pointed
|
|
30
|
+
elsewhere — only `config.json` and the agent journal honored it, producing a
|
|
31
|
+
split-brain config location that broke CI/container isolation and multi-tenant
|
|
32
|
+
use. The CLI now roots SDK vault storage at `getConfigDir()` via a shared
|
|
33
|
+
`createVaultStorage()` helper. When the var is unset it falls back to
|
|
34
|
+
`~/.vultisig`, so the default location is unchanged.
|
|
35
|
+
|
|
36
|
+
- [#1126](https://github.com/vultisig/vultisig-sdk/pull/1126) [`6054ff5`](https://github.com/vultisig/vultisig-sdk/commit/6054ff599e4133c9853f31e8ca2413ab52f606fb) Thanks [@neavra](https://github.com/neavra)! - fix(mpc): keep keygen tracing off stdout so `-o json` output stays parseable
|
|
37
|
+
|
|
38
|
+
The DKLS and Schnorr keygen/reshare/key-import ceremonies logged progress
|
|
39
|
+
(session ids, raw wire messages, "keygen complete", …) to stdout via ungated
|
|
40
|
+
`console.log`. stdout is the machine channel for the CLI's `-o json` mode, so
|
|
41
|
+
the documented `create fast … -o json` agent flow produced unparseable stdout
|
|
42
|
+
(`JSON.parse(stdout)` failed on the leading garbage) and leaked MPC internals
|
|
43
|
+
into terminals and CI logs.
|
|
44
|
+
|
|
45
|
+
Route that tracing through a gated logger that writes to stderr only when
|
|
46
|
+
`VULTISIG_DEBUG=1`, so stdout carries only the final JSON envelope while
|
|
47
|
+
the debug output stays available to humans on demand. No keygen behavior
|
|
48
|
+
changes — only the log sink moves off stdout.
|
|
49
|
+
|
|
50
|
+
- [#1077](https://github.com/vultisig/vultisig-sdk/pull/1077) [`5e5494c`](https://github.com/vultisig/vultisig-sdk/commit/5e5494cf103ea5c17431b6a6231b30931eb004bb) Thanks [@neavra](https://github.com/neavra)! - Tolerate a corrupt `activeVaultId.json` at startup. A truncated or unparseable
|
|
51
|
+
active-vault pointer used to throw during CLI initialization, which broke every
|
|
52
|
+
command — including `vultisig vaults`, the one you run to recover. The pointer
|
|
53
|
+
read now fails open (treated as "no active vault") and self-heals by clearing
|
|
54
|
+
the bad pointer, so vaults still list with none marked active.
|
|
55
|
+
- Updated dependencies [[`280956f`](https://github.com/vultisig/vultisig-sdk/commit/280956f1743564ea271a03c4735f70151b63f161), [`90070f3`](https://github.com/vultisig/vultisig-sdk/commit/90070f39be011821f7508c7ff094025861dce040), [`0e3f86d`](https://github.com/vultisig/vultisig-sdk/commit/0e3f86db82ed086b1e200a6f83434a638f593c17), [`6054ff5`](https://github.com/vultisig/vultisig-sdk/commit/6054ff599e4133c9853f31e8ca2413ab52f606fb), [`b37e726`](https://github.com/vultisig/vultisig-sdk/commit/b37e7264db3291adca1cb366f1311446d6add439), [`1b2636b`](https://github.com/vultisig/vultisig-sdk/commit/1b2636b535736a711fc537a87d2f51090fe6342d), [`2c9d34e`](https://github.com/vultisig/vultisig-sdk/commit/2c9d34e0837f68d92769c7aefa566ffb1c0c52c7), [`ffc75a6`](https://github.com/vultisig/vultisig-sdk/commit/ffc75a6e76af699a78b0fc3411ab052ce5000c91), [`fc595a1`](https://github.com/vultisig/vultisig-sdk/commit/fc595a17cb4901af1dfeac2521b93bb75823068f), [`776c539`](https://github.com/vultisig/vultisig-sdk/commit/776c5398764314f140f18ab4f86a1801fe9fdfe6)]:
|
|
56
|
+
- @vultisig/sdk@2.19.10
|
|
57
|
+
- @vultisig/core-chain@2.24.2
|
|
58
|
+
|
|
3
59
|
## 2.19.7
|
|
4
60
|
|
|
5
61
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1153,6 +1153,43 @@ Configuration is stored in `~/.vultisig/`:
|
|
|
1153
1153
|
> truth) and are covered by a doc-lint test that fails if this table drifts from the code. Run
|
|
1154
1154
|
> `vultisig --help` for the same list.
|
|
1155
1155
|
|
|
1156
|
+
### Partial failures (`portfolio`)
|
|
1157
|
+
|
|
1158
|
+
The `portfolio` command fetches every chain independently, so one unreachable chain no longer
|
|
1159
|
+
fails the whole command. The `-o json` envelope always carries a `failures` array (empty when
|
|
1160
|
+
everything succeeded):
|
|
1161
|
+
|
|
1162
|
+
```jsonc
|
|
1163
|
+
{
|
|
1164
|
+
"success": true,
|
|
1165
|
+
"v": 1,
|
|
1166
|
+
"data": {
|
|
1167
|
+
"portfolio": { "totalValue": { ... }, "chainBalances": [ /* only the chains that loaded */ ] },
|
|
1168
|
+
"currency": "usd",
|
|
1169
|
+
"failures": [
|
|
1170
|
+
{ "chain": "Bitcoin", "stage": "balance", "error": "ECONNREFUSED btc-rpc" },
|
|
1171
|
+
{ "chain": "Ethereum", "stage": "value", "error": "pricing service unavailable" }
|
|
1172
|
+
]
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
```
|
|
1176
|
+
|
|
1177
|
+
- `stage: "balance"` — the balance fetch failed; the chain is omitted from `chainBalances`.
|
|
1178
|
+
- `stage: "value"` — the balance loaded but its fiat value did not; the chain still appears in
|
|
1179
|
+
`chainBalances` (without a `value`) and is also listed here.
|
|
1180
|
+
- `error` is a concise single-line message — never a stack trace or filesystem path.
|
|
1181
|
+
|
|
1182
|
+
**Partial-success exit contract:** if _some_ chains loaded, the command exits **0** and reports
|
|
1183
|
+
the rest under `failures`. Machine consumers should branch on `data.failures.length`, not `$?`.
|
|
1184
|
+
If _every_ chain fails to fetch a balance, the command exits **3** (network error, retryable).
|
|
1185
|
+
On the human-readable (table) output, failures are printed as `Warning:` lines below the table.
|
|
1186
|
+
|
|
1187
|
+
> **Note on `totalValue`:** `failures` describes the per-chain _breakdown_ pass (`chainBalances`).
|
|
1188
|
+
> `portfolio.totalValue` is computed by an independent best-effort aggregate that includes token
|
|
1189
|
+
> values (not just native) and silently omits any chain/token it could not price. It is therefore
|
|
1190
|
+
> not guaranteed to be consistent with `chainBalances`/`failures` — treat it as an approximate
|
|
1191
|
+
> total, and rely on `failures` (not the total) to detect which chains had problems.
|
|
1192
|
+
|
|
1156
1193
|
## Troubleshooting
|
|
1157
1194
|
|
|
1158
1195
|
### "No active vault" error
|
package/dist/index.js
CHANGED
|
@@ -5839,6 +5839,10 @@ async function confirmSwap() {
|
|
|
5839
5839
|
}
|
|
5840
5840
|
|
|
5841
5841
|
// src/commands/balance.ts
|
|
5842
|
+
function conciseError(err) {
|
|
5843
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5844
|
+
return message.split("\n")[0].trim() || "Unknown error";
|
|
5845
|
+
}
|
|
5842
5846
|
async function executeBalance(ctx2, options = {}) {
|
|
5843
5847
|
const vault = await ctx2.ensureActiveVault();
|
|
5844
5848
|
const spinner = createSpinner("Loading balances...");
|
|
@@ -5876,257 +5880,82 @@ async function executePortfolio(ctx2, options = {}) {
|
|
|
5876
5880
|
const spinner = createSpinner(`Loading portfolio in ${currencyName}...`);
|
|
5877
5881
|
const totalValue = await vault.getTotalValue(currency);
|
|
5878
5882
|
const chains = vault.chains;
|
|
5879
|
-
const
|
|
5883
|
+
const results = await Promise.all(
|
|
5880
5884
|
chains.map(async (chain) => {
|
|
5881
|
-
|
|
5885
|
+
let balance;
|
|
5886
|
+
try {
|
|
5887
|
+
balance = await vault.balance(chain);
|
|
5888
|
+
} catch (err) {
|
|
5889
|
+
return { failure: { chain, stage: "balance", error: conciseError(err) } };
|
|
5890
|
+
}
|
|
5882
5891
|
try {
|
|
5883
5892
|
const value = await vault.getValue(chain, void 0, currency);
|
|
5884
|
-
return { chain, balance, value };
|
|
5885
|
-
} catch {
|
|
5886
|
-
return { chain, balance };
|
|
5893
|
+
return { entry: { chain, balance, value } };
|
|
5894
|
+
} catch (err) {
|
|
5895
|
+
return { entry: { chain, balance }, failure: { chain, stage: "value", error: conciseError(err) } };
|
|
5887
5896
|
}
|
|
5888
5897
|
})
|
|
5889
5898
|
);
|
|
5899
|
+
const chainBalances = [];
|
|
5900
|
+
const failures = [];
|
|
5901
|
+
for (const result of results) {
|
|
5902
|
+
if (result.entry) chainBalances.push(result.entry);
|
|
5903
|
+
if (result.failure) failures.push(result.failure);
|
|
5904
|
+
}
|
|
5905
|
+
if (failures.length > 0 && chainBalances.length === 0) {
|
|
5906
|
+
spinner.fail("Portfolio failed to load");
|
|
5907
|
+
throw new NetworkError(
|
|
5908
|
+
`Failed to load balances for all ${failures.length} chain(s): ${failures.map((f) => `${f.chain} (${f.error})`).join("; ")}`,
|
|
5909
|
+
"All chain balance fetches failed \u2014 likely a network/RPC issue",
|
|
5910
|
+
["Check your internet connection", "Retry in a few moments"]
|
|
5911
|
+
);
|
|
5912
|
+
}
|
|
5890
5913
|
const portfolio = { totalValue, chainBalances };
|
|
5891
5914
|
spinner.succeed("Portfolio loaded");
|
|
5892
5915
|
if (isJsonOutput()) {
|
|
5893
|
-
outputJson({ portfolio, currency });
|
|
5916
|
+
outputJson({ portfolio, currency, failures });
|
|
5894
5917
|
return;
|
|
5895
5918
|
}
|
|
5896
5919
|
displayPortfolio(portfolio, currency, options.raw ?? false);
|
|
5920
|
+
if (failures.length > 0) {
|
|
5921
|
+
warn(`
|
|
5922
|
+
Warning: ${failures.length} chain(s) failed to load fully:`);
|
|
5923
|
+
for (const f of failures) {
|
|
5924
|
+
warn(` - ${f.chain} (${f.stage}): ${f.error}`);
|
|
5925
|
+
}
|
|
5926
|
+
}
|
|
5897
5927
|
}
|
|
5898
5928
|
|
|
5899
5929
|
// src/commands/chains.ts
|
|
5900
5930
|
import { SUPPORTED_CHAINS } from "@vultisig/sdk";
|
|
5901
5931
|
import chalk3 from "chalk";
|
|
5902
|
-
async function executeChains(ctx2, options = {}) {
|
|
5903
|
-
const vault = await ctx2.ensureActiveVault();
|
|
5904
|
-
if (options.addAll) {
|
|
5905
|
-
const currentCount = vault.chains.length;
|
|
5906
|
-
const spinner = createSpinner(`Adding all ${SUPPORTED_CHAINS.length} supported chains...`);
|
|
5907
|
-
await vault.setChains([...SUPPORTED_CHAINS]);
|
|
5908
|
-
const addedCount = SUPPORTED_CHAINS.length - currentCount;
|
|
5909
|
-
spinner.succeed(`Added ${addedCount} chains (${SUPPORTED_CHAINS.length} total)`);
|
|
5910
|
-
if (isJsonOutput()) {
|
|
5911
|
-
outputJson({ chains: [...vault.chains], added: addedCount, total: SUPPORTED_CHAINS.length });
|
|
5912
|
-
return;
|
|
5913
|
-
}
|
|
5914
|
-
info(chalk3.gray("\nAll supported chains are now enabled."));
|
|
5915
|
-
return;
|
|
5916
|
-
}
|
|
5917
|
-
if (options.add) {
|
|
5918
|
-
const alreadyActive = vault.chains.includes(options.add);
|
|
5919
|
-
if (!alreadyActive) {
|
|
5920
|
-
await vault.addChain(options.add);
|
|
5921
|
-
}
|
|
5922
|
-
const address = await vault.address(options.add);
|
|
5923
|
-
if (isJsonOutput()) {
|
|
5924
|
-
outputJson({ chain: options.add, alreadyActive, address, chains: [...vault.chains] });
|
|
5925
|
-
return;
|
|
5926
|
-
}
|
|
5927
|
-
success(alreadyActive ? `
|
|
5928
|
-
Chain already active: ${options.add}` : `
|
|
5929
|
-
+ Added chain: ${options.add}`);
|
|
5930
|
-
info(`Address: ${address}`);
|
|
5931
|
-
} else if (options.remove) {
|
|
5932
|
-
await vault.removeChain(options.remove);
|
|
5933
|
-
if (isJsonOutput()) {
|
|
5934
|
-
outputJson({ chain: options.remove, removed: true, chains: [...vault.chains] });
|
|
5935
|
-
return;
|
|
5936
|
-
}
|
|
5937
|
-
success(`
|
|
5938
|
-
+ Removed chain: ${options.remove}`);
|
|
5939
|
-
} else {
|
|
5940
|
-
const chains = vault.chains;
|
|
5941
|
-
if (isJsonOutput()) {
|
|
5942
|
-
outputJson({ chains: [...chains] });
|
|
5943
|
-
return;
|
|
5944
|
-
}
|
|
5945
|
-
printResult(chalk3.cyan("\nActive Chains:\n"));
|
|
5946
|
-
chains.forEach((chain) => {
|
|
5947
|
-
printResult(` - ${chain}`);
|
|
5948
|
-
});
|
|
5949
|
-
info(chalk3.gray(`
|
|
5950
|
-
${chains.length} of ${SUPPORTED_CHAINS.length} chains enabled`));
|
|
5951
|
-
info(chalk3.gray("Use --add <chain>, --add-all, or --remove <chain>"));
|
|
5952
|
-
}
|
|
5953
|
-
}
|
|
5954
|
-
async function executeAddresses(ctx2) {
|
|
5955
|
-
const vault = await ctx2.ensureActiveVault();
|
|
5956
|
-
const spinner = createSpinner("Loading addresses...");
|
|
5957
|
-
const addresses = await vault.addresses();
|
|
5958
|
-
spinner.succeed("Addresses loaded");
|
|
5959
|
-
if (isJsonOutput()) {
|
|
5960
|
-
outputJson({ addresses });
|
|
5961
|
-
return;
|
|
5962
|
-
}
|
|
5963
|
-
displayAddresses(addresses);
|
|
5964
|
-
}
|
|
5965
5932
|
|
|
5966
|
-
// src/
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
await ctx2.ensureActiveVault();
|
|
5971
|
-
if (options.discover) {
|
|
5972
|
-
await discoverTokens(ctx2, options.chain);
|
|
5973
|
-
return;
|
|
5974
|
-
}
|
|
5975
|
-
if (options.add) {
|
|
5976
|
-
let symbol = options.symbol;
|
|
5977
|
-
let name = options.name;
|
|
5978
|
-
let decimals = options.decimals;
|
|
5979
|
-
const prompts = [];
|
|
5980
|
-
if (!symbol) {
|
|
5981
|
-
prompts.push({
|
|
5982
|
-
type: "input",
|
|
5983
|
-
name: "symbol",
|
|
5984
|
-
message: "Enter token symbol (e.g., USDT):",
|
|
5985
|
-
validate: (input) => input.trim() !== "" || "Symbol is required"
|
|
5986
|
-
});
|
|
5987
|
-
}
|
|
5988
|
-
if (!name) {
|
|
5989
|
-
prompts.push({
|
|
5990
|
-
type: "input",
|
|
5991
|
-
name: "name",
|
|
5992
|
-
message: "Enter token name (e.g., Tether USD):",
|
|
5993
|
-
validate: (input) => input.trim() !== "" || "Name is required"
|
|
5994
|
-
});
|
|
5995
|
-
}
|
|
5996
|
-
if (decimals === void 0) {
|
|
5997
|
-
prompts.push({
|
|
5998
|
-
type: "number",
|
|
5999
|
-
name: "decimals",
|
|
6000
|
-
message: "Enter token decimals:",
|
|
6001
|
-
default: 18,
|
|
6002
|
-
validate: (input) => input >= 0 || "Decimals must be non-negative"
|
|
6003
|
-
});
|
|
6004
|
-
}
|
|
6005
|
-
if (prompts.length > 0) {
|
|
6006
|
-
requireInteractive("Provide --symbol, --name, and --decimals flags for non-interactive token addition.");
|
|
6007
|
-
const answers = await inquirer3.prompt(prompts);
|
|
6008
|
-
symbol = symbol || answers.symbol?.trim();
|
|
6009
|
-
name = name || answers.name?.trim();
|
|
6010
|
-
decimals = decimals ?? answers.decimals;
|
|
6011
|
-
}
|
|
6012
|
-
await addToken(ctx2, {
|
|
6013
|
-
chain: options.chain,
|
|
6014
|
-
contractAddress: options.add,
|
|
6015
|
-
symbol,
|
|
6016
|
-
name,
|
|
6017
|
-
decimals
|
|
6018
|
-
});
|
|
6019
|
-
} else if (options.remove) {
|
|
6020
|
-
await removeToken(ctx2, options.chain, options.remove);
|
|
6021
|
-
} else {
|
|
6022
|
-
await listTokens(ctx2, options.chain);
|
|
6023
|
-
}
|
|
6024
|
-
}
|
|
6025
|
-
async function addToken(ctx2, options) {
|
|
6026
|
-
const vault = await ctx2.ensureActiveVault();
|
|
6027
|
-
await vault.addToken(options.chain, {
|
|
6028
|
-
id: `${options.chain}-${options.contractAddress}`,
|
|
6029
|
-
contractAddress: options.contractAddress,
|
|
6030
|
-
symbol: options.symbol,
|
|
6031
|
-
name: options.name,
|
|
6032
|
-
decimals: options.decimals,
|
|
6033
|
-
chainId: options.chain,
|
|
6034
|
-
isNative: false
|
|
6035
|
-
});
|
|
6036
|
-
success(`
|
|
6037
|
-
+ Added token ${options.symbol} on ${options.chain}`);
|
|
6038
|
-
}
|
|
6039
|
-
async function removeToken(ctx2, chain, tokenId) {
|
|
6040
|
-
const vault = await ctx2.ensureActiveVault();
|
|
6041
|
-
await vault.removeToken(chain, tokenId);
|
|
6042
|
-
success(`
|
|
6043
|
-
+ Removed token ${tokenId} from ${chain}`);
|
|
6044
|
-
}
|
|
6045
|
-
async function discoverTokens(ctx2, chain) {
|
|
6046
|
-
const vault = await ctx2.ensureActiveVault();
|
|
6047
|
-
const spinner = createSpinner(`Discovering tokens on ${chain}...`);
|
|
6048
|
-
let discovered;
|
|
5933
|
+
// src/core/active-vault.ts
|
|
5934
|
+
var ACTIVE_VAULT_ID_KEY = "activeVaultId";
|
|
5935
|
+
async function loadActiveVaultSafely(sdk) {
|
|
5936
|
+
let activeId;
|
|
6049
5937
|
try {
|
|
6050
|
-
|
|
5938
|
+
activeId = await sdk.storage.get(ACTIVE_VAULT_ID_KEY);
|
|
6051
5939
|
} catch (err) {
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
5940
|
+
process.stderr.write(
|
|
5941
|
+
`Warning: active vault pointer is unreadable and was ignored (no active vault). ${err?.message ?? ""}
|
|
5942
|
+
`
|
|
5943
|
+
);
|
|
5944
|
+
try {
|
|
5945
|
+
await sdk.setActiveVault(null);
|
|
5946
|
+
} catch {
|
|
6059
5947
|
}
|
|
6060
|
-
return;
|
|
5948
|
+
return { vault: null, corruptPointer: true };
|
|
6061
5949
|
}
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
const newTokens = discovered.filter((d) => d.contractAddress && !existingAddresses.has(d.contractAddress));
|
|
6065
|
-
for (const d of newTokens) {
|
|
6066
|
-
await vault.addToken(chain, {
|
|
6067
|
-
id: d.contractAddress,
|
|
6068
|
-
symbol: d.ticker,
|
|
6069
|
-
name: d.ticker,
|
|
6070
|
-
decimals: d.decimals,
|
|
6071
|
-
contractAddress: d.contractAddress,
|
|
6072
|
-
chainId: chain,
|
|
6073
|
-
isNative: false
|
|
6074
|
-
});
|
|
5950
|
+
if (activeId === null || activeId === void 0) {
|
|
5951
|
+
return { vault: null, corruptPointer: false };
|
|
6075
5952
|
}
|
|
6076
|
-
|
|
6077
|
-
spinner.succeed(`Discovered ${newTokens.length} new token(s) on ${chain}`);
|
|
6078
|
-
if (isJsonOutput()) {
|
|
6079
|
-
outputJson({
|
|
6080
|
-
chain,
|
|
6081
|
-
discovered: newTokens.map((d) => ({
|
|
6082
|
-
symbol: d.ticker,
|
|
6083
|
-
contractAddress: d.contractAddress,
|
|
6084
|
-
decimals: d.decimals
|
|
6085
|
-
})),
|
|
6086
|
-
count: newTokens.length
|
|
6087
|
-
});
|
|
6088
|
-
return;
|
|
6089
|
-
}
|
|
6090
|
-
for (const d of newTokens) {
|
|
6091
|
-
printResult(` ${d.ticker} (${d.contractAddress})`);
|
|
6092
|
-
}
|
|
6093
|
-
info(chalk4.gray(`
|
|
6094
|
-
${allTokens.length} total token(s) tracked on ${chain}`));
|
|
5953
|
+
return { vault: await sdk.getVaultById(activeId), corruptPointer: false };
|
|
6095
5954
|
}
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
const spinner = createSpinner(`Loading tokens for ${chain}...`);
|
|
6099
|
-
const tokens = vault.getTokens(chain);
|
|
6100
|
-
spinner.succeed(`Tokens loaded for ${chain}`);
|
|
6101
|
-
if (isJsonOutput()) {
|
|
6102
|
-
outputJson({ chain, tokens: tokens || [] });
|
|
6103
|
-
return;
|
|
6104
|
-
}
|
|
6105
|
-
if (!tokens || tokens.length === 0) {
|
|
6106
|
-
warn(`
|
|
6107
|
-
No tokens configured for ${chain}`);
|
|
6108
|
-
info(chalk4.gray(`
|
|
6109
|
-
Use --add <contractAddress> to add a token`));
|
|
6110
|
-
} else {
|
|
6111
|
-
printResult(chalk4.cyan(`
|
|
6112
|
-
Tokens for ${chain}:
|
|
6113
|
-
`));
|
|
6114
|
-
const table = tokens.map((token) => ({
|
|
6115
|
-
Symbol: token.symbol,
|
|
6116
|
-
Name: token.name,
|
|
6117
|
-
Contract: token.contractAddress || "N/A",
|
|
6118
|
-
Decimals: token.decimals,
|
|
6119
|
-
Native: token.isNative ? "Yes" : "No"
|
|
6120
|
-
}));
|
|
6121
|
-
printTable(table);
|
|
6122
|
-
info(chalk4.gray(`
|
|
6123
|
-
Use --add <contractAddress> to add or --remove <tokenId> to remove`));
|
|
6124
|
-
}
|
|
5955
|
+
function shouldAutoSelectActiveVault(hasActiveVault, corruptPointer, vaultCount) {
|
|
5956
|
+
return !hasActiveVault && !corruptPointer && vaultCount > 0;
|
|
6125
5957
|
}
|
|
6126
5958
|
|
|
6127
|
-
// src/commands/transaction.ts
|
|
6128
|
-
import { Chain, Vultisig as Vultisig2 } from "@vultisig/sdk";
|
|
6129
|
-
|
|
6130
5959
|
// src/agent/broadcastJournal.ts
|
|
6131
5960
|
import { createHash } from "node:crypto";
|
|
6132
5961
|
import {
|
|
@@ -6703,7 +6532,242 @@ import { getConfigPath, loadConfig, saveConfig } from "@vultisig/client-shared";
|
|
|
6703
6532
|
// src/core/vault-discovery.ts
|
|
6704
6533
|
import { discoverVaultFiles, SEARCH_DIRS } from "@vultisig/client-shared";
|
|
6705
6534
|
|
|
6535
|
+
// src/commands/chains.ts
|
|
6536
|
+
async function executeChains(ctx2, options = {}) {
|
|
6537
|
+
const vault = await ctx2.ensureActiveVault();
|
|
6538
|
+
if (options.addAll) {
|
|
6539
|
+
const currentCount = vault.chains.length;
|
|
6540
|
+
const spinner = createSpinner(`Adding all ${SUPPORTED_CHAINS.length} supported chains...`);
|
|
6541
|
+
await vault.setChains([...SUPPORTED_CHAINS]);
|
|
6542
|
+
const addedCount = SUPPORTED_CHAINS.length - currentCount;
|
|
6543
|
+
spinner.succeed(`Added ${addedCount} chains (${SUPPORTED_CHAINS.length} total)`);
|
|
6544
|
+
if (isJsonOutput()) {
|
|
6545
|
+
outputJson({ chains: [...vault.chains], added: addedCount, total: SUPPORTED_CHAINS.length });
|
|
6546
|
+
return;
|
|
6547
|
+
}
|
|
6548
|
+
info(chalk3.gray("\nAll supported chains are now enabled."));
|
|
6549
|
+
return;
|
|
6550
|
+
}
|
|
6551
|
+
if (options.add) {
|
|
6552
|
+
if (!SUPPORTED_CHAINS.includes(options.add)) {
|
|
6553
|
+
throw new InvalidChainError(
|
|
6554
|
+
`Unsupported chain: "${options.add}"`,
|
|
6555
|
+
'Run "vultisig chains" to see the supported chains, or check the spelling.',
|
|
6556
|
+
void 0,
|
|
6557
|
+
{ chain: String(options.add) }
|
|
6558
|
+
);
|
|
6559
|
+
}
|
|
6560
|
+
const alreadyActive = vault.chains.includes(options.add);
|
|
6561
|
+
if (!alreadyActive) {
|
|
6562
|
+
await vault.addChain(options.add);
|
|
6563
|
+
}
|
|
6564
|
+
const address = await vault.address(options.add);
|
|
6565
|
+
if (isJsonOutput()) {
|
|
6566
|
+
outputJson({ chain: options.add, alreadyActive, address, chains: [...vault.chains] });
|
|
6567
|
+
return;
|
|
6568
|
+
}
|
|
6569
|
+
success(alreadyActive ? `
|
|
6570
|
+
Chain already active: ${options.add}` : `
|
|
6571
|
+
+ Added chain: ${options.add}`);
|
|
6572
|
+
info(`Address: ${address}`);
|
|
6573
|
+
} else if (options.remove) {
|
|
6574
|
+
await vault.removeChain(options.remove);
|
|
6575
|
+
if (isJsonOutput()) {
|
|
6576
|
+
outputJson({ chain: options.remove, removed: true, chains: [...vault.chains] });
|
|
6577
|
+
return;
|
|
6578
|
+
}
|
|
6579
|
+
success(`
|
|
6580
|
+
+ Removed chain: ${options.remove}`);
|
|
6581
|
+
} else {
|
|
6582
|
+
const chains = vault.chains;
|
|
6583
|
+
if (isJsonOutput()) {
|
|
6584
|
+
outputJson({ chains: [...chains] });
|
|
6585
|
+
return;
|
|
6586
|
+
}
|
|
6587
|
+
printResult(chalk3.cyan("\nActive Chains:\n"));
|
|
6588
|
+
chains.forEach((chain) => {
|
|
6589
|
+
printResult(` - ${chain}`);
|
|
6590
|
+
});
|
|
6591
|
+
info(chalk3.gray(`
|
|
6592
|
+
${chains.length} of ${SUPPORTED_CHAINS.length} chains enabled`));
|
|
6593
|
+
info(chalk3.gray("Use --add <chain>, --add-all, or --remove <chain>"));
|
|
6594
|
+
}
|
|
6595
|
+
}
|
|
6596
|
+
async function executeAddresses(ctx2) {
|
|
6597
|
+
const vault = await ctx2.ensureActiveVault();
|
|
6598
|
+
const spinner = createSpinner("Loading addresses...");
|
|
6599
|
+
const addresses = await vault.addresses();
|
|
6600
|
+
spinner.succeed("Addresses loaded");
|
|
6601
|
+
if (isJsonOutput()) {
|
|
6602
|
+
outputJson({ addresses });
|
|
6603
|
+
return;
|
|
6604
|
+
}
|
|
6605
|
+
displayAddresses(addresses);
|
|
6606
|
+
}
|
|
6607
|
+
|
|
6608
|
+
// src/commands/tokens.ts
|
|
6609
|
+
import chalk4 from "chalk";
|
|
6610
|
+
import inquirer3 from "inquirer";
|
|
6611
|
+
async function executeTokens(ctx2, options) {
|
|
6612
|
+
await ctx2.ensureActiveVault();
|
|
6613
|
+
if (options.discover) {
|
|
6614
|
+
await discoverTokens(ctx2, options.chain);
|
|
6615
|
+
return;
|
|
6616
|
+
}
|
|
6617
|
+
if (options.add) {
|
|
6618
|
+
let symbol = options.symbol;
|
|
6619
|
+
let name = options.name;
|
|
6620
|
+
let decimals = options.decimals;
|
|
6621
|
+
const prompts = [];
|
|
6622
|
+
if (!symbol) {
|
|
6623
|
+
prompts.push({
|
|
6624
|
+
type: "input",
|
|
6625
|
+
name: "symbol",
|
|
6626
|
+
message: "Enter token symbol (e.g., USDT):",
|
|
6627
|
+
validate: (input) => input.trim() !== "" || "Symbol is required"
|
|
6628
|
+
});
|
|
6629
|
+
}
|
|
6630
|
+
if (!name) {
|
|
6631
|
+
prompts.push({
|
|
6632
|
+
type: "input",
|
|
6633
|
+
name: "name",
|
|
6634
|
+
message: "Enter token name (e.g., Tether USD):",
|
|
6635
|
+
validate: (input) => input.trim() !== "" || "Name is required"
|
|
6636
|
+
});
|
|
6637
|
+
}
|
|
6638
|
+
if (decimals === void 0) {
|
|
6639
|
+
prompts.push({
|
|
6640
|
+
type: "number",
|
|
6641
|
+
name: "decimals",
|
|
6642
|
+
message: "Enter token decimals:",
|
|
6643
|
+
default: 18,
|
|
6644
|
+
validate: (input) => input >= 0 || "Decimals must be non-negative"
|
|
6645
|
+
});
|
|
6646
|
+
}
|
|
6647
|
+
if (prompts.length > 0) {
|
|
6648
|
+
requireInteractive("Provide --symbol, --name, and --decimals flags for non-interactive token addition.");
|
|
6649
|
+
const answers = await inquirer3.prompt(prompts);
|
|
6650
|
+
symbol = symbol || answers.symbol?.trim();
|
|
6651
|
+
name = name || answers.name?.trim();
|
|
6652
|
+
decimals = decimals ?? answers.decimals;
|
|
6653
|
+
}
|
|
6654
|
+
await addToken(ctx2, {
|
|
6655
|
+
chain: options.chain,
|
|
6656
|
+
contractAddress: options.add,
|
|
6657
|
+
symbol,
|
|
6658
|
+
name,
|
|
6659
|
+
decimals
|
|
6660
|
+
});
|
|
6661
|
+
} else if (options.remove) {
|
|
6662
|
+
await removeToken(ctx2, options.chain, options.remove);
|
|
6663
|
+
} else {
|
|
6664
|
+
await listTokens(ctx2, options.chain);
|
|
6665
|
+
}
|
|
6666
|
+
}
|
|
6667
|
+
async function addToken(ctx2, options) {
|
|
6668
|
+
const vault = await ctx2.ensureActiveVault();
|
|
6669
|
+
await vault.addToken(options.chain, {
|
|
6670
|
+
id: `${options.chain}-${options.contractAddress}`,
|
|
6671
|
+
contractAddress: options.contractAddress,
|
|
6672
|
+
symbol: options.symbol,
|
|
6673
|
+
name: options.name,
|
|
6674
|
+
decimals: options.decimals,
|
|
6675
|
+
chainId: options.chain,
|
|
6676
|
+
isNative: false
|
|
6677
|
+
});
|
|
6678
|
+
success(`
|
|
6679
|
+
+ Added token ${options.symbol} on ${options.chain}`);
|
|
6680
|
+
}
|
|
6681
|
+
async function removeToken(ctx2, chain, tokenId) {
|
|
6682
|
+
const vault = await ctx2.ensureActiveVault();
|
|
6683
|
+
await vault.removeToken(chain, tokenId);
|
|
6684
|
+
success(`
|
|
6685
|
+
+ Removed token ${tokenId} from ${chain}`);
|
|
6686
|
+
}
|
|
6687
|
+
async function discoverTokens(ctx2, chain) {
|
|
6688
|
+
const vault = await ctx2.ensureActiveVault();
|
|
6689
|
+
const spinner = createSpinner(`Discovering tokens on ${chain}...`);
|
|
6690
|
+
let discovered;
|
|
6691
|
+
try {
|
|
6692
|
+
discovered = await vault.discoverTokens(chain);
|
|
6693
|
+
} catch (err) {
|
|
6694
|
+
spinner.fail(`Token discovery failed for ${chain}`);
|
|
6695
|
+
throw err;
|
|
6696
|
+
}
|
|
6697
|
+
if (discovered.length === 0) {
|
|
6698
|
+
spinner.succeed(`No new tokens found on ${chain}`);
|
|
6699
|
+
if (isJsonOutput()) {
|
|
6700
|
+
outputJson({ chain, discovered: [], count: 0 });
|
|
6701
|
+
}
|
|
6702
|
+
return;
|
|
6703
|
+
}
|
|
6704
|
+
const existingTokens = vault.getTokens(chain);
|
|
6705
|
+
const existingAddresses = new Set(existingTokens.map((t) => t.contractAddress ?? t.id));
|
|
6706
|
+
const newTokens = discovered.filter((d) => d.contractAddress && !existingAddresses.has(d.contractAddress));
|
|
6707
|
+
for (const d of newTokens) {
|
|
6708
|
+
await vault.addToken(chain, {
|
|
6709
|
+
id: d.contractAddress,
|
|
6710
|
+
symbol: d.ticker,
|
|
6711
|
+
name: d.ticker,
|
|
6712
|
+
decimals: d.decimals,
|
|
6713
|
+
contractAddress: d.contractAddress,
|
|
6714
|
+
chainId: chain,
|
|
6715
|
+
isNative: false
|
|
6716
|
+
});
|
|
6717
|
+
}
|
|
6718
|
+
const allTokens = vault.getTokens(chain);
|
|
6719
|
+
spinner.succeed(`Discovered ${newTokens.length} new token(s) on ${chain}`);
|
|
6720
|
+
if (isJsonOutput()) {
|
|
6721
|
+
outputJson({
|
|
6722
|
+
chain,
|
|
6723
|
+
discovered: newTokens.map((d) => ({
|
|
6724
|
+
symbol: d.ticker,
|
|
6725
|
+
contractAddress: d.contractAddress,
|
|
6726
|
+
decimals: d.decimals
|
|
6727
|
+
})),
|
|
6728
|
+
count: newTokens.length
|
|
6729
|
+
});
|
|
6730
|
+
return;
|
|
6731
|
+
}
|
|
6732
|
+
for (const d of newTokens) {
|
|
6733
|
+
printResult(` ${d.ticker} (${d.contractAddress})`);
|
|
6734
|
+
}
|
|
6735
|
+
info(chalk4.gray(`
|
|
6736
|
+
${allTokens.length} total token(s) tracked on ${chain}`));
|
|
6737
|
+
}
|
|
6738
|
+
async function listTokens(ctx2, chain) {
|
|
6739
|
+
const vault = await ctx2.ensureActiveVault();
|
|
6740
|
+
const spinner = createSpinner(`Loading tokens for ${chain}...`);
|
|
6741
|
+
const tokens = vault.getTokens(chain);
|
|
6742
|
+
spinner.succeed(`Tokens loaded for ${chain}`);
|
|
6743
|
+
if (isJsonOutput()) {
|
|
6744
|
+
outputJson({ chain, tokens: tokens || [] });
|
|
6745
|
+
return;
|
|
6746
|
+
}
|
|
6747
|
+
if (!tokens || tokens.length === 0) {
|
|
6748
|
+
warn(`
|
|
6749
|
+
No tokens configured for ${chain}`);
|
|
6750
|
+
info(chalk4.gray(`
|
|
6751
|
+
Use --add <contractAddress> to add a token`));
|
|
6752
|
+
} else {
|
|
6753
|
+
printResult(chalk4.cyan(`
|
|
6754
|
+
Tokens for ${chain}:
|
|
6755
|
+
`));
|
|
6756
|
+
const table = tokens.map((token) => ({
|
|
6757
|
+
Symbol: token.symbol,
|
|
6758
|
+
Name: token.name,
|
|
6759
|
+
Contract: token.contractAddress || "N/A",
|
|
6760
|
+
Decimals: token.decimals,
|
|
6761
|
+
Native: token.isNative ? "Yes" : "No"
|
|
6762
|
+
}));
|
|
6763
|
+
printTable(table);
|
|
6764
|
+
info(chalk4.gray(`
|
|
6765
|
+
Use --add <contractAddress> to add or --remove <tokenId> to remove`));
|
|
6766
|
+
}
|
|
6767
|
+
}
|
|
6768
|
+
|
|
6706
6769
|
// src/commands/transaction.ts
|
|
6770
|
+
import { Chain, Vultisig as Vultisig2 } from "@vultisig/sdk";
|
|
6707
6771
|
async function executeSend(ctx2, params) {
|
|
6708
6772
|
const vault = await ctx2.ensureActiveVault();
|
|
6709
6773
|
if (!Object.values(Chain).includes(params.chain)) {
|
|
@@ -14534,7 +14598,7 @@ var cachedVersion = null;
|
|
|
14534
14598
|
function getVersion() {
|
|
14535
14599
|
if (cachedVersion) return cachedVersion;
|
|
14536
14600
|
if (true) {
|
|
14537
|
-
cachedVersion = "2.19.
|
|
14601
|
+
cachedVersion = "2.19.11";
|
|
14538
14602
|
return cachedVersion;
|
|
14539
14603
|
}
|
|
14540
14604
|
try {
|
|
@@ -16150,7 +16214,7 @@ Error: ${error2.message}`));
|
|
|
16150
16214
|
async loadAllVaults() {
|
|
16151
16215
|
const spinner = createSpinner2("Loading vaults...").start();
|
|
16152
16216
|
try {
|
|
16153
|
-
const activeVault = await this.ctx.sdk
|
|
16217
|
+
const { vault: activeVault, corruptPointer } = await loadActiveVaultSafely(this.ctx.sdk);
|
|
16154
16218
|
if (activeVault) {
|
|
16155
16219
|
this.ctx.addVault(activeVault);
|
|
16156
16220
|
await this.ctx.setActiveVault(activeVault);
|
|
@@ -16164,7 +16228,7 @@ Error: ${error2.message}`));
|
|
|
16164
16228
|
this.eventBuffer.setupVaultListeners(vault);
|
|
16165
16229
|
}
|
|
16166
16230
|
});
|
|
16167
|
-
if (
|
|
16231
|
+
if (shouldAutoSelectActiveVault(!!this.ctx.getActiveVault(), corruptPointer, this.ctx.getVaults().size)) {
|
|
16168
16232
|
const firstVault = this.ctx.getVaults().values().next().value;
|
|
16169
16233
|
await this.ctx.setActiveVault(firstVault);
|
|
16170
16234
|
}
|
|
@@ -16174,6 +16238,11 @@ Error: ${error2.message}`));
|
|
|
16174
16238
|
} else {
|
|
16175
16239
|
spinner.succeed("No vaults found");
|
|
16176
16240
|
}
|
|
16241
|
+
if (corruptPointer && !this.ctx.getActiveVault() && this.ctx.getVaults().size > 0) {
|
|
16242
|
+
console.log(
|
|
16243
|
+
chalk14.yellow('Active vault pointer was corrupt and has been reset. Use "vault <name>" to pick one.')
|
|
16244
|
+
);
|
|
16245
|
+
}
|
|
16177
16246
|
} catch (error2) {
|
|
16178
16247
|
if (this.ctx.getVaults().size > 0) {
|
|
16179
16248
|
spinner.succeed(`Loaded ${this.ctx.getVaults().size} vault(s)`);
|
|
@@ -16522,6 +16591,18 @@ complete -c vsig -n "__fish_seen_subcommand_from import export" -a "(__fish_comp
|
|
|
16522
16591
|
`.trim();
|
|
16523
16592
|
}
|
|
16524
16593
|
|
|
16594
|
+
// src/lib/config.ts
|
|
16595
|
+
import { FileStorage } from "@vultisig/sdk/node";
|
|
16596
|
+
import { homedir as homedir6 } from "os";
|
|
16597
|
+
import { join as join6 } from "path";
|
|
16598
|
+
function getConfigDir() {
|
|
16599
|
+
const override = process.env.VULTISIG_CONFIG_DIR?.trim();
|
|
16600
|
+
return override ? override : join6(homedir6(), ".vultisig");
|
|
16601
|
+
}
|
|
16602
|
+
function createVaultStorage() {
|
|
16603
|
+
return new FileStorage({ basePath: getConfigDir() });
|
|
16604
|
+
}
|
|
16605
|
+
|
|
16525
16606
|
// src/lib/errors.ts
|
|
16526
16607
|
import chalk15 from "chalk";
|
|
16527
16608
|
|
|
@@ -16596,6 +16677,7 @@ async function init(vaultOverride, unlockPassword, passwordTTL) {
|
|
|
16596
16677
|
const globalOptions = program.opts();
|
|
16597
16678
|
const serverEndpoints = resolveServerEndpoints(globalOptions);
|
|
16598
16679
|
const sdk = new Vultisig6({
|
|
16680
|
+
storage: createVaultStorage(),
|
|
16599
16681
|
onPasswordRequired: createPasswordCallback(),
|
|
16600
16682
|
...serverEndpoints ? { serverEndpoints } : {},
|
|
16601
16683
|
...passwordTTL !== void 0 ? { passwordCache: { defaultTTL: passwordTTL } } : {}
|
|
@@ -16609,7 +16691,7 @@ async function init(vaultOverride, unlockPassword, passwordTTL) {
|
|
|
16609
16691
|
throw new Error(`Vault not found: "${vaultSelector}"`);
|
|
16610
16692
|
}
|
|
16611
16693
|
} else {
|
|
16612
|
-
vault = await sdk.
|
|
16694
|
+
vault = (await loadActiveVaultSafely(sdk)).vault;
|
|
16613
16695
|
}
|
|
16614
16696
|
if (vault) {
|
|
16615
16697
|
await ctx.setActiveVault(vault);
|
|
@@ -17440,6 +17522,7 @@ program.command("schema", { hidden: true }).description("Output machine-readable
|
|
|
17440
17522
|
async function startInteractiveMode() {
|
|
17441
17523
|
const serverEndpoints = resolveServerEndpoints(parseServerEndpointOverridesFromArgv(process.argv.slice(2)));
|
|
17442
17524
|
const sdk = new Vultisig6({
|
|
17525
|
+
storage: createVaultStorage(),
|
|
17443
17526
|
onPasswordRequired: createPasswordCallback(),
|
|
17444
17527
|
...serverEndpoints ? { serverEndpoints } : {}
|
|
17445
17528
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vultisig/cli",
|
|
3
|
-
"version": "2.19.
|
|
3
|
+
"version": "2.19.11",
|
|
4
4
|
"description": "The self-custody MPC wallet CLI for AI coding agents (Claude Code, Cursor, OpenCode). Natural-language agent mode, 36+ chains, DKLS23 threshold signatures. Seedless.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -74,9 +74,9 @@
|
|
|
74
74
|
"@napi-rs/keyring": "^1.3.0",
|
|
75
75
|
"@noble/hashes": "^2.2.0",
|
|
76
76
|
"@vultisig/client-shared": "^0.2.17",
|
|
77
|
-
"@vultisig/core-chain": "^2.24.
|
|
77
|
+
"@vultisig/core-chain": "^2.24.2",
|
|
78
78
|
"@vultisig/rujira": "^52.0.0",
|
|
79
|
-
"@vultisig/sdk": "^2.19.
|
|
79
|
+
"@vultisig/sdk": "^2.19.11",
|
|
80
80
|
"chalk": "^5.6.2",
|
|
81
81
|
"cli-table3": "^0.6.5",
|
|
82
82
|
"commander": "^15.0.0",
|