@vultisig/cli 2.19.10 → 2.19.14
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 +21 -0
- package/README.md +52 -14
- package/dist/index.js +714 -602
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -4875,7 +4875,6 @@ import { descriptions } from "@vultisig/client-shared";
|
|
|
4875
4875
|
import { parseKeygenQR, Vultisig as Vultisig6 } from "@vultisig/sdk";
|
|
4876
4876
|
import chalk16 from "chalk";
|
|
4877
4877
|
import { InvalidArgumentError, program } from "commander";
|
|
4878
|
-
import inquirer8 from "inquirer";
|
|
4879
4878
|
|
|
4880
4879
|
// src/core/command-context.ts
|
|
4881
4880
|
var DEFAULT_PASSWORD_CACHE_TTL = 5 * 60 * 1e3;
|
|
@@ -4948,616 +4947,647 @@ var BaseCommandContext = class {
|
|
|
4948
4947
|
}
|
|
4949
4948
|
};
|
|
4950
4949
|
|
|
4951
|
-
// src/core/password-manager.ts
|
|
4952
|
-
import inquirer from "inquirer";
|
|
4953
|
-
|
|
4954
4950
|
// src/lib/output.ts
|
|
4955
4951
|
import chalk from "chalk";
|
|
4956
4952
|
import ora from "ora";
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
var
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4953
|
+
|
|
4954
|
+
// src/core/errors.ts
|
|
4955
|
+
import { VaultError, VaultErrorCode, VaultImportError, VaultImportErrorCode } from "@vultisig/sdk";
|
|
4956
|
+
var EXIT_CODE_DESCRIPTIONS = {
|
|
4957
|
+
[0 /* SUCCESS */]: "Success",
|
|
4958
|
+
[1 /* USAGE */]: "Usage error (bad arguments, unknown command)",
|
|
4959
|
+
[2 /* AUTH_REQUIRED */]: "Authentication required",
|
|
4960
|
+
[3 /* NETWORK */]: "Network error (retryable)",
|
|
4961
|
+
[4 /* INVALID_INPUT */]: "Invalid input (bad chain, address, amount)",
|
|
4962
|
+
[5 /* RESOURCE_NOT_FOUND */]: "Resource not found (token, route)",
|
|
4963
|
+
[6 /* EXTERNAL_SERVICE */]: "External service error (retryable)",
|
|
4964
|
+
[7 /* UNKNOWN */]: "Unknown/unexpected error",
|
|
4965
|
+
[8 /* ACK_FAILED */]: "Broadcast succeeded but post-broadcast report failed \u2014 hash is valid, do NOT retry",
|
|
4966
|
+
[9 /* DUPLICATE_BROADCAST */]: "Duplicate broadcast refused (nothing sent) \u2014 retry with --force to override",
|
|
4967
|
+
[10 /* AGENT_TURN_BLOCKED */]: "agent ask: a fund-safety guardrail blocked the requested action",
|
|
4968
|
+
[11 /* AGENT_TURN_REFUSAL */]: "agent ask: the model refused or asked a clarifying question (no action taken)",
|
|
4969
|
+
[12 /* CONFIRMATION_REQUIRED */]: "Interactive confirmation/input required but the session is non-interactive \u2014 pass --yes/--confirm or the required flag"
|
|
4970
|
+
};
|
|
4971
|
+
var VsigError = class extends Error {
|
|
4972
|
+
hint;
|
|
4973
|
+
suggestions;
|
|
4974
|
+
context;
|
|
4975
|
+
retryable = false;
|
|
4976
|
+
constructor(message, hint, suggestions, context) {
|
|
4977
|
+
super(message);
|
|
4978
|
+
this.name = this.constructor.name;
|
|
4979
|
+
this.hint = hint;
|
|
4980
|
+
this.suggestions = suggestions;
|
|
4981
|
+
this.context = context;
|
|
4977
4982
|
}
|
|
4978
|
-
}
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
}
|
|
4985
|
-
function initOutputMode(options) {
|
|
4986
|
-
outputFormat = options.output ?? "table";
|
|
4987
|
-
silentMode = options.silent ?? process.env.VULTISIG_SILENT === "1";
|
|
4988
|
-
if (outputFormat === "json") {
|
|
4989
|
-
silentMode = true;
|
|
4983
|
+
};
|
|
4984
|
+
var UsageError = class extends VsigError {
|
|
4985
|
+
exitCode = 1 /* USAGE */;
|
|
4986
|
+
code = "USAGE_ERROR";
|
|
4987
|
+
constructor(message, hint, suggestions) {
|
|
4988
|
+
super(message, hint, suggestions);
|
|
4990
4989
|
}
|
|
4991
|
-
}
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
);
|
|
4990
|
+
};
|
|
4991
|
+
var AuthRequiredError = class extends VsigError {
|
|
4992
|
+
exitCode = 2 /* AUTH_REQUIRED */;
|
|
4993
|
+
code = "AUTH_REQUIRED";
|
|
4994
|
+
constructor(message) {
|
|
4995
|
+
super(message ?? "Authentication required. Set up vault credentials.", "Ensure your vault is unlocked", [
|
|
4996
|
+
"vsig vaults",
|
|
4997
|
+
"vsig create"
|
|
4998
|
+
]);
|
|
5001
4999
|
}
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
const matched = entries.filter(([k]) => fields.includes(k));
|
|
5010
|
-
if (matched.length > 0) return Object.fromEntries(matched);
|
|
5011
|
-
const recursed = entries.map(([k, v]) => [k, filterFields(v, fields)]);
|
|
5012
|
-
return Object.fromEntries(recursed);
|
|
5000
|
+
};
|
|
5001
|
+
var NetworkError = class extends VsigError {
|
|
5002
|
+
exitCode = 3 /* NETWORK */;
|
|
5003
|
+
code = "NETWORK_ERROR";
|
|
5004
|
+
retryable = true;
|
|
5005
|
+
constructor(message, hint, suggestions) {
|
|
5006
|
+
super(message, hint, suggestions);
|
|
5013
5007
|
}
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
if (item !== null && typeof item === "object") {
|
|
5021
|
-
for (const k of Object.keys(item)) keys.add(k);
|
|
5022
|
-
}
|
|
5023
|
-
}
|
|
5024
|
-
} else if (data !== null && typeof data === "object") {
|
|
5025
|
-
for (const [k, v] of Object.entries(data)) {
|
|
5026
|
-
keys.add(k);
|
|
5027
|
-
if (v !== null && typeof v === "object") {
|
|
5028
|
-
for (const nested of collectKeys(v)) keys.add(nested);
|
|
5029
|
-
}
|
|
5030
|
-
}
|
|
5008
|
+
};
|
|
5009
|
+
var InvalidChainError = class extends VsigError {
|
|
5010
|
+
exitCode = 4 /* INVALID_INPUT */;
|
|
5011
|
+
code = "INVALID_CHAIN";
|
|
5012
|
+
constructor(message, hint, suggestions, context) {
|
|
5013
|
+
super(message, hint, suggestions, context);
|
|
5031
5014
|
}
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
if (invalid.length > 0) {
|
|
5039
|
-
process.stderr.write(`Warning: unknown field(s): ${invalid.join(", ")}. Available: ${[...available].join(", ")}
|
|
5040
|
-
`);
|
|
5015
|
+
};
|
|
5016
|
+
var InvalidAddressError = class extends VsigError {
|
|
5017
|
+
exitCode = 4 /* INVALID_INPUT */;
|
|
5018
|
+
code = "INVALID_ADDRESS";
|
|
5019
|
+
constructor(message, hint, suggestions, context) {
|
|
5020
|
+
super(message, hint, suggestions, context);
|
|
5041
5021
|
}
|
|
5042
|
-
}
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5022
|
+
};
|
|
5023
|
+
var InvalidInputError = class extends VsigError {
|
|
5024
|
+
exitCode = 4 /* INVALID_INPUT */;
|
|
5025
|
+
code = "INVALID_INPUT";
|
|
5026
|
+
constructor(message, hint, suggestions, context) {
|
|
5027
|
+
super(message, hint, suggestions, context);
|
|
5048
5028
|
}
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
const transformed = applyOutputTransforms(data);
|
|
5056
|
-
process.stdout.write(JSON.stringify({ success: true, v: 1, data: transformed }, bigIntReplacer, 2) + "\n");
|
|
5057
|
-
}
|
|
5058
|
-
function outputErrorJson(errJson) {
|
|
5059
|
-
const transformed = applyOutputTransforms(errJson);
|
|
5060
|
-
process.stdout.write(JSON.stringify(transformed, bigIntReplacer, 2) + "\n");
|
|
5061
|
-
}
|
|
5062
|
-
function info(message) {
|
|
5063
|
-
if (!silentMode) {
|
|
5064
|
-
console.log(chalk.blue(message));
|
|
5029
|
+
};
|
|
5030
|
+
var InsufficientBalanceError = class extends VsigError {
|
|
5031
|
+
exitCode = 4 /* INVALID_INPUT */;
|
|
5032
|
+
code = "INSUFFICIENT_BALANCE";
|
|
5033
|
+
constructor(message, hint, suggestions, context) {
|
|
5034
|
+
super(message, hint, suggestions, context);
|
|
5065
5035
|
}
|
|
5066
|
-
}
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5036
|
+
};
|
|
5037
|
+
var NoRouteError = class extends VsigError {
|
|
5038
|
+
exitCode = 5 /* RESOURCE_NOT_FOUND */;
|
|
5039
|
+
code = "NO_ROUTE";
|
|
5040
|
+
constructor(message, hint, suggestions, context) {
|
|
5041
|
+
super(message, hint, suggestions, context);
|
|
5071
5042
|
}
|
|
5072
|
-
}
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5043
|
+
};
|
|
5044
|
+
var TokenNotFoundError = class extends VsigError {
|
|
5045
|
+
exitCode = 5 /* RESOURCE_NOT_FOUND */;
|
|
5046
|
+
code = "TOKEN_NOT_FOUND";
|
|
5047
|
+
constructor(message, hint, suggestions, context) {
|
|
5048
|
+
super(message, hint, suggestions, context);
|
|
5076
5049
|
}
|
|
5077
|
-
}
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
console.log(message);
|
|
5084
|
-
}
|
|
5085
|
-
function printTable(data) {
|
|
5086
|
-
console.table(data);
|
|
5087
|
-
}
|
|
5088
|
-
function printError(message) {
|
|
5089
|
-
console.error(chalk.red(message));
|
|
5090
|
-
}
|
|
5091
|
-
var activeSpinners = /* @__PURE__ */ new Set();
|
|
5092
|
-
function stopAllSpinners() {
|
|
5093
|
-
for (const spinner of activeSpinners) {
|
|
5094
|
-
spinner.stop();
|
|
5050
|
+
};
|
|
5051
|
+
var TxNotFoundError = class extends VsigError {
|
|
5052
|
+
exitCode = 5 /* RESOURCE_NOT_FOUND */;
|
|
5053
|
+
code = "TX_NOT_FOUND";
|
|
5054
|
+
constructor(message, hint, suggestions, context) {
|
|
5055
|
+
super(message, hint, suggestions, context);
|
|
5095
5056
|
}
|
|
5096
|
-
activeSpinners.clear();
|
|
5097
|
-
}
|
|
5098
|
-
function createNoopSpinner(text) {
|
|
5099
|
-
const noopSpinner = {
|
|
5100
|
-
text,
|
|
5101
|
-
start() {
|
|
5102
|
-
return this;
|
|
5103
|
-
},
|
|
5104
|
-
stop() {
|
|
5105
|
-
activeSpinners.delete(this);
|
|
5106
|
-
return this;
|
|
5107
|
-
},
|
|
5108
|
-
succeed() {
|
|
5109
|
-
activeSpinners.delete(this);
|
|
5110
|
-
return this;
|
|
5111
|
-
},
|
|
5112
|
-
fail(text2) {
|
|
5113
|
-
activeSpinners.delete(this);
|
|
5114
|
-
if (text2) printError(text2);
|
|
5115
|
-
return this;
|
|
5116
|
-
},
|
|
5117
|
-
warn() {
|
|
5118
|
-
activeSpinners.delete(this);
|
|
5119
|
-
return this;
|
|
5120
|
-
},
|
|
5121
|
-
info() {
|
|
5122
|
-
activeSpinners.delete(this);
|
|
5123
|
-
return this;
|
|
5124
|
-
}
|
|
5125
|
-
};
|
|
5126
|
-
return noopSpinner;
|
|
5127
|
-
}
|
|
5128
|
-
function wrapOraSpinner(spinner) {
|
|
5129
|
-
const originalStop = spinner.stop.bind(spinner);
|
|
5130
|
-
const originalSucceed = spinner.succeed.bind(spinner);
|
|
5131
|
-
const originalFail = spinner.fail.bind(spinner);
|
|
5132
|
-
const originalWarn = spinner.warn.bind(spinner);
|
|
5133
|
-
const originalInfo = spinner.info.bind(spinner);
|
|
5134
|
-
spinner.stop = () => {
|
|
5135
|
-
activeSpinners.delete(spinner);
|
|
5136
|
-
return originalStop();
|
|
5137
|
-
};
|
|
5138
|
-
spinner.succeed = (text) => {
|
|
5139
|
-
activeSpinners.delete(spinner);
|
|
5140
|
-
return originalSucceed(text);
|
|
5141
|
-
};
|
|
5142
|
-
spinner.fail = (text) => {
|
|
5143
|
-
activeSpinners.delete(spinner);
|
|
5144
|
-
return originalFail(text);
|
|
5145
|
-
};
|
|
5146
|
-
spinner.warn = (text) => {
|
|
5147
|
-
activeSpinners.delete(spinner);
|
|
5148
|
-
return originalWarn(text);
|
|
5149
|
-
};
|
|
5150
|
-
spinner.info = (text) => {
|
|
5151
|
-
activeSpinners.delete(spinner);
|
|
5152
|
-
return originalInfo(text);
|
|
5153
|
-
};
|
|
5154
|
-
return spinner;
|
|
5155
|
-
}
|
|
5156
|
-
function createSpinner(text) {
|
|
5157
|
-
if (silentMode) {
|
|
5158
|
-
const spinner2 = createNoopSpinner(text);
|
|
5159
|
-
activeSpinners.add(spinner2);
|
|
5160
|
-
return spinner2;
|
|
5161
|
-
}
|
|
5162
|
-
const spinner = wrapOraSpinner(ora({ text, stream: process.stderr }).start());
|
|
5163
|
-
activeSpinners.add(spinner);
|
|
5164
|
-
return spinner;
|
|
5165
|
-
}
|
|
5166
|
-
|
|
5167
|
-
// src/core/credential-store.ts
|
|
5168
|
-
import {
|
|
5169
|
-
_resetAll,
|
|
5170
|
-
clearCredentials,
|
|
5171
|
-
getCredentialsPath,
|
|
5172
|
-
getDecryptionPassword,
|
|
5173
|
-
getServerPassword,
|
|
5174
|
-
getStoredServerPassword,
|
|
5175
|
-
isUsingFileFallback,
|
|
5176
|
-
setDecryptionPassword,
|
|
5177
|
-
setFilePassphrase,
|
|
5178
|
-
setServerPassword
|
|
5179
|
-
} from "@vultisig/client-shared";
|
|
5180
|
-
|
|
5181
|
-
// src/core/password-manager.ts
|
|
5182
|
-
var passwordCache = /* @__PURE__ */ new Map();
|
|
5183
|
-
function cachePassword(vaultIdOrName, password) {
|
|
5184
|
-
passwordCache.set(vaultIdOrName, password);
|
|
5185
|
-
}
|
|
5186
|
-
function getCachedPassword(vaultId, vaultName) {
|
|
5187
|
-
if (vaultName && passwordCache.has(vaultName)) return passwordCache.get(vaultName);
|
|
5188
|
-
if (passwordCache.has(vaultId)) return passwordCache.get(vaultId);
|
|
5189
|
-
return null;
|
|
5190
|
-
}
|
|
5191
|
-
function clearCachedPassword(vaultIdOrName) {
|
|
5192
|
-
if (vaultIdOrName) {
|
|
5193
|
-
passwordCache.delete(vaultIdOrName);
|
|
5194
|
-
} else {
|
|
5195
|
-
passwordCache.clear();
|
|
5196
|
-
}
|
|
5197
|
-
}
|
|
5198
|
-
function parseVaultPasswords() {
|
|
5199
|
-
const passwordMap = /* @__PURE__ */ new Map();
|
|
5200
|
-
const passwordsEnv = process.env.VAULT_PASSWORDS;
|
|
5201
|
-
if (passwordsEnv) {
|
|
5202
|
-
const pairs = passwordsEnv.trim().split(/\s+/);
|
|
5203
|
-
for (const pair of pairs) {
|
|
5204
|
-
const colonIndex = pair.indexOf(":");
|
|
5205
|
-
if (colonIndex > 0) {
|
|
5206
|
-
const vaultKey = pair.substring(0, colonIndex);
|
|
5207
|
-
const password = pair.substring(colonIndex + 1);
|
|
5208
|
-
passwordMap.set(vaultKey, password);
|
|
5209
|
-
}
|
|
5210
|
-
}
|
|
5211
|
-
}
|
|
5212
|
-
return passwordMap;
|
|
5213
|
-
}
|
|
5214
|
-
function getPasswordFromEnv(vaultId, vaultName) {
|
|
5215
|
-
const vaultPasswords = parseVaultPasswords();
|
|
5216
|
-
if (vaultName && vaultPasswords.has(vaultName)) {
|
|
5217
|
-
return vaultPasswords.get(vaultName);
|
|
5218
|
-
}
|
|
5219
|
-
if (vaultPasswords.has(vaultId)) {
|
|
5220
|
-
return vaultPasswords.get(vaultId);
|
|
5221
|
-
}
|
|
5222
|
-
if (process.env.VAULT_PASSWORD || process.env.VULTISIG_PASSWORD) {
|
|
5223
|
-
return process.env.VAULT_PASSWORD || process.env.VULTISIG_PASSWORD;
|
|
5224
|
-
}
|
|
5225
|
-
return null;
|
|
5226
|
-
}
|
|
5227
|
-
async function promptForPassword(vaultName, vaultId) {
|
|
5228
|
-
requireInteractive(
|
|
5229
|
-
'Use --password flag, VAULT_PASSWORD env var, or "vsig auth setup" to store credentials in keyring.'
|
|
5230
|
-
);
|
|
5231
|
-
const displayName = vaultName || vaultId || "vault";
|
|
5232
|
-
const { password } = await inquirer.prompt([
|
|
5233
|
-
{
|
|
5234
|
-
type: "password",
|
|
5235
|
-
name: "password",
|
|
5236
|
-
message: `Enter password for vault "${displayName}":`,
|
|
5237
|
-
mask: "*"
|
|
5238
|
-
}
|
|
5239
|
-
]);
|
|
5240
|
-
return password;
|
|
5241
|
-
}
|
|
5242
|
-
async function resolvePasswordNonInteractive(vaultId, vaultName) {
|
|
5243
|
-
const cachedPassword = getCachedPassword(vaultId, vaultName);
|
|
5244
|
-
if (cachedPassword) {
|
|
5245
|
-
return cachedPassword;
|
|
5246
|
-
}
|
|
5247
|
-
try {
|
|
5248
|
-
const keyringPassword = await getServerPassword(vaultId);
|
|
5249
|
-
if (keyringPassword) {
|
|
5250
|
-
cachePassword(vaultId, keyringPassword);
|
|
5251
|
-
if (vaultName) cachePassword(vaultName, keyringPassword);
|
|
5252
|
-
return keyringPassword;
|
|
5253
|
-
}
|
|
5254
|
-
} catch {
|
|
5255
|
-
}
|
|
5256
|
-
const envPassword = getPasswordFromEnv(vaultId, vaultName);
|
|
5257
|
-
if (envPassword) {
|
|
5258
|
-
cachePassword(vaultId, envPassword);
|
|
5259
|
-
if (vaultName) cachePassword(vaultName, envPassword);
|
|
5260
|
-
return envPassword;
|
|
5261
|
-
}
|
|
5262
|
-
return null;
|
|
5263
|
-
}
|
|
5264
|
-
async function getPassword(vaultId, vaultName) {
|
|
5265
|
-
const resolved = await resolvePasswordNonInteractive(vaultId, vaultName);
|
|
5266
|
-
if (resolved) {
|
|
5267
|
-
return resolved;
|
|
5268
|
-
}
|
|
5269
|
-
if (isSilent() || isJsonOutput() || isNonInteractive()) {
|
|
5270
|
-
throw new Error(
|
|
5271
|
-
"Password required but not provided. Set VAULT_PASSWORD or VAULT_PASSWORDS environment variable, or use --password flag."
|
|
5272
|
-
);
|
|
5273
|
-
}
|
|
5274
|
-
const password = await promptForPassword(vaultName, vaultId);
|
|
5275
|
-
cachePassword(vaultId, password);
|
|
5276
|
-
if (vaultName) cachePassword(vaultName, password);
|
|
5277
|
-
return password;
|
|
5278
|
-
}
|
|
5279
|
-
function createPasswordCallback() {
|
|
5280
|
-
return async (vaultId, vaultName) => {
|
|
5281
|
-
return getPassword(vaultId, vaultName);
|
|
5282
|
-
};
|
|
5283
|
-
}
|
|
5284
|
-
async function ensureVaultUnlocked(vault, password) {
|
|
5285
|
-
if (!vault.isEncrypted || vault.isUnlocked()) {
|
|
5286
|
-
return;
|
|
5287
|
-
}
|
|
5288
|
-
const resolvedPassword = password || await getPassword(vault.id, vault.name);
|
|
5289
|
-
await vault.unlock(resolvedPassword);
|
|
5290
|
-
}
|
|
5291
|
-
|
|
5292
|
-
// src/adapters/cli-context.ts
|
|
5293
|
-
var CLIContext = class extends BaseCommandContext {
|
|
5294
|
-
get isInteractive() {
|
|
5295
|
-
return false;
|
|
5296
|
-
}
|
|
5297
|
-
/**
|
|
5298
|
-
* Get password for a vault
|
|
5299
|
-
* In CLI mode, we check env vars first, then prompt
|
|
5300
|
-
* No caching since each command runs independently
|
|
5301
|
-
*/
|
|
5302
|
-
async getPassword(vaultId, vaultName) {
|
|
5303
|
-
return getPassword(vaultId, vaultName);
|
|
5304
|
-
}
|
|
5305
|
-
};
|
|
5306
|
-
|
|
5307
|
-
// src/core/errors.ts
|
|
5308
|
-
import { VaultError, VaultErrorCode, VaultImportError, VaultImportErrorCode } from "@vultisig/sdk";
|
|
5309
|
-
var EXIT_CODE_DESCRIPTIONS = {
|
|
5310
|
-
[0 /* SUCCESS */]: "Success",
|
|
5311
|
-
[1 /* USAGE */]: "Usage error (bad arguments, unknown command)",
|
|
5312
|
-
[2 /* AUTH_REQUIRED */]: "Authentication required",
|
|
5313
|
-
[3 /* NETWORK */]: "Network error (retryable)",
|
|
5314
|
-
[4 /* INVALID_INPUT */]: "Invalid input (bad chain, address, amount)",
|
|
5315
|
-
[5 /* RESOURCE_NOT_FOUND */]: "Resource not found (token, route)",
|
|
5316
|
-
[6 /* EXTERNAL_SERVICE */]: "External service error (retryable)",
|
|
5317
|
-
[7 /* UNKNOWN */]: "Unknown/unexpected error",
|
|
5318
|
-
[8 /* ACK_FAILED */]: "Broadcast succeeded but post-broadcast report failed \u2014 hash is valid, do NOT retry",
|
|
5319
|
-
[9 /* DUPLICATE_BROADCAST */]: "Duplicate broadcast refused (nothing sent) \u2014 retry with --force to override",
|
|
5320
|
-
[10 /* AGENT_TURN_BLOCKED */]: "agent ask: a fund-safety guardrail blocked the requested action",
|
|
5321
|
-
[11 /* AGENT_TURN_REFUSAL */]: "agent ask: the model refused or asked a clarifying question (no action taken)"
|
|
5322
5057
|
};
|
|
5323
|
-
var
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
retryable = false;
|
|
5058
|
+
var TxStatusTimeoutError = class extends VsigError {
|
|
5059
|
+
exitCode = 3 /* NETWORK */;
|
|
5060
|
+
code = "TX_STATUS_TIMEOUT";
|
|
5061
|
+
retryable = true;
|
|
5328
5062
|
constructor(message, hint, suggestions, context) {
|
|
5329
|
-
super(message);
|
|
5330
|
-
this.name = this.constructor.name;
|
|
5331
|
-
this.hint = hint;
|
|
5332
|
-
this.suggestions = suggestions;
|
|
5333
|
-
this.context = context;
|
|
5063
|
+
super(message, hint, suggestions, context);
|
|
5334
5064
|
}
|
|
5335
5065
|
};
|
|
5336
|
-
var
|
|
5337
|
-
exitCode =
|
|
5338
|
-
code = "
|
|
5066
|
+
var ExternalServiceError = class extends VsigError {
|
|
5067
|
+
exitCode = 6 /* EXTERNAL_SERVICE */;
|
|
5068
|
+
code = "EXTERNAL_SERVICE";
|
|
5069
|
+
retryable = true;
|
|
5339
5070
|
constructor(message, hint, suggestions) {
|
|
5340
5071
|
super(message, hint, suggestions);
|
|
5341
5072
|
}
|
|
5342
5073
|
};
|
|
5343
|
-
var
|
|
5344
|
-
exitCode =
|
|
5345
|
-
code = "
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
"vsig create"
|
|
5350
|
-
]);
|
|
5074
|
+
var PricingUnavailableError = class extends VsigError {
|
|
5075
|
+
exitCode = 6 /* EXTERNAL_SERVICE */;
|
|
5076
|
+
code = "PRICING_UNAVAILABLE";
|
|
5077
|
+
retryable = true;
|
|
5078
|
+
constructor(message, hint, suggestions) {
|
|
5079
|
+
super(message, hint, suggestions);
|
|
5351
5080
|
}
|
|
5352
5081
|
};
|
|
5353
|
-
var
|
|
5354
|
-
exitCode =
|
|
5355
|
-
code = "
|
|
5356
|
-
retryable = true;
|
|
5082
|
+
var ConfirmationRequiredError = class extends VsigError {
|
|
5083
|
+
exitCode = 12 /* CONFIRMATION_REQUIRED */;
|
|
5084
|
+
code = "CONFIRMATION_REQUIRED";
|
|
5357
5085
|
constructor(message, hint, suggestions) {
|
|
5358
5086
|
super(message, hint, suggestions);
|
|
5359
5087
|
}
|
|
5360
5088
|
};
|
|
5361
|
-
var
|
|
5362
|
-
exitCode =
|
|
5363
|
-
code = "
|
|
5364
|
-
constructor(message
|
|
5365
|
-
super(message
|
|
5089
|
+
var UnknownError = class extends VsigError {
|
|
5090
|
+
exitCode = 7 /* UNKNOWN */;
|
|
5091
|
+
code = "UNKNOWN_ERROR";
|
|
5092
|
+
constructor(message) {
|
|
5093
|
+
super(message);
|
|
5366
5094
|
}
|
|
5367
5095
|
};
|
|
5368
|
-
var
|
|
5369
|
-
exitCode =
|
|
5370
|
-
code = "
|
|
5371
|
-
constructor(message
|
|
5372
|
-
super(message,
|
|
5096
|
+
var DuplicateBroadcastRefusedError = class extends VsigError {
|
|
5097
|
+
exitCode = 9 /* DUPLICATE_BROADCAST */;
|
|
5098
|
+
code = "DUPLICATE_BROADCAST";
|
|
5099
|
+
constructor(message) {
|
|
5100
|
+
super(message, "An identical transaction was broadcast recently and has not definitively failed", [
|
|
5101
|
+
"Check its status with: vsig tx-status",
|
|
5102
|
+
"Re-broadcast anyway with: --force"
|
|
5103
|
+
]);
|
|
5373
5104
|
}
|
|
5374
5105
|
};
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
code
|
|
5378
|
-
|
|
5379
|
-
super(message, hint, suggestions, context);
|
|
5106
|
+
function classifyError(err) {
|
|
5107
|
+
if (err instanceof VsigError) return err;
|
|
5108
|
+
if (err.code === "DUPLICATE_BROADCAST") {
|
|
5109
|
+
return new DuplicateBroadcastRefusedError(err.message);
|
|
5380
5110
|
}
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5111
|
+
if (err instanceof VaultError) {
|
|
5112
|
+
if (err.code === VaultErrorCode.BalanceFetchFailed && err.originalError) {
|
|
5113
|
+
const inner = classifyError(err.originalError);
|
|
5114
|
+
if (!(inner instanceof UnknownError)) return inner;
|
|
5115
|
+
}
|
|
5116
|
+
switch (err.code) {
|
|
5117
|
+
case VaultErrorCode.UnsupportedChain:
|
|
5118
|
+
case VaultErrorCode.ChainNotSupported:
|
|
5119
|
+
return new InvalidChainError(err.message);
|
|
5120
|
+
case VaultErrorCode.NetworkError:
|
|
5121
|
+
case VaultErrorCode.BalanceFetchFailed:
|
|
5122
|
+
case VaultErrorCode.Timeout:
|
|
5123
|
+
return new NetworkError(err.message);
|
|
5124
|
+
case VaultErrorCode.InvalidAmount:
|
|
5125
|
+
return new InvalidInputError(err.message);
|
|
5126
|
+
case VaultErrorCode.InvalidConfig: {
|
|
5127
|
+
const lowerMsg = err.message.toLowerCase();
|
|
5128
|
+
if (lowerMsg.includes("unknown chain") || lowerMsg.includes("unsupported chain") || lowerMsg.includes("chain not supported")) {
|
|
5129
|
+
const chainMatch = err.message.match(/chain[:\s]*"([^"]+)"/i);
|
|
5130
|
+
return new InvalidChainError(
|
|
5131
|
+
err.message,
|
|
5132
|
+
void 0,
|
|
5133
|
+
void 0,
|
|
5134
|
+
chainMatch ? { chain: chainMatch[1] } : void 0
|
|
5135
|
+
);
|
|
5136
|
+
}
|
|
5137
|
+
return new UsageError(err.message);
|
|
5138
|
+
}
|
|
5139
|
+
case VaultErrorCode.UnsupportedToken:
|
|
5140
|
+
return new TokenNotFoundError(err.message);
|
|
5141
|
+
case VaultErrorCode.BroadcastFailed:
|
|
5142
|
+
return new ExternalServiceError(err.message, "Broadcast failed \u2014 the node may be temporarily unavailable", [
|
|
5143
|
+
"Retry the transaction"
|
|
5144
|
+
]);
|
|
5145
|
+
case VaultErrorCode.GasEstimationFailed:
|
|
5146
|
+
return new InvalidInputError(err.message, "Gas estimation failed \u2014 check balance and transaction params");
|
|
5147
|
+
case VaultErrorCode.SigningFailed:
|
|
5148
|
+
return new UnknownError(err.message);
|
|
5149
|
+
default:
|
|
5150
|
+
return new UnknownError(err.message);
|
|
5151
|
+
}
|
|
5387
5152
|
}
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5153
|
+
if (err instanceof VaultImportError) {
|
|
5154
|
+
switch (err.code) {
|
|
5155
|
+
case VaultImportErrorCode.PASSWORD_REQUIRED:
|
|
5156
|
+
case VaultImportErrorCode.INVALID_PASSWORD:
|
|
5157
|
+
return new AuthRequiredError(err.message);
|
|
5158
|
+
default:
|
|
5159
|
+
return new UsageError(err.message);
|
|
5160
|
+
}
|
|
5394
5161
|
}
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
constructor(message, hint, suggestions, context) {
|
|
5400
|
-
super(message, hint, suggestions, context);
|
|
5162
|
+
const msg = err.message.toLowerCase();
|
|
5163
|
+
if (msg.includes("unsupported chain") || msg.includes("invalid chain") || msg.includes("unknown chain")) {
|
|
5164
|
+
const chainMatch = err.message.match(/chain[:\s]*"([^"]+)"/i) || err.message.match(/chain[:\s]+(\S+)/i);
|
|
5165
|
+
return new InvalidChainError(err.message, void 0, void 0, chainMatch ? { chain: chainMatch[1] } : void 0);
|
|
5401
5166
|
}
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
code = "TX_NOT_FOUND";
|
|
5406
|
-
constructor(message, hint, suggestions, context) {
|
|
5407
|
-
super(message, hint, suggestions, context);
|
|
5167
|
+
if (msg.includes("invalid address") || msg.includes("bad address") || msg.includes("malformed address")) {
|
|
5168
|
+
const addrMatch = err.message.match(/(0x[a-fA-F0-9]+|bc1[a-z0-9]+|[13][a-km-zA-HJ-NP-Z1-9]+)/i);
|
|
5169
|
+
return new InvalidAddressError(err.message, void 0, void 0, addrMatch ? { address: addrMatch[1] } : void 0);
|
|
5408
5170
|
}
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
exitCode = 3 /* NETWORK */;
|
|
5412
|
-
code = "TX_STATUS_TIMEOUT";
|
|
5413
|
-
retryable = true;
|
|
5414
|
-
constructor(message, hint, suggestions, context) {
|
|
5415
|
-
super(message, hint, suggestions, context);
|
|
5171
|
+
if (msg.includes("insufficient") && msg.includes("balance")) {
|
|
5172
|
+
return new InsufficientBalanceError(err.message);
|
|
5416
5173
|
}
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
exitCode = 6 /* EXTERNAL_SERVICE */;
|
|
5420
|
-
code = "EXTERNAL_SERVICE";
|
|
5421
|
-
retryable = true;
|
|
5422
|
-
constructor(message, hint, suggestions) {
|
|
5423
|
-
super(message, hint, suggestions);
|
|
5174
|
+
if (msg.includes("no route") || msg.includes("no swap") || msg.includes("no provider")) {
|
|
5175
|
+
return new NoRouteError(err.message);
|
|
5424
5176
|
}
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
exitCode = 6 /* EXTERNAL_SERVICE */;
|
|
5428
|
-
code = "PRICING_UNAVAILABLE";
|
|
5429
|
-
retryable = true;
|
|
5430
|
-
constructor(message, hint, suggestions) {
|
|
5431
|
-
super(message, hint, suggestions);
|
|
5177
|
+
if (msg.includes("token not found") || msg.includes("unknown token")) {
|
|
5178
|
+
return new TokenNotFoundError(err.message);
|
|
5432
5179
|
}
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
exitCode = 7 /* UNKNOWN */;
|
|
5436
|
-
code = "UNKNOWN_ERROR";
|
|
5437
|
-
constructor(message) {
|
|
5438
|
-
super(message);
|
|
5180
|
+
if (msg.includes("pricing") || msg.includes("price unavailable") || msg.includes("price service")) {
|
|
5181
|
+
return new PricingUnavailableError(err.message);
|
|
5439
5182
|
}
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
exitCode = 9 /* DUPLICATE_BROADCAST */;
|
|
5443
|
-
code = "DUPLICATE_BROADCAST";
|
|
5444
|
-
constructor(message) {
|
|
5445
|
-
super(message, "An identical transaction was broadcast recently and has not definitively failed", [
|
|
5446
|
-
"Check its status with: vsig tx-status",
|
|
5447
|
-
"Re-broadcast anyway with: --force"
|
|
5448
|
-
]);
|
|
5183
|
+
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")) {
|
|
5184
|
+
return new NetworkError(err.message);
|
|
5449
5185
|
}
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
if (err
|
|
5454
|
-
|
|
5186
|
+
return new UnknownError(err.message);
|
|
5187
|
+
}
|
|
5188
|
+
function toErrorJson(err) {
|
|
5189
|
+
if (err instanceof VsigError) {
|
|
5190
|
+
const json = {
|
|
5191
|
+
success: false,
|
|
5192
|
+
v: 1,
|
|
5193
|
+
error: {
|
|
5194
|
+
code: err.code,
|
|
5195
|
+
exitCode: err.exitCode,
|
|
5196
|
+
message: err.message,
|
|
5197
|
+
hint: err.hint,
|
|
5198
|
+
retryable: err.retryable
|
|
5199
|
+
}
|
|
5200
|
+
};
|
|
5201
|
+
if (err.suggestions?.length) json.error.suggestions = err.suggestions;
|
|
5202
|
+
if (err.context) json.error.context = err.context;
|
|
5203
|
+
return json;
|
|
5204
|
+
}
|
|
5205
|
+
return {
|
|
5206
|
+
success: false,
|
|
5207
|
+
v: 1,
|
|
5208
|
+
error: {
|
|
5209
|
+
code: "UNKNOWN_ERROR",
|
|
5210
|
+
exitCode: 7 /* UNKNOWN */,
|
|
5211
|
+
message: err.message,
|
|
5212
|
+
retryable: false
|
|
5213
|
+
}
|
|
5214
|
+
};
|
|
5215
|
+
}
|
|
5216
|
+
|
|
5217
|
+
// src/lib/output.ts
|
|
5218
|
+
var silentMode = false;
|
|
5219
|
+
var outputFormat = "table";
|
|
5220
|
+
var nonInteractive = false;
|
|
5221
|
+
var quietMode = false;
|
|
5222
|
+
var fieldFilter;
|
|
5223
|
+
function setSilentMode(silent) {
|
|
5224
|
+
silentMode = silent;
|
|
5225
|
+
}
|
|
5226
|
+
function isSilent() {
|
|
5227
|
+
return silentMode;
|
|
5228
|
+
}
|
|
5229
|
+
function setNonInteractive(value) {
|
|
5230
|
+
nonInteractive = value;
|
|
5231
|
+
}
|
|
5232
|
+
function isNonInteractive() {
|
|
5233
|
+
return nonInteractive;
|
|
5234
|
+
}
|
|
5235
|
+
function resolveNonInteractive(explicit, stdout = process.stdout, stdin = process.stdin) {
|
|
5236
|
+
return explicit || !stdout.isTTY || !stdin.isTTY;
|
|
5237
|
+
}
|
|
5238
|
+
function requireInteractive(hint) {
|
|
5239
|
+
if (nonInteractive) {
|
|
5240
|
+
throw new ConfirmationRequiredError(
|
|
5241
|
+
"Interactive input required but the session is non-interactive (piped/redirected stdout or stdin, or --non-interactive/--ci).",
|
|
5242
|
+
hint
|
|
5243
|
+
);
|
|
5244
|
+
}
|
|
5245
|
+
}
|
|
5246
|
+
function setQuiet(value) {
|
|
5247
|
+
quietMode = value;
|
|
5248
|
+
}
|
|
5249
|
+
function setFields(fields) {
|
|
5250
|
+
fieldFilter = fields;
|
|
5251
|
+
}
|
|
5252
|
+
function initOutputMode(options) {
|
|
5253
|
+
outputFormat = options.output ?? "table";
|
|
5254
|
+
silentMode = options.silent ?? process.env.VULTISIG_SILENT === "1";
|
|
5255
|
+
if (outputFormat === "json") {
|
|
5256
|
+
silentMode = true;
|
|
5257
|
+
}
|
|
5258
|
+
}
|
|
5259
|
+
function isJsonOutput() {
|
|
5260
|
+
return outputFormat === "json";
|
|
5261
|
+
}
|
|
5262
|
+
function stripEmpty(data) {
|
|
5263
|
+
if (Array.isArray(data)) return data.map(stripEmpty);
|
|
5264
|
+
if (data !== null && typeof data === "object") {
|
|
5265
|
+
return Object.fromEntries(
|
|
5266
|
+
Object.entries(data).filter(([, v]) => v != null && v !== "").map(([k, v]) => [k, stripEmpty(v)])
|
|
5267
|
+
);
|
|
5268
|
+
}
|
|
5269
|
+
return data;
|
|
5270
|
+
}
|
|
5271
|
+
function filterFields(data, fields) {
|
|
5272
|
+
if (!fields.length) return data;
|
|
5273
|
+
if (Array.isArray(data)) return data.map((item) => filterFields(item, fields));
|
|
5274
|
+
if (data !== null && typeof data === "object") {
|
|
5275
|
+
const entries = Object.entries(data);
|
|
5276
|
+
const matched = entries.filter(([k]) => fields.includes(k));
|
|
5277
|
+
if (matched.length > 0) return Object.fromEntries(matched);
|
|
5278
|
+
const recursed = entries.map(([k, v]) => [k, filterFields(v, fields)]);
|
|
5279
|
+
return Object.fromEntries(recursed);
|
|
5280
|
+
}
|
|
5281
|
+
return data;
|
|
5282
|
+
}
|
|
5283
|
+
function collectKeys(data) {
|
|
5284
|
+
const keys = /* @__PURE__ */ new Set();
|
|
5285
|
+
if (Array.isArray(data)) {
|
|
5286
|
+
for (const item of data) {
|
|
5287
|
+
if (item !== null && typeof item === "object") {
|
|
5288
|
+
for (const k of Object.keys(item)) keys.add(k);
|
|
5289
|
+
}
|
|
5290
|
+
}
|
|
5291
|
+
} else if (data !== null && typeof data === "object") {
|
|
5292
|
+
for (const [k, v] of Object.entries(data)) {
|
|
5293
|
+
keys.add(k);
|
|
5294
|
+
if (v !== null && typeof v === "object") {
|
|
5295
|
+
for (const nested of collectKeys(v)) keys.add(nested);
|
|
5296
|
+
}
|
|
5297
|
+
}
|
|
5298
|
+
}
|
|
5299
|
+
return keys;
|
|
5300
|
+
}
|
|
5301
|
+
function warnInvalidFields(data, fields) {
|
|
5302
|
+
const available = collectKeys(data);
|
|
5303
|
+
if (available.size === 0) return;
|
|
5304
|
+
const invalid = fields.filter((f) => !available.has(f));
|
|
5305
|
+
if (invalid.length > 0) {
|
|
5306
|
+
process.stderr.write(`Warning: unknown field(s): ${invalid.join(", ")}. Available: ${[...available].join(", ")}
|
|
5307
|
+
`);
|
|
5308
|
+
}
|
|
5309
|
+
}
|
|
5310
|
+
function applyOutputTransforms(data) {
|
|
5311
|
+
let out = quietMode ? stripEmpty(data) : data;
|
|
5312
|
+
if (fieldFilter?.length) {
|
|
5313
|
+
warnInvalidFields(out, fieldFilter);
|
|
5314
|
+
out = filterFields(out, fieldFilter);
|
|
5315
|
+
}
|
|
5316
|
+
return out;
|
|
5317
|
+
}
|
|
5318
|
+
function bigIntReplacer(_key, value) {
|
|
5319
|
+
return typeof value === "bigint" ? value.toString() : value;
|
|
5320
|
+
}
|
|
5321
|
+
function outputJson(data) {
|
|
5322
|
+
const transformed = applyOutputTransforms(data);
|
|
5323
|
+
process.stdout.write(JSON.stringify({ success: true, v: 1, data: transformed }, bigIntReplacer, 2) + "\n");
|
|
5324
|
+
}
|
|
5325
|
+
function outputErrorJson(errJson) {
|
|
5326
|
+
const transformed = applyOutputTransforms(errJson);
|
|
5327
|
+
process.stdout.write(JSON.stringify(transformed, bigIntReplacer, 2) + "\n");
|
|
5328
|
+
}
|
|
5329
|
+
function info(message) {
|
|
5330
|
+
if (!silentMode) {
|
|
5331
|
+
console.log(chalk.blue(message));
|
|
5332
|
+
}
|
|
5333
|
+
}
|
|
5334
|
+
function success(message) {
|
|
5335
|
+
if (!silentMode) {
|
|
5336
|
+
console.log(chalk.green(`
|
|
5337
|
+
${message}`));
|
|
5338
|
+
}
|
|
5339
|
+
}
|
|
5340
|
+
function warn(message) {
|
|
5341
|
+
if (!silentMode) {
|
|
5342
|
+
console.log(chalk.yellow(message));
|
|
5343
|
+
}
|
|
5344
|
+
}
|
|
5345
|
+
function error(message) {
|
|
5346
|
+
console.error(chalk.red(`
|
|
5347
|
+
${message}`));
|
|
5348
|
+
}
|
|
5349
|
+
function printResult(message) {
|
|
5350
|
+
console.log(message);
|
|
5351
|
+
}
|
|
5352
|
+
function printTable(data) {
|
|
5353
|
+
console.table(data);
|
|
5354
|
+
}
|
|
5355
|
+
function printError(message) {
|
|
5356
|
+
console.error(chalk.red(message));
|
|
5357
|
+
}
|
|
5358
|
+
var activeSpinners = /* @__PURE__ */ new Set();
|
|
5359
|
+
function stopAllSpinners() {
|
|
5360
|
+
for (const spinner of activeSpinners) {
|
|
5361
|
+
spinner.stop();
|
|
5362
|
+
}
|
|
5363
|
+
activeSpinners.clear();
|
|
5364
|
+
}
|
|
5365
|
+
function createNoopSpinner(text) {
|
|
5366
|
+
const noopSpinner = {
|
|
5367
|
+
text,
|
|
5368
|
+
start() {
|
|
5369
|
+
return this;
|
|
5370
|
+
},
|
|
5371
|
+
stop() {
|
|
5372
|
+
activeSpinners.delete(this);
|
|
5373
|
+
return this;
|
|
5374
|
+
},
|
|
5375
|
+
succeed() {
|
|
5376
|
+
activeSpinners.delete(this);
|
|
5377
|
+
return this;
|
|
5378
|
+
},
|
|
5379
|
+
fail(text2) {
|
|
5380
|
+
activeSpinners.delete(this);
|
|
5381
|
+
if (text2) printError(text2);
|
|
5382
|
+
return this;
|
|
5383
|
+
},
|
|
5384
|
+
warn() {
|
|
5385
|
+
activeSpinners.delete(this);
|
|
5386
|
+
return this;
|
|
5387
|
+
},
|
|
5388
|
+
info() {
|
|
5389
|
+
activeSpinners.delete(this);
|
|
5390
|
+
return this;
|
|
5391
|
+
}
|
|
5392
|
+
};
|
|
5393
|
+
return noopSpinner;
|
|
5394
|
+
}
|
|
5395
|
+
function wrapOraSpinner(spinner) {
|
|
5396
|
+
const originalStop = spinner.stop.bind(spinner);
|
|
5397
|
+
const originalSucceed = spinner.succeed.bind(spinner);
|
|
5398
|
+
const originalFail = spinner.fail.bind(spinner);
|
|
5399
|
+
const originalWarn = spinner.warn.bind(spinner);
|
|
5400
|
+
const originalInfo = spinner.info.bind(spinner);
|
|
5401
|
+
spinner.stop = () => {
|
|
5402
|
+
activeSpinners.delete(spinner);
|
|
5403
|
+
return originalStop();
|
|
5404
|
+
};
|
|
5405
|
+
spinner.succeed = (text) => {
|
|
5406
|
+
activeSpinners.delete(spinner);
|
|
5407
|
+
return originalSucceed(text);
|
|
5408
|
+
};
|
|
5409
|
+
spinner.fail = (text) => {
|
|
5410
|
+
activeSpinners.delete(spinner);
|
|
5411
|
+
return originalFail(text);
|
|
5412
|
+
};
|
|
5413
|
+
spinner.warn = (text) => {
|
|
5414
|
+
activeSpinners.delete(spinner);
|
|
5415
|
+
return originalWarn(text);
|
|
5416
|
+
};
|
|
5417
|
+
spinner.info = (text) => {
|
|
5418
|
+
activeSpinners.delete(spinner);
|
|
5419
|
+
return originalInfo(text);
|
|
5420
|
+
};
|
|
5421
|
+
return spinner;
|
|
5422
|
+
}
|
|
5423
|
+
function createSpinner(text) {
|
|
5424
|
+
if (silentMode) {
|
|
5425
|
+
const spinner2 = createNoopSpinner(text);
|
|
5426
|
+
activeSpinners.add(spinner2);
|
|
5427
|
+
return spinner2;
|
|
5428
|
+
}
|
|
5429
|
+
const spinner = wrapOraSpinner(ora({ text, stream: process.stderr }).start());
|
|
5430
|
+
activeSpinners.add(spinner);
|
|
5431
|
+
return spinner;
|
|
5432
|
+
}
|
|
5433
|
+
|
|
5434
|
+
// src/lib/prompt.ts
|
|
5435
|
+
import inquirer from "inquirer";
|
|
5436
|
+
var promptOutput = process.stderr;
|
|
5437
|
+
var promptModule = inquirer.createPromptModule({ output: promptOutput });
|
|
5438
|
+
function deriveHint(questions) {
|
|
5439
|
+
const list = Array.isArray(questions) ? questions : [questions];
|
|
5440
|
+
const needsPassword = list.some(
|
|
5441
|
+
(q) => q != null && typeof q === "object" && q.type === "password"
|
|
5442
|
+
);
|
|
5443
|
+
return needsPassword ? 'Provide the password via --password, the VAULT_PASSWORD env var, or "vsig auth setup" (keyring).' : "Supply the required value(s) via flags (e.g. --yes/--confirm, --code, --chain/--address/--name) so no prompt is needed.";
|
|
5444
|
+
}
|
|
5445
|
+
var prompt = new Proxy(promptModule, {
|
|
5446
|
+
apply(target, thisArg, argArray) {
|
|
5447
|
+
requireInteractive(deriveHint(argArray[0]));
|
|
5448
|
+
return Reflect.apply(target, thisArg, argArray);
|
|
5449
|
+
}
|
|
5450
|
+
});
|
|
5451
|
+
|
|
5452
|
+
// src/core/credential-store.ts
|
|
5453
|
+
import {
|
|
5454
|
+
_resetAll,
|
|
5455
|
+
clearCredentials,
|
|
5456
|
+
getCredentialsPath,
|
|
5457
|
+
getDecryptionPassword,
|
|
5458
|
+
getServerPassword,
|
|
5459
|
+
getStoredServerPassword,
|
|
5460
|
+
isUsingFileFallback,
|
|
5461
|
+
setDecryptionPassword,
|
|
5462
|
+
setFilePassphrase,
|
|
5463
|
+
setServerPassword
|
|
5464
|
+
} from "@vultisig/client-shared";
|
|
5465
|
+
|
|
5466
|
+
// src/core/password-manager.ts
|
|
5467
|
+
var passwordCache = /* @__PURE__ */ new Map();
|
|
5468
|
+
function cachePassword(vaultIdOrName, password) {
|
|
5469
|
+
passwordCache.set(vaultIdOrName, password);
|
|
5470
|
+
}
|
|
5471
|
+
function getCachedPassword(vaultId, vaultName) {
|
|
5472
|
+
if (vaultName && passwordCache.has(vaultName)) return passwordCache.get(vaultName);
|
|
5473
|
+
if (passwordCache.has(vaultId)) return passwordCache.get(vaultId);
|
|
5474
|
+
return null;
|
|
5475
|
+
}
|
|
5476
|
+
function clearCachedPassword(vaultIdOrName) {
|
|
5477
|
+
if (vaultIdOrName) {
|
|
5478
|
+
passwordCache.delete(vaultIdOrName);
|
|
5479
|
+
} else {
|
|
5480
|
+
passwordCache.clear();
|
|
5455
5481
|
}
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
return new NetworkError(err.message);
|
|
5469
|
-
case VaultErrorCode.InvalidAmount:
|
|
5470
|
-
return new InvalidInputError(err.message);
|
|
5471
|
-
case VaultErrorCode.InvalidConfig: {
|
|
5472
|
-
const lowerMsg = err.message.toLowerCase();
|
|
5473
|
-
if (lowerMsg.includes("unknown chain") || lowerMsg.includes("unsupported chain") || lowerMsg.includes("chain not supported")) {
|
|
5474
|
-
const chainMatch = err.message.match(/chain[:\s]*"([^"]+)"/i);
|
|
5475
|
-
return new InvalidChainError(
|
|
5476
|
-
err.message,
|
|
5477
|
-
void 0,
|
|
5478
|
-
void 0,
|
|
5479
|
-
chainMatch ? { chain: chainMatch[1] } : void 0
|
|
5480
|
-
);
|
|
5481
|
-
}
|
|
5482
|
-
return new UsageError(err.message);
|
|
5482
|
+
}
|
|
5483
|
+
function parseVaultPasswords() {
|
|
5484
|
+
const passwordMap = /* @__PURE__ */ new Map();
|
|
5485
|
+
const passwordsEnv = process.env.VAULT_PASSWORDS;
|
|
5486
|
+
if (passwordsEnv) {
|
|
5487
|
+
const pairs = passwordsEnv.trim().split(/\s+/);
|
|
5488
|
+
for (const pair of pairs) {
|
|
5489
|
+
const colonIndex = pair.indexOf(":");
|
|
5490
|
+
if (colonIndex > 0) {
|
|
5491
|
+
const vaultKey = pair.substring(0, colonIndex);
|
|
5492
|
+
const password = pair.substring(colonIndex + 1);
|
|
5493
|
+
passwordMap.set(vaultKey, password);
|
|
5483
5494
|
}
|
|
5484
|
-
case VaultErrorCode.UnsupportedToken:
|
|
5485
|
-
return new TokenNotFoundError(err.message);
|
|
5486
|
-
case VaultErrorCode.BroadcastFailed:
|
|
5487
|
-
return new ExternalServiceError(err.message, "Broadcast failed \u2014 the node may be temporarily unavailable", [
|
|
5488
|
-
"Retry the transaction"
|
|
5489
|
-
]);
|
|
5490
|
-
case VaultErrorCode.GasEstimationFailed:
|
|
5491
|
-
return new InvalidInputError(err.message, "Gas estimation failed \u2014 check balance and transaction params");
|
|
5492
|
-
case VaultErrorCode.SigningFailed:
|
|
5493
|
-
return new UnknownError(err.message);
|
|
5494
|
-
default:
|
|
5495
|
-
return new UnknownError(err.message);
|
|
5496
5495
|
}
|
|
5497
5496
|
}
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
return new UsageError(err.message);
|
|
5505
|
-
}
|
|
5497
|
+
return passwordMap;
|
|
5498
|
+
}
|
|
5499
|
+
function getPasswordFromEnv(vaultId, vaultName) {
|
|
5500
|
+
const vaultPasswords = parseVaultPasswords();
|
|
5501
|
+
if (vaultName && vaultPasswords.has(vaultName)) {
|
|
5502
|
+
return vaultPasswords.get(vaultName);
|
|
5506
5503
|
}
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
const chainMatch = err.message.match(/chain[:\s]*"([^"]+)"/i) || err.message.match(/chain[:\s]+(\S+)/i);
|
|
5510
|
-
return new InvalidChainError(err.message, void 0, void 0, chainMatch ? { chain: chainMatch[1] } : void 0);
|
|
5504
|
+
if (vaultPasswords.has(vaultId)) {
|
|
5505
|
+
return vaultPasswords.get(vaultId);
|
|
5511
5506
|
}
|
|
5512
|
-
if (
|
|
5513
|
-
|
|
5514
|
-
return new InvalidAddressError(err.message, void 0, void 0, addrMatch ? { address: addrMatch[1] } : void 0);
|
|
5507
|
+
if (process.env.VAULT_PASSWORD || process.env.VULTISIG_PASSWORD) {
|
|
5508
|
+
return process.env.VAULT_PASSWORD || process.env.VULTISIG_PASSWORD;
|
|
5515
5509
|
}
|
|
5516
|
-
|
|
5517
|
-
|
|
5510
|
+
return null;
|
|
5511
|
+
}
|
|
5512
|
+
async function promptForPassword(vaultName, vaultId) {
|
|
5513
|
+
requireInteractive(
|
|
5514
|
+
'Use --password flag, VAULT_PASSWORD env var, or "vsig auth setup" to store credentials in keyring.'
|
|
5515
|
+
);
|
|
5516
|
+
const displayName = vaultName || vaultId || "vault";
|
|
5517
|
+
const { password } = await prompt([
|
|
5518
|
+
{
|
|
5519
|
+
type: "password",
|
|
5520
|
+
name: "password",
|
|
5521
|
+
message: `Enter password for vault "${displayName}":`,
|
|
5522
|
+
mask: "*"
|
|
5523
|
+
}
|
|
5524
|
+
]);
|
|
5525
|
+
return password;
|
|
5526
|
+
}
|
|
5527
|
+
async function resolvePasswordNonInteractive(vaultId, vaultName) {
|
|
5528
|
+
const cachedPassword = getCachedPassword(vaultId, vaultName);
|
|
5529
|
+
if (cachedPassword) {
|
|
5530
|
+
return cachedPassword;
|
|
5518
5531
|
}
|
|
5519
|
-
|
|
5520
|
-
|
|
5532
|
+
try {
|
|
5533
|
+
const keyringPassword = await getServerPassword(vaultId);
|
|
5534
|
+
if (keyringPassword) {
|
|
5535
|
+
cachePassword(vaultId, keyringPassword);
|
|
5536
|
+
if (vaultName) cachePassword(vaultName, keyringPassword);
|
|
5537
|
+
return keyringPassword;
|
|
5538
|
+
}
|
|
5539
|
+
} catch {
|
|
5521
5540
|
}
|
|
5522
|
-
|
|
5523
|
-
|
|
5541
|
+
const envPassword = getPasswordFromEnv(vaultId, vaultName);
|
|
5542
|
+
if (envPassword) {
|
|
5543
|
+
cachePassword(vaultId, envPassword);
|
|
5544
|
+
if (vaultName) cachePassword(vaultName, envPassword);
|
|
5545
|
+
return envPassword;
|
|
5524
5546
|
}
|
|
5525
|
-
|
|
5526
|
-
|
|
5547
|
+
return null;
|
|
5548
|
+
}
|
|
5549
|
+
async function getPassword(vaultId, vaultName) {
|
|
5550
|
+
const resolved = await resolvePasswordNonInteractive(vaultId, vaultName);
|
|
5551
|
+
if (resolved) {
|
|
5552
|
+
return resolved;
|
|
5527
5553
|
}
|
|
5528
|
-
if (
|
|
5529
|
-
|
|
5554
|
+
if (isSilent() || isJsonOutput() || isNonInteractive()) {
|
|
5555
|
+
throw new Error(
|
|
5556
|
+
"Password required but not provided. Set VAULT_PASSWORD or VAULT_PASSWORDS environment variable, or use --password flag."
|
|
5557
|
+
);
|
|
5530
5558
|
}
|
|
5531
|
-
|
|
5559
|
+
const password = await promptForPassword(vaultName, vaultId);
|
|
5560
|
+
cachePassword(vaultId, password);
|
|
5561
|
+
if (vaultName) cachePassword(vaultName, password);
|
|
5562
|
+
return password;
|
|
5532
5563
|
}
|
|
5533
|
-
function
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
success: false,
|
|
5537
|
-
v: 1,
|
|
5538
|
-
error: {
|
|
5539
|
-
code: err.code,
|
|
5540
|
-
exitCode: err.exitCode,
|
|
5541
|
-
message: err.message,
|
|
5542
|
-
hint: err.hint,
|
|
5543
|
-
retryable: err.retryable
|
|
5544
|
-
}
|
|
5545
|
-
};
|
|
5546
|
-
if (err.suggestions?.length) json.error.suggestions = err.suggestions;
|
|
5547
|
-
if (err.context) json.error.context = err.context;
|
|
5548
|
-
return json;
|
|
5549
|
-
}
|
|
5550
|
-
return {
|
|
5551
|
-
success: false,
|
|
5552
|
-
v: 1,
|
|
5553
|
-
error: {
|
|
5554
|
-
code: "UNKNOWN_ERROR",
|
|
5555
|
-
exitCode: 7 /* UNKNOWN */,
|
|
5556
|
-
message: err.message,
|
|
5557
|
-
retryable: false
|
|
5558
|
-
}
|
|
5564
|
+
function createPasswordCallback() {
|
|
5565
|
+
return async (vaultId, vaultName) => {
|
|
5566
|
+
return getPassword(vaultId, vaultName);
|
|
5559
5567
|
};
|
|
5560
5568
|
}
|
|
5569
|
+
async function ensureVaultUnlocked(vault, password) {
|
|
5570
|
+
if (!vault.isEncrypted || vault.isUnlocked()) {
|
|
5571
|
+
return;
|
|
5572
|
+
}
|
|
5573
|
+
const resolvedPassword = password || await getPassword(vault.id, vault.name);
|
|
5574
|
+
await vault.unlock(resolvedPassword);
|
|
5575
|
+
}
|
|
5576
|
+
|
|
5577
|
+
// src/adapters/cli-context.ts
|
|
5578
|
+
var CLIContext = class extends BaseCommandContext {
|
|
5579
|
+
get isInteractive() {
|
|
5580
|
+
return false;
|
|
5581
|
+
}
|
|
5582
|
+
/**
|
|
5583
|
+
* Get password for a vault
|
|
5584
|
+
* In CLI mode, we check env vars first, then prompt
|
|
5585
|
+
* No caching since each command runs independently
|
|
5586
|
+
*/
|
|
5587
|
+
async getPassword(vaultId, vaultName) {
|
|
5588
|
+
return getPassword(vaultId, vaultName);
|
|
5589
|
+
}
|
|
5590
|
+
};
|
|
5561
5591
|
|
|
5562
5592
|
// src/adapters/cli-runner.ts
|
|
5563
5593
|
function withExit(handler) {
|
|
@@ -5588,7 +5618,6 @@ import { fiatCurrencies, fiatCurrencyNameRecord as fiatCurrencyNameRecord2 } fro
|
|
|
5588
5618
|
// src/ui.ts
|
|
5589
5619
|
import { fiatCurrencyNameRecord, Vultisig } from "@vultisig/sdk";
|
|
5590
5620
|
import chalk2 from "chalk";
|
|
5591
|
-
import inquirer2 from "inquirer";
|
|
5592
5621
|
function displayBalance(chain, balance, _raw = false) {
|
|
5593
5622
|
printResult(chalk2.cyan(`
|
|
5594
5623
|
${chain} Balance:`));
|
|
@@ -5707,7 +5736,7 @@ function displayVaultsList(vaults, activeVault) {
|
|
|
5707
5736
|
}
|
|
5708
5737
|
async function confirmTransaction() {
|
|
5709
5738
|
requireInteractive("Use --yes to skip confirmation, or --password to provide password non-interactively.");
|
|
5710
|
-
const { confirmed } = await
|
|
5739
|
+
const { confirmed } = await prompt([
|
|
5711
5740
|
{
|
|
5712
5741
|
type: "confirm",
|
|
5713
5742
|
name: "confirmed",
|
|
@@ -5827,7 +5856,7 @@ Total: ${chains.length} chains`);
|
|
|
5827
5856
|
}
|
|
5828
5857
|
async function confirmSwap() {
|
|
5829
5858
|
requireInteractive("Use --yes to skip confirmation, or --password to provide password non-interactively.");
|
|
5830
|
-
const { confirmed } = await
|
|
5859
|
+
const { confirmed } = await prompt([
|
|
5831
5860
|
{
|
|
5832
5861
|
type: "confirm",
|
|
5833
5862
|
name: "confirmed",
|
|
@@ -5839,6 +5868,10 @@ async function confirmSwap() {
|
|
|
5839
5868
|
}
|
|
5840
5869
|
|
|
5841
5870
|
// src/commands/balance.ts
|
|
5871
|
+
function conciseError(err) {
|
|
5872
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5873
|
+
return message.split("\n")[0].trim() || "Unknown error";
|
|
5874
|
+
}
|
|
5842
5875
|
async function executeBalance(ctx2, options = {}) {
|
|
5843
5876
|
const vault = await ctx2.ensureActiveVault();
|
|
5844
5877
|
const spinner = createSpinner("Loading balances...");
|
|
@@ -5876,24 +5909,50 @@ async function executePortfolio(ctx2, options = {}) {
|
|
|
5876
5909
|
const spinner = createSpinner(`Loading portfolio in ${currencyName}...`);
|
|
5877
5910
|
const totalValue = await vault.getTotalValue(currency);
|
|
5878
5911
|
const chains = vault.chains;
|
|
5879
|
-
const
|
|
5912
|
+
const results = await Promise.all(
|
|
5880
5913
|
chains.map(async (chain) => {
|
|
5881
|
-
|
|
5914
|
+
let balance;
|
|
5915
|
+
try {
|
|
5916
|
+
balance = await vault.balance(chain);
|
|
5917
|
+
} catch (err) {
|
|
5918
|
+
return { failure: { chain, stage: "balance", error: conciseError(err) } };
|
|
5919
|
+
}
|
|
5882
5920
|
try {
|
|
5883
5921
|
const value = await vault.getValue(chain, void 0, currency);
|
|
5884
|
-
return { chain, balance, value };
|
|
5885
|
-
} catch {
|
|
5886
|
-
return { chain, balance };
|
|
5922
|
+
return { entry: { chain, balance, value } };
|
|
5923
|
+
} catch (err) {
|
|
5924
|
+
return { entry: { chain, balance }, failure: { chain, stage: "value", error: conciseError(err) } };
|
|
5887
5925
|
}
|
|
5888
5926
|
})
|
|
5889
5927
|
);
|
|
5928
|
+
const chainBalances = [];
|
|
5929
|
+
const failures = [];
|
|
5930
|
+
for (const result of results) {
|
|
5931
|
+
if (result.entry) chainBalances.push(result.entry);
|
|
5932
|
+
if (result.failure) failures.push(result.failure);
|
|
5933
|
+
}
|
|
5934
|
+
if (failures.length > 0 && chainBalances.length === 0) {
|
|
5935
|
+
spinner.fail("Portfolio failed to load");
|
|
5936
|
+
throw new NetworkError(
|
|
5937
|
+
`Failed to load balances for all ${failures.length} chain(s): ${failures.map((f) => `${f.chain} (${f.error})`).join("; ")}`,
|
|
5938
|
+
"All chain balance fetches failed \u2014 likely a network/RPC issue",
|
|
5939
|
+
["Check your internet connection", "Retry in a few moments"]
|
|
5940
|
+
);
|
|
5941
|
+
}
|
|
5890
5942
|
const portfolio = { totalValue, chainBalances };
|
|
5891
5943
|
spinner.succeed("Portfolio loaded");
|
|
5892
5944
|
if (isJsonOutput()) {
|
|
5893
|
-
outputJson({ portfolio, currency });
|
|
5945
|
+
outputJson({ portfolio, currency, failures });
|
|
5894
5946
|
return;
|
|
5895
5947
|
}
|
|
5896
5948
|
displayPortfolio(portfolio, currency, options.raw ?? false);
|
|
5949
|
+
if (failures.length > 0) {
|
|
5950
|
+
warn(`
|
|
5951
|
+
Warning: ${failures.length} chain(s) failed to load fully:`);
|
|
5952
|
+
for (const f of failures) {
|
|
5953
|
+
warn(` - ${f.chain} (${f.stage}): ${f.error}`);
|
|
5954
|
+
}
|
|
5955
|
+
}
|
|
5897
5956
|
}
|
|
5898
5957
|
|
|
5899
5958
|
// src/commands/chains.ts
|
|
@@ -6577,7 +6636,6 @@ async function executeAddresses(ctx2) {
|
|
|
6577
6636
|
|
|
6578
6637
|
// src/commands/tokens.ts
|
|
6579
6638
|
import chalk4 from "chalk";
|
|
6580
|
-
import inquirer3 from "inquirer";
|
|
6581
6639
|
async function executeTokens(ctx2, options) {
|
|
6582
6640
|
await ctx2.ensureActiveVault();
|
|
6583
6641
|
if (options.discover) {
|
|
@@ -6616,7 +6674,7 @@ async function executeTokens(ctx2, options) {
|
|
|
6616
6674
|
}
|
|
6617
6675
|
if (prompts.length > 0) {
|
|
6618
6676
|
requireInteractive("Provide --symbol, --name, and --decimals flags for non-interactive token addition.");
|
|
6619
|
-
const answers = await
|
|
6677
|
+
const answers = await prompt(prompts);
|
|
6620
6678
|
symbol = symbol || answers.symbol?.trim();
|
|
6621
6679
|
name = name || answers.name?.trim();
|
|
6622
6680
|
decimals = decimals ?? answers.decimals;
|
|
@@ -6750,6 +6808,12 @@ async function executeSend(ctx2, params) {
|
|
|
6750
6808
|
return sendTransaction(vault, params);
|
|
6751
6809
|
}
|
|
6752
6810
|
async function sendTransaction(vault, params) {
|
|
6811
|
+
if (!params.dryRun && !params.yes && isNonInteractive()) {
|
|
6812
|
+
throw new ConfirmationRequiredError(
|
|
6813
|
+
"Transaction requires confirmation.",
|
|
6814
|
+
"Pass --yes to confirm, or --dry-run to preview without signing."
|
|
6815
|
+
);
|
|
6816
|
+
}
|
|
6753
6817
|
const prepareSpinner = createSpinner("Preparing transaction...");
|
|
6754
6818
|
const dryResult = await vault.send({
|
|
6755
6819
|
chain: params.chain,
|
|
@@ -6801,9 +6865,6 @@ Dry-run preview:`);
|
|
|
6801
6865
|
displayTransactionPreview(address, params.to, dryResult.total, balance.symbol, params.chain, params.memo, gas);
|
|
6802
6866
|
}
|
|
6803
6867
|
if (!params.yes) {
|
|
6804
|
-
if (isNonInteractive()) {
|
|
6805
|
-
throw new Error("Transaction requires confirmation. Use --yes to skip, or --dry-run to preview.");
|
|
6806
|
-
}
|
|
6807
6868
|
const confirmed = await confirmTransaction();
|
|
6808
6869
|
if (!confirmed) {
|
|
6809
6870
|
warn("Transaction cancelled");
|
|
@@ -6900,6 +6961,12 @@ async function executeExecute(ctx2, params) {
|
|
|
6900
6961
|
return executeContractTransaction(vault, params, chainConfig, msg, funds);
|
|
6901
6962
|
}
|
|
6902
6963
|
async function executeContractTransaction(vault, params, chainConfig, msg, funds) {
|
|
6964
|
+
if (!params.dryRun && !params.yes && isNonInteractive()) {
|
|
6965
|
+
throw new ConfirmationRequiredError(
|
|
6966
|
+
"Transaction requires confirmation.",
|
|
6967
|
+
"Pass --yes to confirm, or --dry-run to preview without signing."
|
|
6968
|
+
);
|
|
6969
|
+
}
|
|
6903
6970
|
const prepareSpinner = createSpinner("Preparing contract execution...");
|
|
6904
6971
|
const address = await vault.address(params.chain);
|
|
6905
6972
|
prepareSpinner.succeed("Transaction prepared");
|
|
@@ -6955,9 +7022,6 @@ async function executeContractTransaction(vault, params, chainConfig, msg, funds
|
|
|
6955
7022
|
info("\u2501".repeat(50));
|
|
6956
7023
|
}
|
|
6957
7024
|
if (!params.yes) {
|
|
6958
|
-
if (isNonInteractive()) {
|
|
6959
|
-
throw new Error("Transaction requires confirmation. Use --yes to skip, or --dry-run to preview.");
|
|
6960
|
-
}
|
|
6961
7025
|
const confirmed = await confirmTransaction();
|
|
6962
7026
|
if (!confirmed) {
|
|
6963
7027
|
warn("Transaction cancelled");
|
|
@@ -7288,7 +7352,6 @@ var import_qrcode_terminal3 = __toESM(require_main(), 1);
|
|
|
7288
7352
|
import { FastVault } from "@vultisig/sdk";
|
|
7289
7353
|
import chalk5 from "chalk";
|
|
7290
7354
|
import { promises as fs } from "fs";
|
|
7291
|
-
import inquirer4 from "inquirer";
|
|
7292
7355
|
import path from "path";
|
|
7293
7356
|
function withAbortSignal(promise, signal) {
|
|
7294
7357
|
if (!signal) return promise;
|
|
@@ -7301,10 +7364,10 @@ function withAbortSignal(promise, signal) {
|
|
|
7301
7364
|
]);
|
|
7302
7365
|
}
|
|
7303
7366
|
async function executeCreateFast(ctx2, options) {
|
|
7304
|
-
const twoStep = options.twoStep ||
|
|
7367
|
+
const twoStep = options.twoStep || isNonInteractive();
|
|
7305
7368
|
const { name, password, email, signal } = options;
|
|
7306
7369
|
if (!options.twoStep && twoStep) {
|
|
7307
|
-
info("Non-interactive
|
|
7370
|
+
info("Non-interactive session detected. Using --two-step mode automatically.");
|
|
7308
7371
|
}
|
|
7309
7372
|
const spinner = createSpinner("Creating vault...");
|
|
7310
7373
|
const vaultId = await withAbortSignal(
|
|
@@ -7347,7 +7410,7 @@ Vault ID: ${vaultId}`);
|
|
|
7347
7410
|
let attempts = 0;
|
|
7348
7411
|
while (attempts < MAX_VERIFY_ATTEMPTS) {
|
|
7349
7412
|
attempts++;
|
|
7350
|
-
const codeAnswer = await
|
|
7413
|
+
const codeAnswer = await prompt([
|
|
7351
7414
|
{
|
|
7352
7415
|
type: "input",
|
|
7353
7416
|
name: "code",
|
|
@@ -7378,7 +7441,7 @@ Vault ID: ${vaultId}`);
|
|
|
7378
7441
|
err.exitCode = 1;
|
|
7379
7442
|
throw err;
|
|
7380
7443
|
}
|
|
7381
|
-
const { action } = await
|
|
7444
|
+
const { action } = await prompt([
|
|
7382
7445
|
{
|
|
7383
7446
|
type: "select",
|
|
7384
7447
|
name: "action",
|
|
@@ -7482,7 +7545,7 @@ Important: Save your vault backup file (.vult) in a secure location.`);
|
|
|
7482
7545
|
async function executeImport(ctx2, file, flagPassword) {
|
|
7483
7546
|
let password = flagPassword || process.env.VAULT_PASSWORD || "";
|
|
7484
7547
|
if (!password) {
|
|
7485
|
-
const answers = await
|
|
7548
|
+
const answers = await prompt([
|
|
7486
7549
|
{
|
|
7487
7550
|
type: "password",
|
|
7488
7551
|
name: "password",
|
|
@@ -7507,8 +7570,9 @@ async function executeVerify(ctx2, vaultId, options = {}) {
|
|
|
7507
7570
|
let email = options.email;
|
|
7508
7571
|
let password = options.password;
|
|
7509
7572
|
if (!email || !password) {
|
|
7573
|
+
requireInteractive("Pass --email and --password to resend non-interactively.");
|
|
7510
7574
|
info("Email and password are required to resend verification.");
|
|
7511
|
-
const answers = await
|
|
7575
|
+
const answers = await prompt([
|
|
7512
7576
|
...!email ? [
|
|
7513
7577
|
{
|
|
7514
7578
|
type: "input",
|
|
@@ -7540,10 +7604,19 @@ async function executeVerify(ctx2, vaultId, options = {}) {
|
|
|
7540
7604
|
error(resendErr.message || "Could not resend email. You may need to wait a few minutes.");
|
|
7541
7605
|
return false;
|
|
7542
7606
|
}
|
|
7607
|
+
if (!options.code && isNonInteractive()) {
|
|
7608
|
+
if (isJsonOutput()) {
|
|
7609
|
+
outputJson({ resent: true, vaultId });
|
|
7610
|
+
return true;
|
|
7611
|
+
}
|
|
7612
|
+
info("\nOnce you have the code, run:");
|
|
7613
|
+
printResult(chalk5.cyan(` vultisig verify ${vaultId} --code <OTP>`));
|
|
7614
|
+
return true;
|
|
7615
|
+
}
|
|
7543
7616
|
}
|
|
7544
7617
|
let code = options.code;
|
|
7545
7618
|
if (!code) {
|
|
7546
|
-
const codeAnswer = await
|
|
7619
|
+
const codeAnswer = await prompt([
|
|
7547
7620
|
{
|
|
7548
7621
|
type: "input",
|
|
7549
7622
|
name: "code",
|
|
@@ -7621,7 +7694,7 @@ async function executeExport(ctx2, options = {}) {
|
|
|
7621
7694
|
if (options.password !== void 0) {
|
|
7622
7695
|
exportPassword = options.password;
|
|
7623
7696
|
} else {
|
|
7624
|
-
const answer = await
|
|
7697
|
+
const answer = await prompt([
|
|
7625
7698
|
{
|
|
7626
7699
|
type: "password",
|
|
7627
7700
|
name: "exportPassword",
|
|
@@ -7761,6 +7834,7 @@ async function executeInfo(ctx2) {
|
|
|
7761
7834
|
displayVaultInfo(vault);
|
|
7762
7835
|
}
|
|
7763
7836
|
async function executeCreateFromSeedphraseFast(ctx2, options) {
|
|
7837
|
+
requireInteractive("Seedphrase fast-vault import requires interactive email-OTP entry; run it in a terminal.");
|
|
7764
7838
|
const { mnemonic, name, password, email, discoverChains, chains, signal, usePhantomSolanaPath } = options;
|
|
7765
7839
|
const validateSpinner = createSpinner("Validating seedphrase...");
|
|
7766
7840
|
const validation = await ctx2.sdk.validateSeedphrase(mnemonic);
|
|
@@ -7821,7 +7895,7 @@ async function executeCreateFromSeedphraseFast(ctx2, options) {
|
|
|
7821
7895
|
let attempts = 0;
|
|
7822
7896
|
while (attempts < MAX_VERIFY_ATTEMPTS) {
|
|
7823
7897
|
attempts++;
|
|
7824
|
-
const codeAnswer = await
|
|
7898
|
+
const codeAnswer = await prompt([
|
|
7825
7899
|
{
|
|
7826
7900
|
type: "input",
|
|
7827
7901
|
name: "code",
|
|
@@ -7852,7 +7926,7 @@ async function executeCreateFromSeedphraseFast(ctx2, options) {
|
|
|
7852
7926
|
err.exitCode = 1;
|
|
7853
7927
|
throw err;
|
|
7854
7928
|
}
|
|
7855
|
-
const { action } = await
|
|
7929
|
+
const { action } = await prompt([
|
|
7856
7930
|
{
|
|
7857
7931
|
type: "select",
|
|
7858
7932
|
name: "action",
|
|
@@ -8071,6 +8145,9 @@ async function executeDelete(ctx2, options = {}) {
|
|
|
8071
8145
|
});
|
|
8072
8146
|
return;
|
|
8073
8147
|
}
|
|
8148
|
+
if (!options.skipConfirmation) {
|
|
8149
|
+
requireInteractive("Pass --yes to skip confirmation, or use -o json for scripting.");
|
|
8150
|
+
}
|
|
8074
8151
|
info("\n" + chalk5.bold("Vault to delete:"));
|
|
8075
8152
|
info(` Name: ${chalk5.cyan(vault.name)}`);
|
|
8076
8153
|
info(` Type: ${chalk5.yellow(vault.type)}`);
|
|
@@ -8081,7 +8158,7 @@ async function executeDelete(ctx2, options = {}) {
|
|
|
8081
8158
|
warn(chalk5.red.bold("WARNING: This action cannot be undone!"));
|
|
8082
8159
|
warn("Make sure you have a backup of your vault (.vult file) before proceeding.");
|
|
8083
8160
|
info("");
|
|
8084
|
-
const { confirmed } = await
|
|
8161
|
+
const { confirmed } = await prompt([
|
|
8085
8162
|
{
|
|
8086
8163
|
type: "confirm",
|
|
8087
8164
|
name: "confirmed",
|
|
@@ -8224,10 +8301,16 @@ Dry-run preview:`);
|
|
|
8224
8301
|
if (result.requiresApproval) info(` Requires approval: yes`);
|
|
8225
8302
|
if (result.warnings?.length) result.warnings.forEach((w) => warn(` Warning: ${w}`));
|
|
8226
8303
|
}
|
|
8304
|
+
function refuseSwapWhenNonInteractive() {
|
|
8305
|
+
throw new ConfirmationRequiredError(
|
|
8306
|
+
"Swap requires confirmation.",
|
|
8307
|
+
"Pass --yes to confirm, or --dry-run to preview without signing."
|
|
8308
|
+
);
|
|
8309
|
+
}
|
|
8227
8310
|
async function confirmSwapIfNeeded(options) {
|
|
8228
8311
|
if (options.yes) return;
|
|
8229
8312
|
if (isNonInteractive()) {
|
|
8230
|
-
|
|
8313
|
+
refuseSwapWhenNonInteractive();
|
|
8231
8314
|
}
|
|
8232
8315
|
const confirmed = await confirmSwap();
|
|
8233
8316
|
if (!confirmed) {
|
|
@@ -8239,6 +8322,9 @@ async function executeSwap(ctx2, options) {
|
|
|
8239
8322
|
const vault = await ctx2.ensureActiveVault();
|
|
8240
8323
|
validateSwapAmount(options.amount);
|
|
8241
8324
|
const amountStr = getSwapAmountString(options.amount);
|
|
8325
|
+
if (!options.dryRun && !options.yes && isNonInteractive()) {
|
|
8326
|
+
refuseSwapWhenNonInteractive();
|
|
8327
|
+
}
|
|
8242
8328
|
const quoteSpinner = createSpinner("Getting swap quote...");
|
|
8243
8329
|
const dryResult = await vault.swap(toSwapRequest(options, amountStr, true));
|
|
8244
8330
|
if (!dryResult.dryRun) throw new Error("unreachable");
|
|
@@ -8310,7 +8396,6 @@ async function executeSwap(ctx2, options) {
|
|
|
8310
8396
|
// src/commands/settings.ts
|
|
8311
8397
|
import { Chain as Chain6, fiatCurrencies as fiatCurrencies2, fiatCurrencyNameRecord as fiatCurrencyNameRecord3 } from "@vultisig/sdk";
|
|
8312
8398
|
import chalk6 from "chalk";
|
|
8313
|
-
import inquirer5 from "inquirer";
|
|
8314
8399
|
async function executeCurrency(ctx2, newCurrency) {
|
|
8315
8400
|
const vault = await ctx2.ensureActiveVault();
|
|
8316
8401
|
if (!newCurrency) {
|
|
@@ -8396,7 +8481,7 @@ async function executeAddressBook(ctx2, options = {}) {
|
|
|
8396
8481
|
});
|
|
8397
8482
|
}
|
|
8398
8483
|
if (prompts.length > 0) {
|
|
8399
|
-
const answers = await
|
|
8484
|
+
const answers = await prompt(prompts);
|
|
8400
8485
|
chain = chain || answers.chain;
|
|
8401
8486
|
address = address || answers.address?.trim();
|
|
8402
8487
|
name = name || answers.name?.trim();
|
|
@@ -8574,7 +8659,12 @@ async function executeRujiraDeposit(ctx2, options = {}) {
|
|
|
8574
8659
|
}
|
|
8575
8660
|
async function executeRujiraSwap(ctx2, options) {
|
|
8576
8661
|
const vault = await ctx2.ensureActiveVault();
|
|
8577
|
-
|
|
8662
|
+
if (!options.dryRun && !options.yes && isNonInteractive()) {
|
|
8663
|
+
throw new ConfirmationRequiredError(
|
|
8664
|
+
"Swap requires confirmation.",
|
|
8665
|
+
"Pass --yes to confirm, or --dry-run to preview."
|
|
8666
|
+
);
|
|
8667
|
+
}
|
|
8578
8668
|
const client = await createRujiraClient(ctx2, options);
|
|
8579
8669
|
const destination = options.destination ?? await vault.address("THORChain");
|
|
8580
8670
|
const quoteSpinner = createSpinner("Getting FIN swap quote...");
|
|
@@ -8614,8 +8704,12 @@ async function executeRujiraSwap(ctx2, options) {
|
|
|
8614
8704
|
}
|
|
8615
8705
|
if (!options.yes) {
|
|
8616
8706
|
warn("This command will execute a swap. Re-run with -y/--yes to skip this warning.");
|
|
8617
|
-
throw new
|
|
8707
|
+
throw new ConfirmationRequiredError(
|
|
8708
|
+
"Swap requires confirmation.",
|
|
8709
|
+
"Pass --yes to confirm, or --dry-run to preview."
|
|
8710
|
+
);
|
|
8618
8711
|
}
|
|
8712
|
+
await ensureVaultUnlocked(vault, options.password);
|
|
8619
8713
|
const execSpinner = createSpinner("Executing FIN swap...");
|
|
8620
8714
|
const result = await client.swap.execute(quote, { slippageBps: options.slippageBps });
|
|
8621
8715
|
execSpinner.succeed("Swap submitted");
|
|
@@ -8627,7 +8721,12 @@ async function executeRujiraSwap(ctx2, options) {
|
|
|
8627
8721
|
}
|
|
8628
8722
|
async function executeRujiraWithdraw(ctx2, options) {
|
|
8629
8723
|
const vault = await ctx2.ensureActiveVault();
|
|
8630
|
-
|
|
8724
|
+
if (!options.dryRun && !options.yes && isNonInteractive()) {
|
|
8725
|
+
throw new ConfirmationRequiredError(
|
|
8726
|
+
"Withdrawal requires confirmation.",
|
|
8727
|
+
"Pass --yes to confirm, or --dry-run to preview."
|
|
8728
|
+
);
|
|
8729
|
+
}
|
|
8631
8730
|
const client = await createRujiraClient(ctx2, options);
|
|
8632
8731
|
const prepSpinner = createSpinner("Preparing withdrawal (MsgDeposit)...");
|
|
8633
8732
|
const prepared = await client.withdraw.prepare({
|
|
@@ -8660,8 +8759,12 @@ async function executeRujiraWithdraw(ctx2, options) {
|
|
|
8660
8759
|
}
|
|
8661
8760
|
if (!options.yes) {
|
|
8662
8761
|
warn("This command will broadcast a THORChain MsgDeposit withdrawal. Re-run with -y/--yes to proceed.");
|
|
8663
|
-
throw new
|
|
8762
|
+
throw new ConfirmationRequiredError(
|
|
8763
|
+
"Withdrawal requires confirmation.",
|
|
8764
|
+
"Pass --yes to confirm, or --dry-run to preview."
|
|
8765
|
+
);
|
|
8664
8766
|
}
|
|
8767
|
+
await ensureVaultUnlocked(vault, options.password);
|
|
8665
8768
|
const execSpinner = createSpinner("Broadcasting withdrawal...");
|
|
8666
8769
|
const result = await client.withdraw.execute(prepared);
|
|
8667
8770
|
execSpinner.succeed("Withdrawal submitted");
|
|
@@ -14251,8 +14354,8 @@ ${chalk9.gray(ts)} ${chalk9.magenta.bold("Notification")}: ${chalk9.bold(heading
|
|
|
14251
14354
|
}
|
|
14252
14355
|
showPrompt() {
|
|
14253
14356
|
if (this.stopped) return;
|
|
14254
|
-
const
|
|
14255
|
-
this.rl.setPrompt(
|
|
14357
|
+
const prompt2 = chalk9.gray(`${this.timestamp()} `) + chalk9.green.bold("You") + ": ";
|
|
14358
|
+
this.rl.setPrompt(prompt2);
|
|
14256
14359
|
this.rl.prompt();
|
|
14257
14360
|
}
|
|
14258
14361
|
timestamp() {
|
|
@@ -14568,7 +14671,7 @@ var cachedVersion = null;
|
|
|
14568
14671
|
function getVersion() {
|
|
14569
14672
|
if (cachedVersion) return cachedVersion;
|
|
14570
14673
|
if (true) {
|
|
14571
|
-
cachedVersion = "2.19.
|
|
14674
|
+
cachedVersion = "2.19.14";
|
|
14572
14675
|
return cachedVersion;
|
|
14573
14676
|
}
|
|
14574
14677
|
try {
|
|
@@ -15194,7 +15297,6 @@ import * as readline3 from "readline";
|
|
|
15194
15297
|
// src/interactive/shell-commands.ts
|
|
15195
15298
|
import chalk13 from "chalk";
|
|
15196
15299
|
import Table2 from "cli-table3";
|
|
15197
|
-
import inquirer6 from "inquirer";
|
|
15198
15300
|
import ora2 from "ora";
|
|
15199
15301
|
function formatTimeRemaining(ms) {
|
|
15200
15302
|
if (!ms || ms <= 0) return "expired";
|
|
@@ -15230,7 +15332,7 @@ async function executeUnlock(ctx2) {
|
|
|
15230
15332
|
console.log(chalk13.gray(`Time remaining: ${formatTimeRemaining(timeRemaining)}`));
|
|
15231
15333
|
return;
|
|
15232
15334
|
}
|
|
15233
|
-
const { password } = await
|
|
15335
|
+
const { password } = await prompt([
|
|
15234
15336
|
{
|
|
15235
15337
|
type: "password",
|
|
15236
15338
|
name: "password",
|
|
@@ -15375,7 +15477,6 @@ function showHelp() {
|
|
|
15375
15477
|
}
|
|
15376
15478
|
|
|
15377
15479
|
// src/interactive/shell-context.ts
|
|
15378
|
-
import inquirer7 from "inquirer";
|
|
15379
15480
|
var ShellContext = class extends BaseCommandContext {
|
|
15380
15481
|
vaults = /* @__PURE__ */ new Map();
|
|
15381
15482
|
constructor(sdk, options) {
|
|
@@ -15430,7 +15531,7 @@ var ShellContext = class extends BaseCommandContext {
|
|
|
15430
15531
|
return envPassword;
|
|
15431
15532
|
}
|
|
15432
15533
|
const displayName = vaultName || vaultId;
|
|
15433
|
-
const { password } = await
|
|
15534
|
+
const { password } = await prompt([
|
|
15434
15535
|
{
|
|
15435
15536
|
type: "password",
|
|
15436
15537
|
name: "password",
|
|
@@ -15525,7 +15626,7 @@ var ShellSession = class {
|
|
|
15525
15626
|
/**
|
|
15526
15627
|
* Read a single line with tab completion, then close readline
|
|
15527
15628
|
*/
|
|
15528
|
-
readLine(
|
|
15629
|
+
readLine(prompt2) {
|
|
15529
15630
|
return new Promise((resolve) => {
|
|
15530
15631
|
const rl = readline3.createInterface({
|
|
15531
15632
|
input: process.stdin,
|
|
@@ -15535,7 +15636,7 @@ var ShellSession = class {
|
|
|
15535
15636
|
},
|
|
15536
15637
|
terminal: true
|
|
15537
15638
|
});
|
|
15538
|
-
rl.question(
|
|
15639
|
+
rl.question(prompt2, (answer) => {
|
|
15539
15640
|
rl.close();
|
|
15540
15641
|
resolve(answer);
|
|
15541
15642
|
});
|
|
@@ -16404,6 +16505,12 @@ function setupCompletionCommand(program2) {
|
|
|
16404
16505
|
process.exit(1);
|
|
16405
16506
|
}
|
|
16406
16507
|
if (options.install) {
|
|
16508
|
+
if (isNonInteractive()) {
|
|
16509
|
+
console.error(
|
|
16510
|
+
"completion --install needs an interactive terminal to pick your shell.\nRun it directly in your shell, or append the script manually, e.g.:\n vultisig completion bash >> ~/.bashrc\n vultisig completion zsh >> ~/.zshrc"
|
|
16511
|
+
);
|
|
16512
|
+
process.exit(12 /* CONFIRMATION_REQUIRED */);
|
|
16513
|
+
}
|
|
16407
16514
|
try {
|
|
16408
16515
|
await tt.install({
|
|
16409
16516
|
name: "vultisig",
|
|
@@ -16622,7 +16729,7 @@ program.name("vultisig").description("Vultisig CLI - Secure multi-party crypto w
|
|
|
16622
16729
|
}
|
|
16623
16730
|
initOutputMode({ silent: opts.silent, output: opts.output });
|
|
16624
16731
|
setQuiet(!!opts.quiet);
|
|
16625
|
-
setNonInteractive(!!opts.nonInteractive);
|
|
16732
|
+
setNonInteractive(resolveNonInteractive(!!opts.nonInteractive));
|
|
16626
16733
|
const fields = opts.fields;
|
|
16627
16734
|
setFields(
|
|
16628
16735
|
fields ? fields.split(",").map((f) => f.trim()).filter(Boolean) : void 0
|
|
@@ -16726,7 +16833,7 @@ async function promptSeedphrase() {
|
|
|
16726
16833
|
requireInteractive("Use --mnemonic flag to provide seedphrase non-interactively.");
|
|
16727
16834
|
info("\nEnter your 12 or 24-word recovery phrase.");
|
|
16728
16835
|
info("Words will be hidden as you type.\n");
|
|
16729
|
-
const answer = await
|
|
16836
|
+
const answer = await prompt([
|
|
16730
16837
|
{
|
|
16731
16838
|
type: "password",
|
|
16732
16839
|
name: "mnemonic",
|
|
@@ -16747,7 +16854,7 @@ async function promptQrPayload() {
|
|
|
16747
16854
|
requireInteractive("Use --qr or --qr-file flag to provide QR payload non-interactively.");
|
|
16748
16855
|
info("\nEnter the QR code payload from the initiator device.");
|
|
16749
16856
|
info('The payload starts with "vultisig://".\n');
|
|
16750
|
-
const answer = await
|
|
16857
|
+
const answer = await prompt([
|
|
16751
16858
|
{
|
|
16752
16859
|
type: "input",
|
|
16753
16860
|
name: "qrPayload",
|
|
@@ -16845,6 +16952,7 @@ joinCmd.command("secure").description("Join a SecureVault creation session").opt
|
|
|
16845
16952
|
const qrParams = await parseKeygenQR(qrPayload);
|
|
16846
16953
|
let mnemonic = options.mnemonic;
|
|
16847
16954
|
if (qrParams.libType === "KEYIMPORT" && !mnemonic) {
|
|
16955
|
+
requireInteractive("Use --mnemonic flag to provide seedphrase non-interactively.");
|
|
16848
16956
|
info("\nThis session requires a seedphrase to join.");
|
|
16849
16957
|
mnemonic = await promptSeedphrase();
|
|
16850
16958
|
}
|
|
@@ -17508,6 +17616,10 @@ process.on("SIGINT", () => {
|
|
|
17508
17616
|
process.exit(0);
|
|
17509
17617
|
});
|
|
17510
17618
|
if (isInteractiveMode) {
|
|
17619
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
17620
|
+
error("Interactive mode (-i/--interactive) requires a TTY on both stdin and stdout.");
|
|
17621
|
+
process.exit(12 /* CONFIRMATION_REQUIRED */);
|
|
17622
|
+
}
|
|
17511
17623
|
startInteractiveMode().catch((err) => {
|
|
17512
17624
|
error(`Failed to start interactive mode: ${err.message}`);
|
|
17513
17625
|
process.exit(1);
|