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