@wlfi-agent/cli 1.4.10

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.
@@ -0,0 +1,1894 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/wlfc/index.ts
4
+ import { chmod, mkdir, readdir, readFile, writeFile } from "fs/promises";
5
+ import { createServer } from "http";
6
+ import { homedir } from "os";
7
+ import path from "path";
8
+ import process from "process";
9
+ import {
10
+ input as promptInput,
11
+ password as promptPassword
12
+ } from "@inquirer/prompts";
13
+ import {
14
+ AgentWalletSdkError,
15
+ createAgentWalletClient,
16
+ DEFAULT_BASE_URL
17
+ } from "@wlfi-agent/agent-wallet-sdk";
18
+ import { Command, Option } from "commander";
19
+ var CONFIG_DIR = path.join(homedir(), ".config", "wlfc-agent");
20
+ var CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
21
+ var OTP_DIR = path.join(homedir(), ".test_otp");
22
+ var DEFAULT_CALLBACK_PORT = 54545;
23
+ var DEFAULT_LOGIN_TIMEOUT_MS = 15 * 60 * 1e3;
24
+ var DEFAULT_WALLET_CHAIN_ID = 56;
25
+ var DEFAULT_WALLET_NETWORK = "evm";
26
+ var MAX_CALLBACK_BODY_BYTES = 1024 * 1024;
27
+ var MAX_LIST_ITEMS_TO_PRINT = 20;
28
+ var createEmptyAliases = () => ({
29
+ destination: {},
30
+ project: {},
31
+ wallet: {}
32
+ });
33
+ var normalizeAliasMap = (raw) => {
34
+ const defaults = createEmptyAliases();
35
+ if (!(raw && typeof raw === "object")) {
36
+ return defaults;
37
+ }
38
+ for (const type of Object.keys(defaults)) {
39
+ const source = raw[type];
40
+ if (!(source && typeof source === "object")) {
41
+ continue;
42
+ }
43
+ const entries = Object.entries(source);
44
+ for (const [alias, id] of entries) {
45
+ const normalizedAlias = alias.trim();
46
+ const normalizedId = typeof id === "string" ? id.trim() : "";
47
+ if (!(normalizedAlias && normalizedId)) {
48
+ continue;
49
+ }
50
+ defaults[type][normalizedAlias] = normalizedId;
51
+ }
52
+ }
53
+ return defaults;
54
+ };
55
+ var resolveBaseUrl = () => (process.env.WLFI_AGENT_BACKEND_URL ?? process.env.WLF_AGENT_BACKEND_URL ?? DEFAULT_BASE_URL).trim();
56
+ var toFlagKey = (key) => key.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
57
+ var optionsToFlags = (options) => {
58
+ const flags = {};
59
+ for (const [key, value] of Object.entries(options)) {
60
+ if (value === void 0 || value === null) {
61
+ continue;
62
+ }
63
+ const flagKey = toFlagKey(key);
64
+ if (typeof value === "boolean") {
65
+ if (value) {
66
+ flags[flagKey] = "true";
67
+ }
68
+ continue;
69
+ }
70
+ if (typeof value === "number" || typeof value === "string") {
71
+ flags[flagKey] = String(value);
72
+ }
73
+ }
74
+ return flags;
75
+ };
76
+ var collectFlags = (command) => {
77
+ const lineage = [];
78
+ let cursor = command;
79
+ while (cursor) {
80
+ lineage.unshift(cursor);
81
+ cursor = cursor.parent ?? null;
82
+ }
83
+ const flags = {};
84
+ for (const node of lineage) {
85
+ Object.assign(flags, optionsToFlags(node.opts()));
86
+ }
87
+ return flags;
88
+ };
89
+ var withJsonOption = (command) => command.option("--json", "Print machine-readable JSON output");
90
+ var withTokenOption = (command) => command.option("--token <token>", "Session token override");
91
+ var withUnsupportedBaseUrlOption = (command) => command.addOption(new Option("--base-url <url>").hideHelp());
92
+ var assertNoBaseUrlFlag = (flags) => {
93
+ if (flags["base-url"]) {
94
+ throw new Error(
95
+ "--base-url is not supported. Use WLFI_AGENT_BACKEND_URL or WLF_AGENT_BACKEND_URL instead."
96
+ );
97
+ }
98
+ };
99
+ var getCommandFlags = (command) => {
100
+ const flags = collectFlags(command);
101
+ assertNoBaseUrlFlag(flags);
102
+ return flags;
103
+ };
104
+ var loadConfig = async () => {
105
+ try {
106
+ const raw = await readFile(CONFIG_FILE, "utf8");
107
+ const parsed = JSON.parse(raw);
108
+ const token = typeof parsed.token === "string" && parsed.token.trim().length > 0 ? parsed.token.trim() : void 0;
109
+ const aliases = normalizeAliasMap(parsed.aliases);
110
+ return { aliases, token };
111
+ } catch {
112
+ return {
113
+ aliases: createEmptyAliases()
114
+ };
115
+ }
116
+ };
117
+ var saveConfig = async (config) => {
118
+ await mkdir(CONFIG_DIR, { mode: 448, recursive: true });
119
+ await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), {
120
+ mode: 384
121
+ });
122
+ await chmod(CONFIG_FILE, 384);
123
+ };
124
+ var parseCallbackPort = (value) => {
125
+ if (!value) {
126
+ return DEFAULT_CALLBACK_PORT;
127
+ }
128
+ const parsed = Number(value);
129
+ if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) {
130
+ throw new Error("--callback-port must be a valid TCP port (1-65535)");
131
+ }
132
+ return parsed;
133
+ };
134
+ var parsePositiveInteger = (value, flagName) => {
135
+ const parsed = Number(value);
136
+ if (!Number.isInteger(parsed) || parsed <= 0) {
137
+ throw new Error(`${flagName} must be a positive integer`);
138
+ }
139
+ return parsed;
140
+ };
141
+ var readRequestBody = async (request) => new Promise((resolve, reject) => {
142
+ const chunks = [];
143
+ let size = 0;
144
+ request.on("data", (chunk) => {
145
+ size += chunk.length;
146
+ if (size > MAX_CALLBACK_BODY_BYTES) {
147
+ reject(new Error("Callback request body is too large"));
148
+ request.destroy();
149
+ return;
150
+ }
151
+ chunks.push(chunk);
152
+ });
153
+ request.on("end", () => {
154
+ resolve(Buffer.concat(chunks).toString("utf8"));
155
+ });
156
+ request.on("error", (error) => {
157
+ reject(error);
158
+ });
159
+ });
160
+ var getErrorPayload = (error) => {
161
+ if (error instanceof AgentWalletSdkError) {
162
+ if (typeof error.payload === "object" && error.payload !== null) {
163
+ return {
164
+ ...error.payload,
165
+ message: error.message
166
+ };
167
+ }
168
+ return { message: error.message };
169
+ }
170
+ if (error instanceof Error) {
171
+ return { message: error.message };
172
+ }
173
+ return { message: String(error) };
174
+ };
175
+ var getErrorMessage = (error) => {
176
+ if (error instanceof Error) {
177
+ return error.message;
178
+ }
179
+ return String(error);
180
+ };
181
+ var printJson = (value) => {
182
+ console.log(JSON.stringify(value, null, 2));
183
+ };
184
+ var shouldOutputJson = (flags) => flags.json === "true";
185
+ var summarizeRecord = (record) => {
186
+ const priorityKeys = [
187
+ "id",
188
+ "type",
189
+ "alias",
190
+ "name",
191
+ "label",
192
+ "ok",
193
+ "statusCode",
194
+ "status",
195
+ "eventType",
196
+ "webhookId",
197
+ "webhookUrl",
198
+ "walletId",
199
+ "projectId",
200
+ "address",
201
+ "approveId",
202
+ "paymentId",
203
+ "txHash",
204
+ "url",
205
+ "message"
206
+ ];
207
+ const parts = [];
208
+ for (const key of priorityKeys) {
209
+ if (!(key in record)) {
210
+ continue;
211
+ }
212
+ const value = record[key];
213
+ if (value === null || value === void 0) {
214
+ continue;
215
+ }
216
+ const rendered = typeof value === "string" || typeof value === "number" ? String(value) : JSON.stringify(value);
217
+ parts.push(`${key}=${rendered}`);
218
+ }
219
+ if (parts.length > 0) {
220
+ return parts.join(" | ");
221
+ }
222
+ const fallback = Object.entries(record).slice(0, 4).map(([key, value]) => {
223
+ let rendered = String(value);
224
+ if (typeof value === "string" || typeof value === "number") {
225
+ rendered = String(value);
226
+ } else if (Array.isArray(value)) {
227
+ rendered = `array(${value.length})`;
228
+ } else if (value && typeof value === "object") {
229
+ rendered = "object";
230
+ }
231
+ return `${key}=${rendered}`;
232
+ });
233
+ return fallback.join(" | ");
234
+ };
235
+ var printHumanValue = (value, title) => {
236
+ if (title) {
237
+ console.log(title);
238
+ }
239
+ if (Array.isArray(value)) {
240
+ if (value.length === 0) {
241
+ console.log("No results.");
242
+ return;
243
+ }
244
+ console.log(`Found ${value.length} item(s):`);
245
+ const visibleItems = value.slice(0, MAX_LIST_ITEMS_TO_PRINT);
246
+ for (const [index, item] of visibleItems.entries()) {
247
+ if (item && typeof item === "object" && !Array.isArray(item)) {
248
+ console.log(`${index + 1}. ${summarizeRecord(item)}`);
249
+ } else {
250
+ console.log(`${index + 1}. ${String(item)}`);
251
+ }
252
+ }
253
+ if (value.length > MAX_LIST_ITEMS_TO_PRINT) {
254
+ console.log(
255
+ `... ${value.length - MAX_LIST_ITEMS_TO_PRINT} more item(s). Use --json for full output.`
256
+ );
257
+ }
258
+ return;
259
+ }
260
+ if (value && typeof value === "object") {
261
+ const record = value;
262
+ const hasOnlyOkResult = Object.keys(record).every(
263
+ (key) => key === "ok" || key === "message"
264
+ );
265
+ if (hasOnlyOkResult && "ok" in record && typeof record.ok === "boolean") {
266
+ console.log(record.ok ? "Success." : "Failed.");
267
+ if (typeof record.message === "string" && record.message.trim()) {
268
+ console.log(record.message);
269
+ }
270
+ return;
271
+ }
272
+ console.log(summarizeRecord(record));
273
+ return;
274
+ }
275
+ console.log(String(value));
276
+ };
277
+ var printValue = (flags, value, options) => {
278
+ if (shouldOutputJson(flags)) {
279
+ printJson(value);
280
+ return;
281
+ }
282
+ printHumanValue(value, options?.title);
283
+ if (options?.nextSteps && options.nextSteps.length > 0) {
284
+ console.log("\nNext steps:");
285
+ for (const step of options.nextSteps) {
286
+ console.log(`- ${step}`);
287
+ }
288
+ }
289
+ };
290
+ var parseAliasType = (value, flagName = "--type") => {
291
+ if (value === "destination" || value === "project" || value === "wallet") {
292
+ return value;
293
+ }
294
+ throw new Error(`${flagName} must be one of: wallet, project, destination`);
295
+ };
296
+ var resolveAliasId = async (type, rawValue) => {
297
+ const value = rawValue.trim();
298
+ if (!value) {
299
+ return "";
300
+ }
301
+ const config = await loadConfig();
302
+ return config.aliases[type][value] ?? value;
303
+ };
304
+ var parseNetwork = (value) => {
305
+ if (value !== "evm" && value !== "solana") {
306
+ throw new Error("--network must be either evm or solana");
307
+ }
308
+ return value;
309
+ };
310
+ var parseApprovalPayload = async (flags) => {
311
+ const inline = flags["approval-payload-json"];
312
+ const file = flags["approval-payload-file"];
313
+ if (!(inline || file)) {
314
+ return void 0;
315
+ }
316
+ if (inline && file) {
317
+ throw new Error(
318
+ "Use either --approval-payload-json or --approval-payload-file, not both"
319
+ );
320
+ }
321
+ const raw = inline ? inline : await readFile(file, "utf8");
322
+ try {
323
+ const parsed = JSON.parse(raw);
324
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
325
+ throw new Error("approval payload must be a JSON object");
326
+ }
327
+ return parsed;
328
+ } catch (error) {
329
+ throw new Error(
330
+ `Failed to parse approval payload JSON: ${getErrorMessage(error)}`
331
+ );
332
+ }
333
+ };
334
+ var parsePolicyDestinations = async (flags) => {
335
+ const inline = flags["destinations-json"];
336
+ const file = flags["destinations-file"];
337
+ if (!(inline || file)) {
338
+ throw new Error(
339
+ "policy put requires exactly one of --destinations-json or --destinations-file"
340
+ );
341
+ }
342
+ if (inline && file) {
343
+ throw new Error(
344
+ "policy put requires exactly one of --destinations-json or --destinations-file"
345
+ );
346
+ }
347
+ const raw = inline ? inline : await readFile(file, "utf8");
348
+ try {
349
+ const parsed = JSON.parse(raw);
350
+ if (!Array.isArray(parsed)) {
351
+ throw new Error("Policy destinations must be a JSON array");
352
+ }
353
+ return parsed;
354
+ } catch (error) {
355
+ throw new Error(
356
+ `Failed to parse policy destinations JSON: ${getErrorMessage(error)}`
357
+ );
358
+ }
359
+ };
360
+ var getAuthedClient = async (flags) => {
361
+ const config = await loadConfig();
362
+ const token = (flags.token ?? config.token ?? "").trim();
363
+ if (!token) {
364
+ throw new Error(
365
+ "Token is missing. Run `wlfc login --token <token>` or `wlfc login --email <email>` first."
366
+ );
367
+ }
368
+ return createAgentWalletClient({
369
+ baseUrl: resolveBaseUrl(),
370
+ token
371
+ });
372
+ };
373
+ var startCallbackServer = async (options) => {
374
+ const { port } = options;
375
+ const client = createAgentWalletClient({ baseUrl: resolveBaseUrl() });
376
+ let resolved = false;
377
+ let settle = null;
378
+ let rejectSettle = null;
379
+ let timeout;
380
+ const tokenPromise = new Promise((resolve, reject) => {
381
+ settle = resolve;
382
+ rejectSettle = reject;
383
+ });
384
+ const complete = (token) => {
385
+ if (resolved) {
386
+ return false;
387
+ }
388
+ resolved = true;
389
+ if (timeout) {
390
+ clearTimeout(timeout);
391
+ }
392
+ settle?.(token);
393
+ return true;
394
+ };
395
+ const fail = (error) => {
396
+ if (resolved) {
397
+ return;
398
+ }
399
+ resolved = true;
400
+ if (timeout) {
401
+ clearTimeout(timeout);
402
+ }
403
+ rejectSettle?.(error);
404
+ };
405
+ const server = createServer(async (request, response) => {
406
+ const requestUrl = new URL(request.url ?? "/", `http://127.0.0.1:${port}`);
407
+ if (request.method === "GET" && requestUrl.pathname === "/") {
408
+ response.statusCode = 200;
409
+ response.setHeader("Content-Type", "text/html; charset=utf-8");
410
+ response.end(`<!doctype html>
411
+ <html>
412
+ <head>
413
+ <meta charset="utf-8" />
414
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
415
+ <title>wlfc callback server</title>
416
+ </head>
417
+ <body style="font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; margin: 24px; line-height: 1.5;">
418
+ <h1>wlfc callback server is running</h1>
419
+ <p>Open the link from your email. For manual testing:</p>
420
+ <ul>
421
+ <li><a href="/login">/login?requestId=&lt;id&gt;</a></li>
422
+ <li><a href="/register">/register?requestId=&lt;id&gt;</a></li>
423
+ </ul>
424
+ <p>This server will receive your session callback on <code>/callback</code>.</p>
425
+ </body>
426
+ </html>`);
427
+ return;
428
+ }
429
+ if (request.method === "GET" && requestUrl.pathname === "/login") {
430
+ response.statusCode = 200;
431
+ response.setHeader("Content-Type", "text/html; charset=utf-8");
432
+ response.end(`<!doctype html>
433
+ <html lang="en">
434
+ <head>
435
+ <meta charset="utf-8" />
436
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
437
+ <title>wlfc login</title>
438
+ <style>
439
+ body { font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; margin: 24px; color: #10212f; background: #f2f5f8; }
440
+ .panel { max-width: 720px; margin: 0 auto; background: #fff; border: 1px solid #dce3ea; border-radius: 12px; padding: 20px; box-shadow: 0 10px 25px rgba(2, 30, 62, 0.06); }
441
+ h1 { margin-top: 0; }
442
+ textarea { width: 100%; min-height: 130px; border: 1px solid #cfd9e3; border-radius: 8px; padding: 10px; box-sizing: border-box; font-size: 14px; }
443
+ button { border: 0; border-radius: 8px; background: #0d5f4f; color: #fff; padding: 10px 14px; font-weight: 600; cursor: pointer; margin-top: 12px; }
444
+ code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; background: #f5f8fb; padding: 2px 6px; border-radius: 6px; border: 1px solid #dbe4ee; }
445
+ .status { margin-top: 12px; min-height: 20px; font-size: 14px; }
446
+ .status.error { color: #ab2d2d; }
447
+ .status.ok { color: #1b7a36; }
448
+ </style>
449
+ </head>
450
+ <body>
451
+ <main class="panel">
452
+ <h1>wlfc login</h1>
453
+ <p>Request ID: <code id="request-id">(missing)</code></p>
454
+ <p>Paste OTP signature from your CubeSigner login email.</p>
455
+ <form id="login-form">
456
+ <textarea id="otp-signature" placeholder="Paste OTP signature or full token" required></textarea>
457
+ <button type="submit">Complete login</button>
458
+ </form>
459
+ <div id="status" class="status"></div>
460
+ </main>
461
+ <script>
462
+ const requestId = (new URLSearchParams(window.location.search).get("requestId") || "").trim();
463
+ const requestIdEl = document.getElementById("request-id");
464
+ const form = document.getElementById("login-form");
465
+ const otpInput = document.getElementById("otp-signature");
466
+ const statusEl = document.getElementById("status");
467
+ requestIdEl.textContent = requestId || "(missing)";
468
+ const setStatus = (text, isError = false) => {
469
+ statusEl.textContent = text;
470
+ statusEl.className = "status " + (isError ? "error" : "ok");
471
+ };
472
+
473
+ if (!requestId) {
474
+ setStatus("Missing requestId query parameter", true);
475
+ form.style.display = "none";
476
+ }
477
+
478
+ form.addEventListener("submit", async (event) => {
479
+ event.preventDefault();
480
+ const otpSignature = (otpInput.value || "").trim();
481
+ if (!otpSignature) {
482
+ setStatus("OTP signature is required", true);
483
+ return;
484
+ }
485
+
486
+ setStatus("Submitting OTP...");
487
+ try {
488
+ const response = await fetch("/internal/login/complete", {
489
+ method: "POST",
490
+ headers: { "Content-Type": "application/json" },
491
+ body: JSON.stringify({ requestId, otpSignature }),
492
+ });
493
+ const payload = await response.json().catch(() => ({}));
494
+ if (!response.ok) {
495
+ throw new Error(payload?.message || ("HTTP " + response.status));
496
+ }
497
+ setStatus("Success. Session token has been sent to your terminal callback.");
498
+ form.style.display = "none";
499
+ } catch (error) {
500
+ const message = error instanceof Error ? error.message : String(error);
501
+ setStatus(message, true);
502
+ }
503
+ });
504
+ </script>
505
+ </body>
506
+ </html>`);
507
+ return;
508
+ }
509
+ if (request.method === "GET" && (requestUrl.pathname === "/register" || requestUrl.pathname === "/agent-wallet/register")) {
510
+ response.statusCode = 200;
511
+ response.setHeader("Content-Type", "text/html; charset=utf-8");
512
+ response.end(`<!doctype html>
513
+ <html lang="en">
514
+ <head>
515
+ <meta charset="utf-8" />
516
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
517
+ <title>wlfc register</title>
518
+ <style>
519
+ body { font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; margin: 24px; color: #10212f; background: #f2f5f8; }
520
+ .panel { max-width: 760px; margin: 0 auto; background: #fff; border: 1px solid #dce3ea; border-radius: 12px; padding: 20px; box-shadow: 0 10px 25px rgba(2, 30, 62, 0.06); }
521
+ h1 { margin-top: 0; }
522
+ label { display: block; margin: 10px 0 6px; font-weight: 600; }
523
+ input { width: 100%; border: 1px solid #cfd9e3; border-radius: 8px; padding: 10px; box-sizing: border-box; font-size: 14px; }
524
+ button { border: 0; border-radius: 8px; background: #0d5f4f; color: #fff; padding: 10px 14px; font-weight: 600; cursor: pointer; margin-top: 12px; }
525
+ code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; background: #f5f8fb; padding: 2px 6px; border-radius: 6px; border: 1px solid #dbe4ee; word-break: break-all; }
526
+ .status { margin-top: 12px; min-height: 20px; font-size: 14px; }
527
+ .status.error { color: #ab2d2d; }
528
+ .status.ok { color: #1b7a36; }
529
+ .totp-box { margin-top: 16px; padding-top: 16px; border-top: 1px solid #e5ebf2; display: none; }
530
+ .qr { margin: 10px 0; width: 220px; height: 220px; border: 1px solid #dce3ea; border-radius: 8px; }
531
+ </style>
532
+ <script src="https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js"></script>
533
+ </head>
534
+ <body>
535
+ <main class="panel">
536
+ <h1>wlfc register</h1>
537
+ <p>Request ID: <code id="request-id">(from query)</code></p>
538
+ <ol>
539
+ <li>Open the invite link from your CubeSigner email.</li>
540
+ <li>Set a password and submit below.</li>
541
+ <li>Scan TOTP QR, then enter the OTP code.</li>
542
+ </ol>
543
+
544
+ <form id="accept-form">
545
+ <label for="password">Password</label>
546
+ <input id="password" type="password" autocomplete="new-password" required />
547
+ <button type="submit">Accept invite</button>
548
+ </form>
549
+
550
+ <section id="totp-section" class="totp-box">
551
+ <h2>Set up OTP</h2>
552
+ <p>Scan this QR code with your authenticator app:</p>
553
+ <div id="totp-qr" class="qr" aria-label="TOTP QR code"></div>
554
+ <p><strong>Fallback URL:</strong> <code id="totp-url"></code></p>
555
+ <form id="totp-form">
556
+ <label for="otp-code">Current OTP code</label>
557
+ <input id="otp-code" type="text" inputmode="numeric" pattern="[0-9]*" required />
558
+ <button type="submit">Complete registration</button>
559
+ </form>
560
+ </section>
561
+
562
+ <div id="status" class="status"></div>
563
+ </main>
564
+ <script>
565
+ const searchParams = new URLSearchParams(window.location.search);
566
+ let requestId = (searchParams.get("requestId") || "").trim();
567
+ const inviteTokenFromQuery = (
568
+ searchParams.get("inviteToken") ||
569
+ searchParams.get("token") ||
570
+ ""
571
+ ).trim();
572
+ const inviteProofFromQuery = (
573
+ searchParams.get("inviteProof") ||
574
+ searchParams.get("proof") ||
575
+ ""
576
+ ).trim();
577
+ const requestIdEl = document.getElementById("request-id");
578
+ const acceptForm = document.getElementById("accept-form");
579
+ const totpSection = document.getElementById("totp-section");
580
+ const totpForm = document.getElementById("totp-form");
581
+ const passwordInput = document.getElementById("password");
582
+ const otpCodeInput = document.getElementById("otp-code");
583
+ const totpQr = document.getElementById("totp-qr");
584
+ const totpUrlEl = document.getElementById("totp-url");
585
+ const statusEl = document.getElementById("status");
586
+ let totpId = "";
587
+
588
+ requestIdEl.textContent = requestId || "(from query)";
589
+ const setStatus = (text, isError = false) => {
590
+ statusEl.textContent = text;
591
+ statusEl.className = "status " + (isError ? "error" : "ok");
592
+ };
593
+
594
+ if (!requestId) {
595
+ setStatus("Missing requestId query parameter in invite link.", true);
596
+ }
597
+
598
+ if (!(inviteTokenFromQuery || inviteProofFromQuery)) {
599
+ setStatus("Invite link is missing inviteToken/inviteProof.", true);
600
+ }
601
+
602
+ if (inviteTokenFromQuery && inviteProofFromQuery) {
603
+ setStatus("Invite link contains both inviteToken and inviteProof; expected only one.", true);
604
+ }
605
+
606
+ acceptForm.addEventListener("submit", async (event) => {
607
+ event.preventDefault();
608
+ const password = (passwordInput.value || "").trim();
609
+
610
+ if (!password) {
611
+ setStatus("Password is required", true);
612
+ return;
613
+ }
614
+
615
+ if (!requestId) {
616
+ setStatus("Missing requestId query parameter in invite link.", true);
617
+ return;
618
+ }
619
+
620
+ if (!(inviteTokenFromQuery || inviteProofFromQuery)) {
621
+ setStatus("Invite link is missing inviteToken/inviteProof.", true);
622
+ return;
623
+ }
624
+
625
+ if (inviteTokenFromQuery && inviteProofFromQuery) {
626
+ setStatus("Invite link contains both inviteToken and inviteProof; expected only one.", true);
627
+ return;
628
+ }
629
+
630
+ setStatus("Accepting invite...");
631
+ try {
632
+ const payloadBody = {
633
+ ...(inviteProofFromQuery
634
+ ? { inviteProof: inviteProofFromQuery }
635
+ : { inviteToken: inviteTokenFromQuery }),
636
+ password,
637
+ requestId,
638
+ };
639
+
640
+ const response = await fetch("/internal/register/accept", {
641
+ method: "POST",
642
+ headers: { "Content-Type": "application/json" },
643
+ body: JSON.stringify(payloadBody),
644
+ });
645
+ const payload = await response.json().catch(() => ({}));
646
+ if (!response.ok) {
647
+ throw new Error(payload?.message || ("HTTP " + response.status));
648
+ }
649
+
650
+ const nextRequestId = (payload?.requestId || "").trim();
651
+ totpId = (payload?.totpId || "").trim();
652
+ const totpUrl = (payload?.totpUrl || "").trim();
653
+ if (!nextRequestId || !totpId || !totpUrl) {
654
+ throw new Error("Missing TOTP challenge response");
655
+ }
656
+
657
+ if (typeof window.QRCode !== "function") {
658
+ throw new Error("QR code library did not load");
659
+ }
660
+
661
+ requestId = nextRequestId;
662
+ requestIdEl.textContent = requestId;
663
+ totpQr.innerHTML = "";
664
+ new window.QRCode(totpQr, {
665
+ height: 220,
666
+ text: totpUrl,
667
+ width: 220,
668
+ });
669
+ totpUrlEl.textContent = totpUrl;
670
+ totpSection.style.display = "block";
671
+ setStatus("Invite accepted. Scan QR and enter OTP code.");
672
+ acceptForm.style.display = "none";
673
+ } catch (error) {
674
+ const message = error instanceof Error ? error.message : String(error);
675
+ setStatus(message, true);
676
+ }
677
+ });
678
+
679
+ totpForm.addEventListener("submit", async (event) => {
680
+ event.preventDefault();
681
+ const otpCode = (otpCodeInput.value || "").trim();
682
+ if (!(requestId && totpId && otpCode)) {
683
+ setStatus("Request ID, TOTP challenge, and OTP code are required", true);
684
+ return;
685
+ }
686
+
687
+ setStatus("Verifying OTP...");
688
+ try {
689
+ const response = await fetch("/internal/register/complete", {
690
+ method: "POST",
691
+ headers: { "Content-Type": "application/json" },
692
+ body: JSON.stringify({ otpCode, requestId, totpId }),
693
+ });
694
+ const payload = await response.json().catch(() => ({}));
695
+ if (!response.ok) {
696
+ throw new Error(payload?.message || ("HTTP " + response.status));
697
+ }
698
+
699
+ setStatus("Success. Session token has been sent to your terminal callback.");
700
+ totpForm.style.display = "none";
701
+ } catch (error) {
702
+ const message = error instanceof Error ? error.message : String(error);
703
+ setStatus(message, true);
704
+ }
705
+ });
706
+ </script>
707
+ </body>
708
+ </html>`);
709
+ return;
710
+ }
711
+ if (request.method === "POST" && requestUrl.pathname === "/internal/login/complete") {
712
+ try {
713
+ const rawBody = await readRequestBody(request);
714
+ const parsed = JSON.parse(rawBody);
715
+ const payload = await client.wcLoginCompleteFromOtpSignature({
716
+ otpSignature: typeof parsed.otpSignature === "string" ? parsed.otpSignature : "",
717
+ requestId: typeof parsed.requestId === "string" ? parsed.requestId : ""
718
+ });
719
+ response.statusCode = 200;
720
+ response.setHeader("Content-Type", "application/json");
721
+ response.end(JSON.stringify(payload));
722
+ } catch (error) {
723
+ const status = error instanceof AgentWalletSdkError ? error.status : 400;
724
+ response.statusCode = status;
725
+ response.setHeader("Content-Type", "application/json");
726
+ response.end(JSON.stringify(getErrorPayload(error)));
727
+ }
728
+ return;
729
+ }
730
+ if (request.method === "POST" && requestUrl.pathname === "/internal/register/accept") {
731
+ try {
732
+ const rawBody = await readRequestBody(request);
733
+ const parsed = JSON.parse(rawBody);
734
+ const payload = await client.wcRegisterAcceptInvite({
735
+ callbackUrl: typeof parsed.callbackUrl === "string" ? parsed.callbackUrl : "",
736
+ email: typeof parsed.email === "string" ? parsed.email : "",
737
+ inviteProof: typeof parsed.inviteProof === "string" ? parsed.inviteProof : "",
738
+ inviteToken: typeof parsed.inviteToken === "string" ? parsed.inviteToken : "",
739
+ password: typeof parsed.password === "string" ? parsed.password : "",
740
+ requestId: typeof parsed.requestId === "string" ? parsed.requestId : ""
741
+ });
742
+ response.statusCode = 200;
743
+ response.setHeader("Content-Type", "application/json");
744
+ response.end(JSON.stringify(payload));
745
+ } catch (error) {
746
+ const status = error instanceof AgentWalletSdkError ? error.status : 400;
747
+ response.statusCode = status;
748
+ response.setHeader("Content-Type", "application/json");
749
+ response.end(JSON.stringify(getErrorPayload(error)));
750
+ }
751
+ return;
752
+ }
753
+ if (request.method === "POST" && requestUrl.pathname === "/internal/register/complete") {
754
+ try {
755
+ const rawBody = await readRequestBody(request);
756
+ const parsed = JSON.parse(rawBody);
757
+ const payload = await client.wcRegisterCompleteTotp({
758
+ otpCode: typeof parsed.otpCode === "string" ? parsed.otpCode : "",
759
+ requestId: typeof parsed.requestId === "string" ? parsed.requestId : "",
760
+ totpId: typeof parsed.totpId === "string" ? parsed.totpId : ""
761
+ });
762
+ response.statusCode = 200;
763
+ response.setHeader("Content-Type", "application/json");
764
+ response.end(JSON.stringify(payload));
765
+ } catch (error) {
766
+ const status = error instanceof AgentWalletSdkError ? error.status : 400;
767
+ response.statusCode = status;
768
+ response.setHeader("Content-Type", "application/json");
769
+ response.end(JSON.stringify(getErrorPayload(error)));
770
+ }
771
+ return;
772
+ }
773
+ if (request.method === "GET" && requestUrl.pathname === "/callback") {
774
+ const token = requestUrl.searchParams.get("token")?.trim();
775
+ if (!token) {
776
+ response.statusCode = 400;
777
+ response.setHeader("Content-Type", "application/json");
778
+ response.end(
779
+ JSON.stringify({ message: "Missing token query parameter" })
780
+ );
781
+ return;
782
+ }
783
+ const accepted = complete(token);
784
+ response.statusCode = accepted ? 200 : 409;
785
+ response.setHeader("Content-Type", "text/html; charset=utf-8");
786
+ response.end(`<!doctype html>
787
+ <html>
788
+ <head><meta charset="utf-8" /><title>wlfc login callback</title></head>
789
+ <body style="font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; margin: 24px;">
790
+ <h1>${accepted ? "wlfc login completed" : "wlfc callback already consumed"}</h1>
791
+ <p>You can return to your terminal.</p>
792
+ </body>
793
+ </html>`);
794
+ return;
795
+ }
796
+ if (request.method === "POST" && requestUrl.pathname === "/callback") {
797
+ try {
798
+ const rawBody = await readRequestBody(request);
799
+ const parsed = JSON.parse(rawBody);
800
+ const token = typeof parsed === "object" && parsed !== null && "token" in parsed && typeof parsed.token === "string" ? parsed.token.trim() : "";
801
+ if (!token) {
802
+ response.statusCode = 400;
803
+ response.setHeader("Content-Type", "application/json");
804
+ response.end(
805
+ JSON.stringify({ message: "Missing token in callback payload" })
806
+ );
807
+ return;
808
+ }
809
+ const accepted = complete(token);
810
+ response.statusCode = accepted ? 200 : 409;
811
+ response.setHeader("Content-Type", "application/json");
812
+ response.end(JSON.stringify({ ok: accepted }));
813
+ } catch (error) {
814
+ response.statusCode = 400;
815
+ response.setHeader("Content-Type", "application/json");
816
+ response.end(JSON.stringify(getErrorPayload(error)));
817
+ }
818
+ return;
819
+ }
820
+ response.statusCode = 404;
821
+ response.setHeader("Content-Type", "application/json");
822
+ response.end(JSON.stringify({ message: "Not found" }));
823
+ });
824
+ await new Promise((resolve, reject) => {
825
+ server.once("error", (error) => {
826
+ reject(error);
827
+ });
828
+ server.listen(port, "127.0.0.1", () => {
829
+ resolve();
830
+ });
831
+ });
832
+ timeout = setTimeout(() => {
833
+ fail(
834
+ new Error(
835
+ `Timed out waiting for callback on http://127.0.0.1:${port}/callback`
836
+ )
837
+ );
838
+ }, DEFAULT_LOGIN_TIMEOUT_MS);
839
+ const close = async () => {
840
+ await new Promise((resolve) => {
841
+ server.close(() => resolve());
842
+ });
843
+ };
844
+ return {
845
+ callbackUrl: `http://127.0.0.1:${port}/callback`,
846
+ close,
847
+ waitForToken: async () => {
848
+ try {
849
+ return await tokenPromise;
850
+ } finally {
851
+ await close();
852
+ }
853
+ }
854
+ };
855
+ };
856
+ var login = async (args, flags) => {
857
+ const token = (flags.token ?? "").trim();
858
+ if (token) {
859
+ const client2 = createAgentWalletClient({
860
+ baseUrl: resolveBaseUrl(),
861
+ token
862
+ });
863
+ await client2.listProjects();
864
+ const config2 = await loadConfig();
865
+ await saveConfig({
866
+ ...config2,
867
+ token
868
+ });
869
+ console.log("Saved credentials to ~/.config/wlfc-agent/config.json");
870
+ return;
871
+ }
872
+ const email = (flags.email ?? args[0] ?? "").trim().toLowerCase();
873
+ if (!email) {
874
+ throw new Error(
875
+ "login requires --token <token> or --email <email>. Run `wlfc login --help` for details."
876
+ );
877
+ }
878
+ const password = (await promptPassword({ message: `Password for ${email}` })).trim();
879
+ if (!password) {
880
+ throw new Error("Password is required");
881
+ }
882
+ const client = createAgentWalletClient({ baseUrl: resolveBaseUrl() });
883
+ let otpCode = (flags.otp ?? "").trim();
884
+ let loginResult;
885
+ try {
886
+ loginResult = await client.wcLoginPassword({
887
+ email,
888
+ ...otpCode ? { otpCode } : {},
889
+ password
890
+ });
891
+ } catch (error) {
892
+ const message = getErrorMessage(error);
893
+ if (!otpCode && message.includes("TOTP code is required")) {
894
+ otpCode = (await promptInput({ message: "TOTP code" })).trim();
895
+ if (!otpCode) {
896
+ throw new Error("TOTP code is required");
897
+ }
898
+ loginResult = await client.wcLoginPassword({
899
+ email,
900
+ otpCode,
901
+ password
902
+ });
903
+ } else {
904
+ throw error;
905
+ }
906
+ }
907
+ if (!loginResult.token) {
908
+ throw new Error("Login response did not include a session token");
909
+ }
910
+ const authedClient = createAgentWalletClient({
911
+ baseUrl: resolveBaseUrl(),
912
+ token: loginResult.token
913
+ });
914
+ await authedClient.listProjects();
915
+ const config = await loadConfig();
916
+ await saveConfig({
917
+ ...config,
918
+ token: loginResult.token
919
+ });
920
+ console.log("Saved credentials to ~/.config/wlfc-agent/config.json");
921
+ };
922
+ var register = async (args, flags) => {
923
+ const email = args[0]?.trim().toLowerCase();
924
+ if (!email) {
925
+ throw new Error("register requires an email argument");
926
+ }
927
+ const callbackPort = parseCallbackPort(flags["callback-port"]);
928
+ const callback = await startCallbackServer({
929
+ port: callbackPort
930
+ });
931
+ const client = createAgentWalletClient({
932
+ baseUrl: resolveBaseUrl()
933
+ });
934
+ try {
935
+ const start = await client.wcRegisterStart({
936
+ callbackUrl: callback.callbackUrl,
937
+ email
938
+ });
939
+ console.log("Check your email. We sent you a registration link.");
940
+ console.log(
941
+ "Open the link from your email. It already includes requestId and invite token/proof."
942
+ );
943
+ if (!start.emailSent) {
944
+ console.log(
945
+ `Email delivery is unavailable in this environment. Open this local link instead:
946
+ ${start.setupUrl}`
947
+ );
948
+ }
949
+ const callbackToken = await callback.waitForToken();
950
+ const authedClient = createAgentWalletClient({
951
+ baseUrl: resolveBaseUrl(),
952
+ token: callbackToken
953
+ });
954
+ await authedClient.listProjects();
955
+ const config = await loadConfig();
956
+ await saveConfig({
957
+ ...config,
958
+ token: callbackToken
959
+ });
960
+ console.log("Saved credentials to ~/.config/wlfc-agent/config.json");
961
+ } catch (error) {
962
+ await callback.close();
963
+ throw error;
964
+ }
965
+ };
966
+ var logout = async () => {
967
+ const config = await loadConfig();
968
+ await saveConfig({
969
+ aliases: config.aliases
970
+ });
971
+ console.log("Cleared session token from ~/.config/wlfc-agent/config.json");
972
+ };
973
+ var printDoctorReport = (flags, report) => {
974
+ if (shouldOutputJson(flags)) {
975
+ printJson(report);
976
+ return;
977
+ }
978
+ console.log(`Doctor report (${report.generatedAt})`);
979
+ console.log(`Backend URL: ${report.baseUrl}`);
980
+ for (const check of report.checks) {
981
+ let marker = "[FAIL]";
982
+ if (check.status === "ok") {
983
+ marker = "[OK] ";
984
+ } else if (check.status === "warn") {
985
+ marker = "[WARN]";
986
+ }
987
+ console.log(`${marker} ${check.name}: ${check.detail}`);
988
+ }
989
+ };
990
+ var doctor = async (flags) => {
991
+ const baseUrl = resolveBaseUrl();
992
+ const checks = [];
993
+ try {
994
+ const response = await fetch(`${baseUrl}/rest/custody/agent/pay`, {
995
+ body: "{}",
996
+ headers: {
997
+ "content-type": "application/json"
998
+ },
999
+ method: "POST"
1000
+ });
1001
+ checks.push({
1002
+ detail: `Reachable (HTTP ${response.status})`,
1003
+ name: "backend",
1004
+ status: "ok"
1005
+ });
1006
+ } catch (error) {
1007
+ checks.push({
1008
+ detail: getErrorMessage(error),
1009
+ name: "backend",
1010
+ status: "fail"
1011
+ });
1012
+ }
1013
+ const config = await loadConfig();
1014
+ if (config.token) {
1015
+ checks.push({
1016
+ detail: "Session token is configured",
1017
+ name: "token",
1018
+ status: "ok"
1019
+ });
1020
+ } else {
1021
+ checks.push({
1022
+ detail: "No session token configured. Run `wlfc login`.",
1023
+ name: "token",
1024
+ status: "warn"
1025
+ });
1026
+ }
1027
+ const aliasCount = Object.values(config.aliases).reduce(
1028
+ (count, aliasMap) => count + Object.keys(aliasMap).length,
1029
+ 0
1030
+ );
1031
+ checks.push({
1032
+ detail: aliasCount > 0 ? `${aliasCount} alias(es) configured` : "No aliases configured",
1033
+ name: "aliases",
1034
+ status: aliasCount > 0 ? "ok" : "warn"
1035
+ });
1036
+ try {
1037
+ const files = await readdir(OTP_DIR);
1038
+ const otpFileContents = await Promise.all(
1039
+ files.map(async (filename) => {
1040
+ const fullPath = path.join(OTP_DIR, filename);
1041
+ return (await readFile(fullPath, "utf8")).trim();
1042
+ })
1043
+ );
1044
+ const otpSecrets = otpFileContents.reduce((count, content) => {
1045
+ if (!content.startsWith("otpauth://")) {
1046
+ return count;
1047
+ }
1048
+ const secret = new URL(content).searchParams.get("secret");
1049
+ return secret?.trim() ? count + 1 : count;
1050
+ }, 0);
1051
+ checks.push({
1052
+ detail: otpSecrets > 0 ? `${otpSecrets} OTP secret file(s) found in ${OTP_DIR}` : `No valid OTP secret found in ${OTP_DIR}`,
1053
+ name: "otp",
1054
+ status: otpSecrets > 0 ? "ok" : "warn"
1055
+ });
1056
+ } catch {
1057
+ checks.push({
1058
+ detail: `Directory not found: ${OTP_DIR}`,
1059
+ name: "otp",
1060
+ status: "warn"
1061
+ });
1062
+ }
1063
+ if (config.token) {
1064
+ const client = createAgentWalletClient({
1065
+ baseUrl,
1066
+ token: config.token
1067
+ });
1068
+ try {
1069
+ const projects = await client.listProjects();
1070
+ checks.push({
1071
+ detail: `Token valid (${projects.length} visible project(s))`,
1072
+ name: "auth",
1073
+ status: "ok"
1074
+ });
1075
+ } catch (error) {
1076
+ checks.push({
1077
+ detail: getErrorMessage(error),
1078
+ name: "auth",
1079
+ status: "fail"
1080
+ });
1081
+ }
1082
+ try {
1083
+ const wallets = await client.listWallets();
1084
+ checks.push({
1085
+ detail: `${wallets.length} wallet(s) visible`,
1086
+ name: "wallet visibility",
1087
+ status: wallets.length > 0 ? "ok" : "warn"
1088
+ });
1089
+ } catch (error) {
1090
+ checks.push({
1091
+ detail: getErrorMessage(error),
1092
+ name: "wallet visibility",
1093
+ status: "fail"
1094
+ });
1095
+ }
1096
+ try {
1097
+ const configs = await client.listWalletConfigs();
1098
+ const activePolicies = configs.filter(
1099
+ (configItem) => Boolean(configItem.policy) && configItem.policy?.status !== "deleted"
1100
+ ).length;
1101
+ checks.push({
1102
+ detail: `${activePolicies} wallet policy config(s) visible`,
1103
+ name: "policy visibility",
1104
+ status: activePolicies > 0 ? "ok" : "warn"
1105
+ });
1106
+ } catch (error) {
1107
+ checks.push({
1108
+ detail: getErrorMessage(error),
1109
+ name: "policy visibility",
1110
+ status: "fail"
1111
+ });
1112
+ }
1113
+ }
1114
+ printDoctorReport(flags, {
1115
+ baseUrl,
1116
+ checks,
1117
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
1118
+ });
1119
+ };
1120
+ var aliasList = async (flags) => {
1121
+ const config = await loadConfig();
1122
+ const typeRaw = (flags.type ?? "").trim();
1123
+ if (!typeRaw) {
1124
+ const entries2 = Object.keys(config.aliases).flatMap(
1125
+ (aliasType) => Object.entries(config.aliases[aliasType]).map(([alias, id]) => ({
1126
+ alias,
1127
+ id,
1128
+ type: aliasType
1129
+ }))
1130
+ );
1131
+ printValue(flags, entries2, {
1132
+ title: "Aliases"
1133
+ });
1134
+ return;
1135
+ }
1136
+ const type = parseAliasType(typeRaw);
1137
+ const entries = Object.entries(config.aliases[type]).map(([alias, id]) => ({
1138
+ alias,
1139
+ id,
1140
+ type
1141
+ }));
1142
+ printValue(flags, entries, {
1143
+ title: `${type} aliases`
1144
+ });
1145
+ };
1146
+ var aliasSet = async (flags) => {
1147
+ const type = parseAliasType((flags.type ?? "").trim());
1148
+ const name = (flags.name ?? "").trim();
1149
+ const id = (flags.id ?? "").trim();
1150
+ if (!(name && id)) {
1151
+ throw new Error("alias set requires --type, --name, and --id");
1152
+ }
1153
+ const config = await loadConfig();
1154
+ const nextAliases = {
1155
+ ...config.aliases,
1156
+ [type]: {
1157
+ ...config.aliases[type],
1158
+ [name]: id
1159
+ }
1160
+ };
1161
+ await saveConfig({
1162
+ ...config,
1163
+ aliases: nextAliases
1164
+ });
1165
+ printValue(
1166
+ flags,
1167
+ { alias: name, id, type },
1168
+ {
1169
+ nextSteps: [
1170
+ `Use --${type}-id ${name} in CLI commands to resolve to ${id}`
1171
+ ],
1172
+ title: "Alias saved"
1173
+ }
1174
+ );
1175
+ };
1176
+ var aliasRemove = async (flags) => {
1177
+ const type = parseAliasType((flags.type ?? "").trim());
1178
+ const name = (flags.name ?? "").trim();
1179
+ if (!name) {
1180
+ throw new Error("alias rm requires --type and --name");
1181
+ }
1182
+ const config = await loadConfig();
1183
+ if (!(name in config.aliases[type])) {
1184
+ throw new Error(`Alias not found: ${name}`);
1185
+ }
1186
+ const nextTypeAliases = { ...config.aliases[type] };
1187
+ delete nextTypeAliases[name];
1188
+ await saveConfig({
1189
+ ...config,
1190
+ aliases: {
1191
+ ...config.aliases,
1192
+ [type]: nextTypeAliases
1193
+ }
1194
+ });
1195
+ printValue(
1196
+ flags,
1197
+ { ok: true },
1198
+ {
1199
+ title: `Removed alias ${name} (${type})`
1200
+ }
1201
+ );
1202
+ };
1203
+ var walletList = async (flags) => {
1204
+ const client = await getAuthedClient(flags);
1205
+ const wallets = await client.listWallets();
1206
+ printValue(flags, wallets, {
1207
+ title: "Wallets"
1208
+ });
1209
+ };
1210
+ var walletNew = async (flags) => {
1211
+ const projectIdInput = (flags["project-id"] ?? "").trim();
1212
+ const label = (flags.label ?? "").trim();
1213
+ const network = (flags.network ?? DEFAULT_WALLET_NETWORK).trim();
1214
+ const chainId = (flags["chain-id"] ?? String(DEFAULT_WALLET_CHAIN_ID)).trim();
1215
+ if (!(projectIdInput && label)) {
1216
+ throw new Error("wallet new requires --project-id and --label");
1217
+ }
1218
+ const projectId = await resolveAliasId("project", projectIdInput);
1219
+ const client = await getAuthedClient(flags);
1220
+ const wallet = await client.createWallet({
1221
+ chainId: parsePositiveInteger(chainId, "--chain-id"),
1222
+ label,
1223
+ network: parseNetwork(network),
1224
+ projectId
1225
+ });
1226
+ printValue(flags, wallet, {
1227
+ nextSteps: [
1228
+ `wlfc alias set --type wallet --name <name> --id ${wallet.id ?? "<wallet-id>"}`,
1229
+ `wlfc agent-key new --wallet-id ${wallet.id ?? "<wallet-id>"}`
1230
+ ],
1231
+ title: "Wallet created"
1232
+ });
1233
+ };
1234
+ var walletBalances = async (flags) => {
1235
+ const client = await getAuthedClient(flags);
1236
+ const balances = await client.listWalletBalances();
1237
+ printValue(flags, balances, {
1238
+ title: "Wallet balances"
1239
+ });
1240
+ };
1241
+ var walletConfigs = async (flags) => {
1242
+ const client = await getAuthedClient(flags);
1243
+ const configs = await client.listWalletConfigs();
1244
+ printValue(flags, configs, {
1245
+ title: "Wallet configs"
1246
+ });
1247
+ };
1248
+ var walletAuditLogs = async (flags) => {
1249
+ const walletIdInput = (flags["wallet-id"] ?? "").trim();
1250
+ if (!walletIdInput) {
1251
+ throw new Error("wallet audit-logs requires --wallet-id <id>");
1252
+ }
1253
+ const limitRaw = (flags.limit ?? "").trim();
1254
+ const walletId = await resolveAliasId("wallet", walletIdInput);
1255
+ const client = await getAuthedClient(flags);
1256
+ const logs = await client.listWalletAuditLogs({
1257
+ action: flags.action,
1258
+ browser: flags.browser,
1259
+ cursor: flags.cursor,
1260
+ ipAddress: flags["ip-address"],
1261
+ ...limitRaw ? { limit: parsePositiveInteger(limitRaw, "--limit") } : {},
1262
+ location: flags.location,
1263
+ query: flags.query,
1264
+ user: flags.user,
1265
+ walletId
1266
+ });
1267
+ printValue(flags, logs, {
1268
+ title: "Wallet audit logs"
1269
+ });
1270
+ };
1271
+ var destinationList = async (flags) => {
1272
+ const client = await getAuthedClient(flags);
1273
+ const destinations = await client.listDestinations();
1274
+ printValue(flags, destinations, {
1275
+ title: "Destinations"
1276
+ });
1277
+ };
1278
+ var destinationNew = async (flags) => {
1279
+ const projectIdInput = (flags["project-id"] ?? "").trim();
1280
+ const label = (flags.label ?? "").trim();
1281
+ const network = (flags.network ?? "").trim();
1282
+ const chainId = (flags["chain-id"] ?? "").trim();
1283
+ const address = (flags.address ?? flags["vendor-address"] ?? flags["destination-address"] ?? "").trim();
1284
+ if (!(projectIdInput && label && network && chainId && address)) {
1285
+ throw new Error(
1286
+ "destination new requires --project-id, --label, --network, --chain-id, and --address"
1287
+ );
1288
+ }
1289
+ const projectId = await resolveAliasId("project", projectIdInput);
1290
+ const client = await getAuthedClient(flags);
1291
+ const destination = await client.createDestination({
1292
+ chainId: parsePositiveInteger(chainId, "--chain-id"),
1293
+ label,
1294
+ network: parseNetwork(network),
1295
+ projectId,
1296
+ vendor_address: address
1297
+ });
1298
+ printValue(flags, destination, {
1299
+ nextSteps: [
1300
+ `wlfc alias set --type destination --name <name> --id ${destination.id ?? "<destination-id>"}`,
1301
+ "wlfc policy put --wallet-id <wallet-id-or-alias> --destinations-file ./policy.json"
1302
+ ],
1303
+ title: "Destination created"
1304
+ });
1305
+ };
1306
+ var agentKeyList = async (flags) => {
1307
+ const client = await getAuthedClient(flags);
1308
+ const agentKeys = await client.listAgentKeys();
1309
+ printValue(flags, agentKeys, {
1310
+ title: "Agent keys"
1311
+ });
1312
+ };
1313
+ var agentKeyGet = async (flags) => {
1314
+ const id = (flags.id ?? flags["agent-key-id"] ?? "").trim();
1315
+ if (!id) {
1316
+ throw new Error("agent-key get requires --id <agent-key-id>");
1317
+ }
1318
+ const client = await getAuthedClient(flags);
1319
+ const agentKey = await client.getAgentKey(id);
1320
+ printValue(flags, agentKey, {
1321
+ title: "Agent key"
1322
+ });
1323
+ };
1324
+ var agentKeyNew = async (flags) => {
1325
+ const walletIdInput = (flags["wallet-id"] ?? "").trim();
1326
+ if (!walletIdInput) {
1327
+ throw new Error("agent-key new requires --wallet-id <id>");
1328
+ }
1329
+ const walletId = await resolveAliasId("wallet", walletIdInput);
1330
+ const client = await getAuthedClient(flags);
1331
+ const created = await client.createAgentKey({ walletId });
1332
+ const keyValue = typeof created.key === "string" ? created.key ?? "" : "";
1333
+ printValue(flags, created, {
1334
+ nextSteps: [
1335
+ keyValue ? `wlfa login --agent-key ${keyValue}` : "wlfa login --agent-key <key-from-output>",
1336
+ "wlfa pay --address <destination> --amount <amount>"
1337
+ ],
1338
+ title: "Agent key created"
1339
+ });
1340
+ };
1341
+ var agentKeyRemove = async (flags) => {
1342
+ const id = (flags.id ?? flags["agent-key-id"] ?? "").trim();
1343
+ if (!id) {
1344
+ throw new Error("agent-key rm requires --id <agent-key-id>");
1345
+ }
1346
+ const client = await getAuthedClient(flags);
1347
+ const deleted = await client.deleteAgentKey(id);
1348
+ printValue(flags, deleted, {
1349
+ title: "Agent key revoked"
1350
+ });
1351
+ };
1352
+ var policyGet = async (flags) => {
1353
+ const walletIdInput = (flags["wallet-id"] ?? "").trim();
1354
+ if (!walletIdInput) {
1355
+ throw new Error("policy get requires --wallet-id <id>");
1356
+ }
1357
+ const walletId = await resolveAliasId("wallet", walletIdInput);
1358
+ const client = await getAuthedClient(flags);
1359
+ const policy = await client.getPolicy(walletId);
1360
+ printValue(flags, policy, {
1361
+ title: "Policy"
1362
+ });
1363
+ };
1364
+ var policyPut = async (flags) => {
1365
+ const walletIdInput = (flags["wallet-id"] ?? "").trim();
1366
+ if (!walletIdInput) {
1367
+ throw new Error("policy put requires --wallet-id <id>");
1368
+ }
1369
+ const walletId = await resolveAliasId("wallet", walletIdInput);
1370
+ const destinations = await parsePolicyDestinations(flags);
1371
+ const client = await getAuthedClient(flags);
1372
+ const updated = await client.putPolicy({
1373
+ destinations,
1374
+ walletId
1375
+ });
1376
+ printValue(flags, updated, {
1377
+ nextSteps: [
1378
+ "wlfa pay --address <destination-address> --amount <amount>",
1379
+ `wlfc wallet audit-logs --wallet-id ${walletId}`
1380
+ ],
1381
+ title: "Policy updated"
1382
+ });
1383
+ };
1384
+ var policyRemove = async (flags) => {
1385
+ const walletIdInput = (flags["wallet-id"] ?? "").trim();
1386
+ if (!walletIdInput) {
1387
+ throw new Error("policy rm requires --wallet-id <id>");
1388
+ }
1389
+ const walletId = await resolveAliasId("wallet", walletIdInput);
1390
+ const client = await getAuthedClient(flags);
1391
+ const removed = await client.deletePolicy(walletId);
1392
+ printValue(flags, removed, {
1393
+ title: "Policy removed"
1394
+ });
1395
+ };
1396
+ var projectList = async (flags) => {
1397
+ const client = await getAuthedClient(flags);
1398
+ const projects = await client.listProjects();
1399
+ printValue(flags, projects, {
1400
+ title: "Projects"
1401
+ });
1402
+ };
1403
+ var projectNew = async (args, flags) => {
1404
+ const projectName = (flags.name ?? flags["project-name"] ?? args[0] ?? "").trim();
1405
+ if (!projectName) {
1406
+ throw new Error("project new requires --name <project-name>");
1407
+ }
1408
+ const client = await getAuthedClient(flags);
1409
+ const project = await client.createProject({ projectName });
1410
+ printValue(flags, project, {
1411
+ nextSteps: [
1412
+ `wlfc alias set --type project --name <name> --id ${project.id ?? "<project-id>"}`,
1413
+ `wlfc wallet new --project-id ${project.id ?? "<project-id>"} --label <wallet-label>`
1414
+ ],
1415
+ title: "Project created"
1416
+ });
1417
+ };
1418
+ var approvalList = async (flags) => {
1419
+ const limitRaw = (flags.limit ?? "").trim();
1420
+ const walletIdInput = (flags["wallet-id"] ?? "").trim();
1421
+ const walletId = walletIdInput ? await resolveAliasId("wallet", walletIdInput) : void 0;
1422
+ const client = await getAuthedClient(flags);
1423
+ const approvals = await client.listPendingApprovals({
1424
+ ...limitRaw ? { limit: parsePositiveInteger(limitRaw, "--limit") } : {},
1425
+ ...walletId ? { walletId } : {}
1426
+ });
1427
+ printValue(flags, approvals, {
1428
+ title: "Pending approvals"
1429
+ });
1430
+ };
1431
+ var approvalApprove = async (flags) => {
1432
+ const approveId = (flags.id ?? flags["approve-id"] ?? "").trim();
1433
+ const mfaType = (flags["mfa-type"] ?? "").trim();
1434
+ const otp = (flags.otp ?? "").trim();
1435
+ if (!approveId) {
1436
+ throw new Error("approval approve requires --id <approve-id>");
1437
+ }
1438
+ const approvalPayload = await parseApprovalPayload(flags);
1439
+ const client = await getAuthedClient(flags);
1440
+ const runApprove = async (otpCode) => client.approveManual({
1441
+ ...approvalPayload ? { approvalPayload } : {},
1442
+ approveId,
1443
+ ...mfaType ? { mfaType } : {},
1444
+ ...otpCode ? { otp: otpCode } : {}
1445
+ });
1446
+ let approved;
1447
+ try {
1448
+ approved = await runApprove(otp || void 0);
1449
+ } catch (error) {
1450
+ if (!otp && getErrorMessage(error).toLowerCase().includes("otp code is required")) {
1451
+ const promptedOtp = (await promptInput({ message: "TOTP code" })).trim();
1452
+ if (!promptedOtp) {
1453
+ throw new Error("TOTP code is required");
1454
+ }
1455
+ approved = await runApprove(promptedOtp);
1456
+ } else {
1457
+ throw error;
1458
+ }
1459
+ }
1460
+ printValue(flags, approved, {
1461
+ nextSteps: [
1462
+ "wlfc approval ls",
1463
+ "wlfc wallet audit-logs --wallet-id <wallet-id-or-alias>"
1464
+ ],
1465
+ title: "Approval submitted"
1466
+ });
1467
+ };
1468
+ var parseWebhookEventType = (value) => {
1469
+ if (value === "approval" || value === "payment") {
1470
+ return value;
1471
+ }
1472
+ throw new Error("--event-type must be either approval or payment");
1473
+ };
1474
+ var parseWebhookStatus = (value) => {
1475
+ if (value === "active" || value === "disabled") {
1476
+ return value;
1477
+ }
1478
+ throw new Error("--status must be either active or disabled");
1479
+ };
1480
+ var webhookList = async (flags) => {
1481
+ const client = await getAuthedClient(flags);
1482
+ const webhooks = await client.listWebhooks();
1483
+ printValue(flags, webhooks, {
1484
+ title: "Webhooks"
1485
+ });
1486
+ };
1487
+ var webhookGet = async (flags) => {
1488
+ const webhookId = (flags.id ?? flags["webhook-id"] ?? "").trim();
1489
+ if (!webhookId) {
1490
+ throw new Error("webhook get requires --id <webhook-id>");
1491
+ }
1492
+ const client = await getAuthedClient(flags);
1493
+ const webhook = await client.getWebhook(webhookId);
1494
+ printValue(flags, webhook, {
1495
+ title: "Webhook"
1496
+ });
1497
+ };
1498
+ var webhookNew = async (flags) => {
1499
+ const projectIdInput = (flags["project-id"] ?? "").trim();
1500
+ const eventTypeRaw = (flags["event-type"] ?? "").trim();
1501
+ const url = (flags.url ?? "").trim();
1502
+ const secret = (flags.secret ?? "").trim();
1503
+ if (!(projectIdInput && eventTypeRaw && url)) {
1504
+ throw new Error(
1505
+ "webhook new requires --project-id, --event-type, and --url"
1506
+ );
1507
+ }
1508
+ const projectId = await resolveAliasId("project", projectIdInput);
1509
+ const client = await getAuthedClient(flags);
1510
+ const webhook = await client.createWebhook({
1511
+ eventType: parseWebhookEventType(eventTypeRaw),
1512
+ projectId,
1513
+ ...secret ? { secret } : {},
1514
+ url
1515
+ });
1516
+ printValue(flags, webhook, {
1517
+ nextSteps: [
1518
+ `wlfc webhook test --id ${webhook.id ?? "<webhook-id>"}`,
1519
+ `wlfc webhook deliveries --id ${webhook.id ?? "<webhook-id>"}`
1520
+ ],
1521
+ title: "Webhook created"
1522
+ });
1523
+ };
1524
+ var webhookUpdate = async (flags) => {
1525
+ const webhookId = (flags.id ?? flags["webhook-id"] ?? "").trim();
1526
+ const eventTypeRaw = (flags["event-type"] ?? "").trim();
1527
+ const statusRaw = (flags.status ?? "").trim();
1528
+ const url = (flags.url ?? "").trim();
1529
+ const secret = (flags.secret ?? "").trim();
1530
+ if (!webhookId) {
1531
+ throw new Error("webhook update requires --id <webhook-id>");
1532
+ }
1533
+ if (!(eventTypeRaw || statusRaw || url || secret)) {
1534
+ throw new Error(
1535
+ "webhook update requires at least one of --event-type, --status, --url, or --secret"
1536
+ );
1537
+ }
1538
+ const client = await getAuthedClient(flags);
1539
+ const updated = await client.updateWebhook({
1540
+ ...eventTypeRaw ? { eventType: parseWebhookEventType(eventTypeRaw) } : {},
1541
+ ...secret ? { secret } : {},
1542
+ ...statusRaw ? { status: parseWebhookStatus(statusRaw) } : {},
1543
+ ...url ? { url } : {},
1544
+ webhookId
1545
+ });
1546
+ printValue(flags, updated, {
1547
+ title: "Webhook updated"
1548
+ });
1549
+ };
1550
+ var webhookRemove = async (flags) => {
1551
+ const webhookId = (flags.id ?? flags["webhook-id"] ?? "").trim();
1552
+ if (!webhookId) {
1553
+ throw new Error("webhook rm requires --id <webhook-id>");
1554
+ }
1555
+ const client = await getAuthedClient(flags);
1556
+ const removed = await client.deleteWebhook(webhookId);
1557
+ printValue(flags, removed, {
1558
+ title: "Webhook removed"
1559
+ });
1560
+ };
1561
+ var webhookDeliveries = async (flags) => {
1562
+ const webhookId = (flags.id ?? flags["webhook-id"] ?? "").trim();
1563
+ if (!webhookId) {
1564
+ throw new Error("webhook deliveries requires --id <webhook-id>");
1565
+ }
1566
+ const limitRaw = (flags.limit ?? "").trim();
1567
+ const config = await loadConfig();
1568
+ const token = (flags.token ?? config.token ?? "").trim();
1569
+ if (!token) {
1570
+ throw new Error(
1571
+ "Token is missing. Run `wlfc login --token <token>` or `wlfc login --email <email>` first."
1572
+ );
1573
+ }
1574
+ const url = new URL(
1575
+ `${resolveBaseUrl()}/rest/custody/agent/webhooks/${encodeURIComponent(webhookId)}/deliveries`
1576
+ );
1577
+ if (limitRaw) {
1578
+ url.searchParams.set(
1579
+ "limit",
1580
+ String(parsePositiveInteger(limitRaw, "--limit"))
1581
+ );
1582
+ }
1583
+ const response = await fetch(url, {
1584
+ headers: {
1585
+ Authorization: token
1586
+ },
1587
+ method: "GET"
1588
+ });
1589
+ const payload = await response.json().catch(async () => ({
1590
+ message: await response.text().catch(() => "Unknown error")
1591
+ }));
1592
+ if (!response.ok) {
1593
+ if (typeof payload === "object" && payload !== null && "message" in payload && typeof payload.message === "string") {
1594
+ throw new Error(payload.message);
1595
+ }
1596
+ throw new Error(
1597
+ `Failed to list webhook deliveries (HTTP ${response.status})`
1598
+ );
1599
+ }
1600
+ printValue(flags, payload, {
1601
+ title: "Webhook deliveries"
1602
+ });
1603
+ };
1604
+ var webhookTest = async (flags) => {
1605
+ const webhookId = (flags.id ?? flags["webhook-id"] ?? "").trim();
1606
+ if (!webhookId) {
1607
+ throw new Error("webhook test requires --id <webhook-id>");
1608
+ }
1609
+ const client = await getAuthedClient(flags);
1610
+ const webhook = await client.getWebhook(webhookId);
1611
+ const eventType = webhook.eventType === "approval" ? "approval" : "payment";
1612
+ const payload = eventType === "approval" ? {
1613
+ approveId: `test_approve_${Date.now()}`,
1614
+ paymentId: `test_payment_${Date.now()}`,
1615
+ status: "success",
1616
+ txHash: `0xtest${Date.now().toString(16)}`,
1617
+ walletId: "test_wallet"
1618
+ } : {
1619
+ destinationAddress: "0x0000000000000000000000000000000000000000",
1620
+ paymentId: `test_payment_${Date.now()}`,
1621
+ reason: "webhook_test_event",
1622
+ status: "failed",
1623
+ walletId: "test_wallet"
1624
+ };
1625
+ const response = await fetch(webhook.url, {
1626
+ body: JSON.stringify(payload),
1627
+ headers: {
1628
+ "content-type": "application/json",
1629
+ "x-wlfc-webhook-test": "true"
1630
+ },
1631
+ method: "POST"
1632
+ });
1633
+ const responseBody = (await response.text()).slice(0, 1e3);
1634
+ printValue(
1635
+ flags,
1636
+ {
1637
+ eventType,
1638
+ ok: response.ok,
1639
+ responseBody,
1640
+ statusCode: response.status,
1641
+ webhookId,
1642
+ webhookUrl: webhook.url
1643
+ },
1644
+ {
1645
+ nextSteps: [`wlfc webhook deliveries --id ${webhookId}`],
1646
+ title: "Webhook test result"
1647
+ }
1648
+ );
1649
+ };
1650
+ var withCommandOptions = (command, options = {}) => {
1651
+ const { auth = false, json = true } = options;
1652
+ if (json) {
1653
+ withJsonOption(command);
1654
+ }
1655
+ if (auth) {
1656
+ withTokenOption(command);
1657
+ }
1658
+ withUnsupportedBaseUrlOption(command);
1659
+ return command;
1660
+ };
1661
+ var createProgram = () => {
1662
+ const program = new Command();
1663
+ program.name("wlfc").description("WLFI Custody CLI").showHelpAfterError().showSuggestionAfterError().addHelpText(
1664
+ "after",
1665
+ `
1666
+ Environment:
1667
+ WLFI_AGENT_BACKEND_URL Backend URL override (default: ${DEFAULT_BASE_URL})
1668
+ `
1669
+ );
1670
+ withUnsupportedBaseUrlOption(program);
1671
+ withCommandOptions(
1672
+ program.command("login [email]").description("Login with token or email/password").option("--email <email>", "Email address").option("--otp <code>", "TOTP code"),
1673
+ { auth: true, json: false }
1674
+ ).action(async (emailArg, _options, command) => {
1675
+ const flags = getCommandFlags(command);
1676
+ await login(emailArg ? [emailArg] : [], flags);
1677
+ });
1678
+ withCommandOptions(
1679
+ program.command("register <email>").description("Start registration flow").option(
1680
+ "--callback-port <port>",
1681
+ "Local callback TCP port",
1682
+ String(DEFAULT_CALLBACK_PORT)
1683
+ ),
1684
+ { json: false }
1685
+ ).action(async (emailArg, _options, command) => {
1686
+ const flags = getCommandFlags(command);
1687
+ await register([emailArg], flags);
1688
+ });
1689
+ withCommandOptions(program.command("doctor").description("Run diagnostics"), {
1690
+ json: true
1691
+ }).action(async (_options, command) => {
1692
+ await doctor(getCommandFlags(command));
1693
+ });
1694
+ withCommandOptions(
1695
+ program.command("logout").description("Clear saved session token"),
1696
+ { json: false }
1697
+ ).action(async () => {
1698
+ await logout();
1699
+ });
1700
+ const alias = program.command("alias").description("Manage aliases");
1701
+ withCommandOptions(
1702
+ alias.command("ls").description("List aliases").option("--type <wallet|project|destination>", "Filter alias type"),
1703
+ { json: true }
1704
+ ).action(async (_options, command) => {
1705
+ await aliasList(getCommandFlags(command));
1706
+ });
1707
+ withCommandOptions(
1708
+ alias.command("set").description("Set alias mapping").requiredOption("--type <wallet|project|destination>", "Alias type").requiredOption("--name <alias>", "Alias name").requiredOption("--id <resource-id>", "Resource id"),
1709
+ { json: true }
1710
+ ).action(async (_options, command) => {
1711
+ await aliasSet(getCommandFlags(command));
1712
+ });
1713
+ withCommandOptions(
1714
+ alias.command("rm").description("Remove alias mapping").requiredOption("--type <wallet|project|destination>", "Alias type").requiredOption("--name <alias>", "Alias name"),
1715
+ { json: true }
1716
+ ).action(async (_options, command) => {
1717
+ await aliasRemove(getCommandFlags(command));
1718
+ });
1719
+ const destination = program.command("destination").description("Manage destinations");
1720
+ withCommandOptions(
1721
+ destination.command("ls").description("List destinations"),
1722
+ { auth: true, json: true }
1723
+ ).action(async (_options, command) => {
1724
+ await destinationList(getCommandFlags(command));
1725
+ });
1726
+ withCommandOptions(
1727
+ destination.command("new").description("Create destination").requiredOption("--project-id <id>", "Project id or alias").requiredOption("--label <label>", "Destination label").requiredOption("--network <evm|solana>", "Destination network").requiredOption("--chain-id <id>", "Chain id").requiredOption("--address <address>", "Destination address").addOption(new Option("--vendor-address <address>").hideHelp()).addOption(new Option("--destination-address <address>").hideHelp()),
1728
+ { auth: true, json: true }
1729
+ ).action(async (_options, command) => {
1730
+ await destinationNew(getCommandFlags(command));
1731
+ });
1732
+ const agentKey = program.command("agent-key").description("Manage agent keys");
1733
+ withCommandOptions(agentKey.command("ls").description("List agent keys"), {
1734
+ auth: true,
1735
+ json: true
1736
+ }).action(async (_options, command) => {
1737
+ await agentKeyList(getCommandFlags(command));
1738
+ });
1739
+ withCommandOptions(
1740
+ agentKey.command("get").description("Get one agent key").option("--id <agent-key-id>", "Agent key id").addOption(new Option("--agent-key-id <agent-key-id>").hideHelp()),
1741
+ { auth: true, json: true }
1742
+ ).action(async (_options, command) => {
1743
+ await agentKeyGet(getCommandFlags(command));
1744
+ });
1745
+ withCommandOptions(
1746
+ agentKey.command("new").description("Create agent key").requiredOption("--wallet-id <id>", "Wallet id or alias"),
1747
+ { auth: true, json: true }
1748
+ ).action(async (_options, command) => {
1749
+ await agentKeyNew(getCommandFlags(command));
1750
+ });
1751
+ withCommandOptions(
1752
+ agentKey.command("rm").description("Revoke agent key").option("--id <agent-key-id>", "Agent key id").addOption(new Option("--agent-key-id <agent-key-id>").hideHelp()),
1753
+ { auth: true, json: true }
1754
+ ).action(async (_options, command) => {
1755
+ await agentKeyRemove(getCommandFlags(command));
1756
+ });
1757
+ const policy = program.command("policy").description("Manage wallet policy");
1758
+ withCommandOptions(
1759
+ policy.command("get").description("Get policy").requiredOption("--wallet-id <id>", "Wallet id or alias"),
1760
+ { auth: true, json: true }
1761
+ ).action(async (_options, command) => {
1762
+ await policyGet(getCommandFlags(command));
1763
+ });
1764
+ withCommandOptions(
1765
+ policy.command("put").description("Create or update policy").requiredOption("--wallet-id <id>", "Wallet id or alias").option("--destinations-json <json>", "Inline JSON destinations array").option("--destinations-file <path>", "Path to destinations JSON"),
1766
+ { auth: true, json: true }
1767
+ ).action(async (_options, command) => {
1768
+ await policyPut(getCommandFlags(command));
1769
+ });
1770
+ withCommandOptions(
1771
+ policy.command("rm").description("Delete policy").requiredOption("--wallet-id <id>", "Wallet id or alias"),
1772
+ { auth: true, json: true }
1773
+ ).action(async (_options, command) => {
1774
+ await policyRemove(getCommandFlags(command));
1775
+ });
1776
+ const project = program.command("project").description("Manage projects");
1777
+ withCommandOptions(project.command("ls").description("List projects"), {
1778
+ auth: true,
1779
+ json: true
1780
+ }).action(async (_options, command) => {
1781
+ await projectList(getCommandFlags(command));
1782
+ });
1783
+ withCommandOptions(
1784
+ project.command("new [name]").description("Create project").option("--name <project-name>", "Project name").addOption(new Option("--project-name <project-name>").hideHelp()),
1785
+ { auth: true, json: true }
1786
+ ).action(async (nameArg, _options, command) => {
1787
+ const flags = getCommandFlags(command);
1788
+ await projectNew(nameArg ? [nameArg] : [], flags);
1789
+ });
1790
+ const wallet = program.command("wallet").description("Manage wallets");
1791
+ withCommandOptions(wallet.command("ls").description("List wallets"), {
1792
+ auth: true,
1793
+ json: true
1794
+ }).action(async (_options, command) => {
1795
+ await walletList(getCommandFlags(command));
1796
+ });
1797
+ withCommandOptions(
1798
+ wallet.command("new").description("Create wallet").requiredOption("--project-id <id>", "Project id or alias").requiredOption("--label <label>", "Wallet label").option(
1799
+ "--network <evm|solana>",
1800
+ "Wallet network",
1801
+ DEFAULT_WALLET_NETWORK
1802
+ ).option("--chain-id <id>", "Chain id", String(DEFAULT_WALLET_CHAIN_ID)),
1803
+ { auth: true, json: true }
1804
+ ).action(async (_options, command) => {
1805
+ await walletNew(getCommandFlags(command));
1806
+ });
1807
+ withCommandOptions(
1808
+ wallet.command("balances").description("List balances for each wallet"),
1809
+ { auth: true, json: true }
1810
+ ).action(async (_options, command) => {
1811
+ await walletBalances(getCommandFlags(command));
1812
+ });
1813
+ withCommandOptions(
1814
+ wallet.command("configs").description("List config for each wallet"),
1815
+ { auth: true, json: true }
1816
+ ).action(async (_options, command) => {
1817
+ await walletConfigs(getCommandFlags(command));
1818
+ });
1819
+ withCommandOptions(
1820
+ wallet.command("audit-logs").description("List wallet audit logs").requiredOption("--wallet-id <id>", "Wallet id or alias").option("--limit <n>", "Result limit").option("--cursor <cursor>", "Pagination cursor").option("--query <query>", "Free text query").option("--action <action>", "Action filter").option("--user <user>", "User filter").option("--browser <browser>", "Browser filter").option("--ip-address <ip>", "IP filter").option("--location <location>", "Location filter"),
1821
+ { auth: true, json: true }
1822
+ ).action(async (_options, command) => {
1823
+ await walletAuditLogs(getCommandFlags(command));
1824
+ });
1825
+ const webhook = program.command("webhook").description("Manage webhooks");
1826
+ withCommandOptions(webhook.command("ls").description("List webhooks"), {
1827
+ auth: true,
1828
+ json: true
1829
+ }).action(async (_options, command) => {
1830
+ await webhookList(getCommandFlags(command));
1831
+ });
1832
+ withCommandOptions(
1833
+ webhook.command("get").description("Get webhook").option("--id <webhook-id>", "Webhook id").addOption(new Option("--webhook-id <webhook-id>").hideHelp()),
1834
+ { auth: true, json: true }
1835
+ ).action(async (_options, command) => {
1836
+ await webhookGet(getCommandFlags(command));
1837
+ });
1838
+ withCommandOptions(
1839
+ webhook.command("new").description("Create webhook").requiredOption("--project-id <id>", "Project id or alias").requiredOption("--event-type <approval|payment>", "Webhook event type").requiredOption("--url <https://...>", "Webhook destination URL").option("--secret <value>", "Webhook secret"),
1840
+ { auth: true, json: true }
1841
+ ).action(async (_options, command) => {
1842
+ await webhookNew(getCommandFlags(command));
1843
+ });
1844
+ withCommandOptions(
1845
+ webhook.command("update").description("Update webhook").option("--id <webhook-id>", "Webhook id").option("--event-type <approval|payment>", "Webhook event type").option("--status <active|disabled>", "Webhook status").option("--url <https://...>", "Webhook destination URL").option("--secret <value>", "Webhook secret").addOption(new Option("--webhook-id <webhook-id>").hideHelp()),
1846
+ { auth: true, json: true }
1847
+ ).action(async (_options, command) => {
1848
+ await webhookUpdate(getCommandFlags(command));
1849
+ });
1850
+ withCommandOptions(
1851
+ webhook.command("rm").description("Delete webhook").option("--id <webhook-id>", "Webhook id").addOption(new Option("--webhook-id <webhook-id>").hideHelp()),
1852
+ { auth: true, json: true }
1853
+ ).action(async (_options, command) => {
1854
+ await webhookRemove(getCommandFlags(command));
1855
+ });
1856
+ withCommandOptions(
1857
+ webhook.command("deliveries").description("List webhook deliveries").option("--id <webhook-id>", "Webhook id").option("--limit <n>", "Result limit").addOption(new Option("--webhook-id <webhook-id>").hideHelp()),
1858
+ { auth: true, json: true }
1859
+ ).action(async (_options, command) => {
1860
+ await webhookDeliveries(getCommandFlags(command));
1861
+ });
1862
+ withCommandOptions(
1863
+ webhook.command("test").description("Send webhook test payload").option("--id <webhook-id>", "Webhook id").addOption(new Option("--webhook-id <webhook-id>").hideHelp()),
1864
+ { auth: true, json: true }
1865
+ ).action(async (_options, command) => {
1866
+ await webhookTest(getCommandFlags(command));
1867
+ });
1868
+ const approval = program.command("approval").description("Manage pending approvals");
1869
+ withCommandOptions(
1870
+ approval.command("ls").description("List pending approvals").option("--wallet-id <id>", "Wallet id or alias").option("--limit <n>", "Result limit"),
1871
+ { auth: true, json: true }
1872
+ ).action(async (_options, command) => {
1873
+ await approvalList(getCommandFlags(command));
1874
+ });
1875
+ withCommandOptions(
1876
+ approval.command("approve").description("Submit manual approval").option("--id <approve-id>", "Approval id").option("--mfa-type <type>", "MFA type").option("--otp <code>", "TOTP code").option("--approval-payload-json <json>", "Inline approval payload JSON").option("--approval-payload-file <path>", "Path to approval payload JSON").addOption(new Option("--approve-id <approve-id>").hideHelp()),
1877
+ { auth: true, json: true }
1878
+ ).action(async (_options, command) => {
1879
+ await approvalApprove(getCommandFlags(command));
1880
+ });
1881
+ return program;
1882
+ };
1883
+ var main = async () => {
1884
+ const program = createProgram();
1885
+ if (process.argv.length <= 2) {
1886
+ program.outputHelp();
1887
+ return;
1888
+ }
1889
+ await program.parseAsync(process.argv);
1890
+ };
1891
+ main().catch((error) => {
1892
+ console.error(getErrorMessage(error));
1893
+ process.exit(1);
1894
+ });