@fiber-pay/cli 0.1.0-rc.7 → 0.1.1
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/README.md +6 -0
- package/dist/cli.js +1281 -348
- package/dist/cli.js.map +1 -1
- package/package.json +11 -4
package/dist/cli.js
CHANGED
|
@@ -1,167 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { join as
|
|
5
|
-
import { Command as
|
|
4
|
+
import { join as join11 } from "path";
|
|
5
|
+
import { Command as Command16 } from "commander";
|
|
6
6
|
|
|
7
|
-
// src/commands/
|
|
8
|
-
import { DEFAULT_FIBER_VERSION, downloadFiberBinary } from "@fiber-pay/node";
|
|
7
|
+
// src/commands/agent.ts
|
|
9
8
|
import { Command } from "commander";
|
|
10
9
|
|
|
11
|
-
// src/lib/
|
|
12
|
-
import {
|
|
13
|
-
import { BinaryManager, getFiberBinaryInfo } from "@fiber-pay/node";
|
|
14
|
-
|
|
15
|
-
// src/lib/node-runtime-daemon.ts
|
|
16
|
-
import { spawnSync } from "child_process";
|
|
17
|
-
import { existsSync } from "fs";
|
|
18
|
-
function getCustomBinaryState(binaryPath) {
|
|
19
|
-
const exists = existsSync(binaryPath);
|
|
20
|
-
if (!exists) {
|
|
21
|
-
return { path: binaryPath, ready: false, version: "unknown" };
|
|
22
|
-
}
|
|
23
|
-
try {
|
|
24
|
-
const result = spawnSync(binaryPath, ["--version"], { encoding: "utf-8" });
|
|
25
|
-
if (result.status !== 0) {
|
|
26
|
-
return { path: binaryPath, ready: false, version: "unknown" };
|
|
27
|
-
}
|
|
28
|
-
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
29
|
-
const firstLine = output.split("\n").find((line) => line.trim().length > 0) ?? "unknown";
|
|
30
|
-
return { path: binaryPath, ready: true, version: firstLine.trim() };
|
|
31
|
-
} catch {
|
|
32
|
-
return { path: binaryPath, ready: false, version: "unknown" };
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
function getBinaryVersion(binaryPath) {
|
|
36
|
-
try {
|
|
37
|
-
const result = spawnSync(binaryPath, ["--version"], { encoding: "utf-8" });
|
|
38
|
-
if (result.status !== 0) {
|
|
39
|
-
return "unknown";
|
|
40
|
-
}
|
|
41
|
-
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
42
|
-
if (!output) {
|
|
43
|
-
return "unknown";
|
|
44
|
-
}
|
|
45
|
-
const firstLine = output.split("\n").find((line) => line.trim().length > 0);
|
|
46
|
-
return firstLine?.trim() ?? "unknown";
|
|
47
|
-
} catch {
|
|
48
|
-
return "unknown";
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
function getCliEntrypoint() {
|
|
52
|
-
const entrypoint = process.argv[1];
|
|
53
|
-
if (!entrypoint) {
|
|
54
|
-
throw new Error("Unable to resolve CLI entrypoint path");
|
|
55
|
-
}
|
|
56
|
-
return entrypoint;
|
|
57
|
-
}
|
|
58
|
-
function startRuntimeDaemonFromNode(params) {
|
|
59
|
-
const cliEntrypoint = getCliEntrypoint();
|
|
60
|
-
const result = spawnSync(
|
|
61
|
-
process.execPath,
|
|
62
|
-
[
|
|
63
|
-
cliEntrypoint,
|
|
64
|
-
"--data-dir",
|
|
65
|
-
params.dataDir,
|
|
66
|
-
"--rpc-url",
|
|
67
|
-
params.rpcUrl,
|
|
68
|
-
"runtime",
|
|
69
|
-
"start",
|
|
70
|
-
"--daemon",
|
|
71
|
-
"--fiber-rpc-url",
|
|
72
|
-
params.rpcUrl,
|
|
73
|
-
"--proxy-listen",
|
|
74
|
-
params.proxyListen,
|
|
75
|
-
"--state-file",
|
|
76
|
-
params.stateFilePath,
|
|
77
|
-
"--alert-logs-base-dir",
|
|
78
|
-
params.alertLogsBaseDir,
|
|
79
|
-
"--json"
|
|
80
|
-
],
|
|
81
|
-
{ encoding: "utf-8" }
|
|
82
|
-
);
|
|
83
|
-
if (result.status === 0) {
|
|
84
|
-
return { ok: true };
|
|
85
|
-
}
|
|
86
|
-
const stderr = (result.stderr ?? "").trim();
|
|
87
|
-
const stdout = (result.stdout ?? "").trim();
|
|
88
|
-
const details = stderr || stdout || `exit code ${result.status ?? "unknown"}`;
|
|
89
|
-
return { ok: false, message: details };
|
|
90
|
-
}
|
|
91
|
-
function stopRuntimeDaemonFromNode(params) {
|
|
92
|
-
const cliEntrypoint = getCliEntrypoint();
|
|
93
|
-
spawnSync(
|
|
94
|
-
process.execPath,
|
|
95
|
-
[
|
|
96
|
-
cliEntrypoint,
|
|
97
|
-
"--data-dir",
|
|
98
|
-
params.dataDir,
|
|
99
|
-
"--rpc-url",
|
|
100
|
-
params.rpcUrl,
|
|
101
|
-
"runtime",
|
|
102
|
-
"stop",
|
|
103
|
-
"--json"
|
|
104
|
-
],
|
|
105
|
-
{ encoding: "utf-8" }
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// src/lib/binary-path.ts
|
|
110
|
-
function getProfileBinaryInstallDir(dataDir) {
|
|
111
|
-
return join(dataDir, "bin");
|
|
112
|
-
}
|
|
113
|
-
function getProfileManagedBinaryPath(dataDir) {
|
|
114
|
-
return new BinaryManager(getProfileBinaryInstallDir(dataDir)).getBinaryPath();
|
|
115
|
-
}
|
|
116
|
-
function validateConfiguredBinaryPath(binaryPath) {
|
|
117
|
-
const value = binaryPath.trim();
|
|
118
|
-
if (!value) {
|
|
119
|
-
throw new Error("Configured binaryPath cannot be empty");
|
|
120
|
-
}
|
|
121
|
-
if (value.includes("\0")) {
|
|
122
|
-
throw new Error("Configured binaryPath contains an invalid null byte");
|
|
123
|
-
}
|
|
124
|
-
return value;
|
|
125
|
-
}
|
|
126
|
-
function resolveBinaryPath(config) {
|
|
127
|
-
const managedPath = getProfileManagedBinaryPath(config.dataDir);
|
|
128
|
-
if (config.binaryPath) {
|
|
129
|
-
const binaryPath2 = validateConfiguredBinaryPath(config.binaryPath);
|
|
130
|
-
const installDir2 = dirname(binaryPath2);
|
|
131
|
-
const expectedPath = new BinaryManager(installDir2).getBinaryPath();
|
|
132
|
-
const managedByBinaryManager = expectedPath === binaryPath2;
|
|
133
|
-
const source = binaryPath2 === managedPath ? "profile-managed" : "configured-path";
|
|
134
|
-
return {
|
|
135
|
-
binaryPath: binaryPath2,
|
|
136
|
-
installDir: managedByBinaryManager ? installDir2 : null,
|
|
137
|
-
managedPath,
|
|
138
|
-
managedByBinaryManager,
|
|
139
|
-
source
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
const installDir = getProfileBinaryInstallDir(config.dataDir);
|
|
143
|
-
const binaryPath = managedPath;
|
|
144
|
-
return {
|
|
145
|
-
binaryPath,
|
|
146
|
-
installDir,
|
|
147
|
-
managedPath,
|
|
148
|
-
managedByBinaryManager: true,
|
|
149
|
-
source: "profile-managed"
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
function getBinaryManagerInstallDirOrThrow(resolvedBinary) {
|
|
153
|
-
if (resolvedBinary.installDir) {
|
|
154
|
-
return resolvedBinary.installDir;
|
|
155
|
-
}
|
|
156
|
-
throw new Error(
|
|
157
|
-
`Configured binaryPath "${resolvedBinary.binaryPath}" is incompatible with BinaryManager-managed path naming. BinaryManager expects "${new BinaryManager(dirname(resolvedBinary.binaryPath)).getBinaryPath()}". Set binaryPath to a standard managed name (fnn/fnn.exe) in the target directory, or unset binaryPath to use the profile-managed binary.`
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
async function getBinaryDetails(config) {
|
|
161
|
-
const resolvedBinary = resolveBinaryPath(config);
|
|
162
|
-
const info = resolvedBinary.installDir ? await getFiberBinaryInfo(resolvedBinary.installDir) : getCustomBinaryState(resolvedBinary.binaryPath);
|
|
163
|
-
return { resolvedBinary, info };
|
|
164
|
-
}
|
|
10
|
+
// src/lib/agent-call.ts
|
|
11
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
165
12
|
|
|
166
13
|
// src/lib/format.ts
|
|
167
14
|
import {
|
|
@@ -485,96 +332,21 @@ function printPeerListHuman(peers) {
|
|
|
485
332
|
}
|
|
486
333
|
}
|
|
487
334
|
|
|
488
|
-
// src/commands/binary.ts
|
|
489
|
-
function showProgress(progress) {
|
|
490
|
-
const percent = progress.percent !== void 0 ? ` (${progress.percent}%)` : "";
|
|
491
|
-
process.stdout.write(`\r[${progress.phase}]${percent} ${progress.message}`.padEnd(80));
|
|
492
|
-
if (progress.phase === "installing") {
|
|
493
|
-
console.log();
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
function createBinaryCommand(config) {
|
|
497
|
-
const binary = new Command("binary").description("Fiber binary management");
|
|
498
|
-
binary.command("download").option("--version <version>", "Fiber binary version", DEFAULT_FIBER_VERSION).option("--force", "Force re-download").option("--json").action(async (options) => {
|
|
499
|
-
const resolvedBinary = resolveBinaryPath(config);
|
|
500
|
-
let installDir;
|
|
501
|
-
try {
|
|
502
|
-
installDir = getBinaryManagerInstallDirOrThrow(resolvedBinary);
|
|
503
|
-
} catch (error) {
|
|
504
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
505
|
-
if (options.json) {
|
|
506
|
-
printJsonError({
|
|
507
|
-
code: "BINARY_PATH_INCOMPATIBLE",
|
|
508
|
-
message,
|
|
509
|
-
recoverable: true,
|
|
510
|
-
suggestion: "Use `fiber-pay config profile unset binaryPath` or set binaryPath to a standard fnn filename in the target directory."
|
|
511
|
-
});
|
|
512
|
-
} else {
|
|
513
|
-
console.error(`\u274C ${message}`);
|
|
514
|
-
}
|
|
515
|
-
process.exit(1);
|
|
516
|
-
}
|
|
517
|
-
const info = await downloadFiberBinary({
|
|
518
|
-
installDir,
|
|
519
|
-
version: options.version,
|
|
520
|
-
force: Boolean(options.force),
|
|
521
|
-
onProgress: options.json ? void 0 : showProgress
|
|
522
|
-
});
|
|
523
|
-
if (options.json) {
|
|
524
|
-
printJsonSuccess({
|
|
525
|
-
...info,
|
|
526
|
-
source: resolvedBinary.source,
|
|
527
|
-
resolvedPath: resolvedBinary.binaryPath
|
|
528
|
-
});
|
|
529
|
-
} else {
|
|
530
|
-
console.log("\n\u2705 Binary installed successfully!");
|
|
531
|
-
console.log(` Path: ${info.path}`);
|
|
532
|
-
console.log(` Version: ${info.version}`);
|
|
533
|
-
console.log(` Ready: ${info.ready ? "yes" : "no"}`);
|
|
534
|
-
}
|
|
535
|
-
});
|
|
536
|
-
binary.command("info").option("--json").action(async (options) => {
|
|
537
|
-
const { resolvedBinary, info } = await getBinaryDetails(config);
|
|
538
|
-
if (options.json) {
|
|
539
|
-
printJsonSuccess({
|
|
540
|
-
...info,
|
|
541
|
-
source: resolvedBinary.source,
|
|
542
|
-
resolvedPath: resolvedBinary.binaryPath
|
|
543
|
-
});
|
|
544
|
-
} else {
|
|
545
|
-
console.log(info.ready ? "\u2705 Binary is ready" : "\u274C Binary not found or not executable");
|
|
546
|
-
console.log(` Path: ${info.path}`);
|
|
547
|
-
console.log(` Version: ${info.version}`);
|
|
548
|
-
}
|
|
549
|
-
});
|
|
550
|
-
return binary;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
// src/commands/channel.ts
|
|
554
|
-
import { randomUUID } from "crypto";
|
|
555
|
-
import { ckbToShannons as ckbToShannons2 } from "@fiber-pay/sdk";
|
|
556
|
-
import { Command as Command2 } from "commander";
|
|
557
|
-
|
|
558
|
-
// src/lib/async.ts
|
|
559
|
-
function sleep(ms) {
|
|
560
|
-
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
561
|
-
}
|
|
562
|
-
|
|
563
335
|
// src/lib/rpc.ts
|
|
564
336
|
import { FiberRpcClient } from "@fiber-pay/sdk";
|
|
565
337
|
|
|
566
338
|
// src/lib/pid.ts
|
|
567
|
-
import { existsSync
|
|
568
|
-
import { join
|
|
339
|
+
import { existsSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
340
|
+
import { join } from "path";
|
|
569
341
|
function getPidFilePath(dataDir) {
|
|
570
|
-
return
|
|
342
|
+
return join(dataDir, "fiber.pid");
|
|
571
343
|
}
|
|
572
344
|
function writePidFile(dataDir, pid) {
|
|
573
345
|
writeFileSync(getPidFilePath(dataDir), String(pid));
|
|
574
346
|
}
|
|
575
347
|
function readPidFile(dataDir) {
|
|
576
348
|
const pidPath = getPidFilePath(dataDir);
|
|
577
|
-
if (!
|
|
349
|
+
if (!existsSync(pidPath)) return null;
|
|
578
350
|
try {
|
|
579
351
|
return parseInt(readFileSync(pidPath, "utf-8").trim(), 10);
|
|
580
352
|
} catch {
|
|
@@ -583,7 +355,7 @@ function readPidFile(dataDir) {
|
|
|
583
355
|
}
|
|
584
356
|
function removePidFile(dataDir) {
|
|
585
357
|
const pidPath = getPidFilePath(dataDir);
|
|
586
|
-
if (
|
|
358
|
+
if (existsSync(pidPath)) {
|
|
587
359
|
unlinkSync(pidPath);
|
|
588
360
|
}
|
|
589
361
|
}
|
|
@@ -597,20 +369,20 @@ function isProcessRunning(pid) {
|
|
|
597
369
|
}
|
|
598
370
|
|
|
599
371
|
// src/lib/runtime-meta.ts
|
|
600
|
-
import { existsSync as
|
|
601
|
-
import { join as
|
|
372
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
373
|
+
import { join as join2 } from "path";
|
|
602
374
|
function getRuntimePidFilePath(dataDir) {
|
|
603
|
-
return
|
|
375
|
+
return join2(dataDir, "runtime.pid");
|
|
604
376
|
}
|
|
605
377
|
function getRuntimeMetaFilePath(dataDir) {
|
|
606
|
-
return
|
|
378
|
+
return join2(dataDir, "runtime.meta.json");
|
|
607
379
|
}
|
|
608
380
|
function writeRuntimePid(dataDir, pid) {
|
|
609
381
|
writeFileSync2(getRuntimePidFilePath(dataDir), String(pid));
|
|
610
382
|
}
|
|
611
383
|
function readRuntimePid(dataDir) {
|
|
612
384
|
const pidPath = getRuntimePidFilePath(dataDir);
|
|
613
|
-
if (!
|
|
385
|
+
if (!existsSync2(pidPath)) return null;
|
|
614
386
|
try {
|
|
615
387
|
return Number.parseInt(readFileSync2(pidPath, "utf-8").trim(), 10);
|
|
616
388
|
} catch {
|
|
@@ -622,7 +394,7 @@ function writeRuntimeMeta(dataDir, meta) {
|
|
|
622
394
|
}
|
|
623
395
|
function readRuntimeMeta(dataDir) {
|
|
624
396
|
const metaPath = getRuntimeMetaFilePath(dataDir);
|
|
625
|
-
if (!
|
|
397
|
+
if (!existsSync2(metaPath)) return null;
|
|
626
398
|
try {
|
|
627
399
|
return JSON.parse(readFileSync2(metaPath, "utf-8"));
|
|
628
400
|
} catch {
|
|
@@ -632,10 +404,10 @@ function readRuntimeMeta(dataDir) {
|
|
|
632
404
|
function removeRuntimeFiles(dataDir) {
|
|
633
405
|
const pidPath = getRuntimePidFilePath(dataDir);
|
|
634
406
|
const metaPath = getRuntimeMetaFilePath(dataDir);
|
|
635
|
-
if (
|
|
407
|
+
if (existsSync2(pidPath)) {
|
|
636
408
|
unlinkSync2(pidPath);
|
|
637
409
|
}
|
|
638
|
-
if (
|
|
410
|
+
if (existsSync2(metaPath)) {
|
|
639
411
|
unlinkSync2(metaPath);
|
|
640
412
|
}
|
|
641
413
|
}
|
|
@@ -664,32 +436,808 @@ function resolveRuntimeProxyUrl(config) {
|
|
|
664
436
|
if (runtimeMeta.proxyListen.startsWith("http://") || runtimeMeta.proxyListen.startsWith("https://")) {
|
|
665
437
|
return runtimeMeta.proxyListen;
|
|
666
438
|
}
|
|
667
|
-
return `http://${runtimeMeta.proxyListen}`;
|
|
439
|
+
return `http://${runtimeMeta.proxyListen}`;
|
|
440
|
+
}
|
|
441
|
+
function createRpcClient(config) {
|
|
442
|
+
const resolved = resolveRpcEndpoint(config);
|
|
443
|
+
return new FiberRpcClient({
|
|
444
|
+
url: resolved.url,
|
|
445
|
+
biscuitToken: config.rpcBiscuitToken
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
function resolveRpcEndpoint(config) {
|
|
449
|
+
const runtimeProxyUrl = resolveRuntimeProxyUrl(config);
|
|
450
|
+
if (runtimeProxyUrl) {
|
|
451
|
+
return {
|
|
452
|
+
url: runtimeProxyUrl,
|
|
453
|
+
target: "runtime-proxy"
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
return {
|
|
457
|
+
url: config.rpcUrl,
|
|
458
|
+
target: "node-rpc"
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
async function createReadyRpcClient(config, options = {}) {
|
|
462
|
+
const rpc = createRpcClient(config);
|
|
463
|
+
await rpc.waitForReady({ timeout: options.timeout ?? 3e3, interval: options.interval ?? 500 });
|
|
464
|
+
return rpc;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// src/lib/agent-call.ts
|
|
468
|
+
function formatDuration(durationMs) {
|
|
469
|
+
if (typeof durationMs !== "number" || Number.isNaN(durationMs)) {
|
|
470
|
+
return "unknown";
|
|
471
|
+
}
|
|
472
|
+
return `${durationMs}ms`;
|
|
473
|
+
}
|
|
474
|
+
function printFriendlySuccess(result, options) {
|
|
475
|
+
const agent = typeof result.agent === "string" ? result.agent : "unknown";
|
|
476
|
+
const duration = formatDuration(result.durationMs);
|
|
477
|
+
const response = typeof result.response === "string" ? result.response : JSON.stringify(result.response, null, 2);
|
|
478
|
+
console.log("Agent call succeeded");
|
|
479
|
+
console.log(` Agent: ${agent}`);
|
|
480
|
+
console.log(` Duration: ${duration}`);
|
|
481
|
+
console.log(` Payment: ${options.paymentRequired ? "required" : "not required"}`);
|
|
482
|
+
if (options.paymentHash) {
|
|
483
|
+
console.log(` Payment hash: ${options.paymentHash}`);
|
|
484
|
+
}
|
|
485
|
+
console.log("");
|
|
486
|
+
console.log("Agent response:");
|
|
487
|
+
console.log(response ?? "");
|
|
488
|
+
}
|
|
489
|
+
async function runAgentCallCommand(config, url, options) {
|
|
490
|
+
const asJson = Boolean(options.json);
|
|
491
|
+
const timeoutMs = parseInt(options.timeout, 10) * 1e3;
|
|
492
|
+
let prompt = options.prompt;
|
|
493
|
+
if (!prompt && options.file) {
|
|
494
|
+
try {
|
|
495
|
+
prompt = readFileSync3(options.file, "utf-8").trim();
|
|
496
|
+
} catch (err) {
|
|
497
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
498
|
+
if (asJson) {
|
|
499
|
+
printJsonError({
|
|
500
|
+
code: "AGENT_CALL_FILE_READ_ERROR",
|
|
501
|
+
message: `Failed to read prompt file: ${message}`,
|
|
502
|
+
recoverable: true,
|
|
503
|
+
suggestion: "Check the file path and try again."
|
|
504
|
+
});
|
|
505
|
+
} else {
|
|
506
|
+
console.error(`Error: Failed to read prompt file: ${message}`);
|
|
507
|
+
}
|
|
508
|
+
process.exit(1);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
if (!prompt) {
|
|
512
|
+
if (!process.stdin.isTTY) {
|
|
513
|
+
const chunks = [];
|
|
514
|
+
for await (const chunk of process.stdin) {
|
|
515
|
+
chunks.push(chunk);
|
|
516
|
+
}
|
|
517
|
+
prompt = Buffer.concat(chunks).toString("utf-8").trim();
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
if (!prompt) {
|
|
521
|
+
if (asJson) {
|
|
522
|
+
printJsonError({
|
|
523
|
+
code: "AGENT_CALL_NO_PROMPT",
|
|
524
|
+
message: "No prompt provided.",
|
|
525
|
+
recoverable: true,
|
|
526
|
+
suggestion: "Use --prompt <text>, --file <path>, or pipe via stdin."
|
|
527
|
+
});
|
|
528
|
+
} else {
|
|
529
|
+
console.error("Error: No prompt provided.");
|
|
530
|
+
console.error(" Use --prompt <text>, --file <path>, or pipe via stdin.");
|
|
531
|
+
}
|
|
532
|
+
process.exit(1);
|
|
533
|
+
}
|
|
534
|
+
const targetUrl = url.endsWith("/") ? url : `${url}/`;
|
|
535
|
+
const controller = new AbortController();
|
|
536
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
537
|
+
try {
|
|
538
|
+
if (!asJson) {
|
|
539
|
+
console.log(`Calling agent at ${targetUrl}...`);
|
|
540
|
+
}
|
|
541
|
+
const initialResponse = await fetch(targetUrl, {
|
|
542
|
+
method: "POST",
|
|
543
|
+
headers: { "Content-Type": "application/json" },
|
|
544
|
+
body: JSON.stringify({ prompt }),
|
|
545
|
+
signal: controller.signal
|
|
546
|
+
});
|
|
547
|
+
if (initialResponse.ok) {
|
|
548
|
+
const body = await initialResponse.json();
|
|
549
|
+
clearTimeout(timer);
|
|
550
|
+
if (asJson) {
|
|
551
|
+
printJsonSuccess(body);
|
|
552
|
+
} else {
|
|
553
|
+
printFriendlySuccess(body, { paymentRequired: false });
|
|
554
|
+
}
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
if (initialResponse.status !== 402 && initialResponse.status !== 401) {
|
|
558
|
+
const body = await initialResponse.text();
|
|
559
|
+
clearTimeout(timer);
|
|
560
|
+
if (asJson) {
|
|
561
|
+
printJsonError({
|
|
562
|
+
code: "AGENT_CALL_UNEXPECTED_STATUS",
|
|
563
|
+
message: `Unexpected status ${initialResponse.status} from agent.`,
|
|
564
|
+
recoverable: false,
|
|
565
|
+
details: { body: body.slice(0, 500) }
|
|
566
|
+
});
|
|
567
|
+
} else {
|
|
568
|
+
console.error(`Error: Unexpected status ${initialResponse.status}`);
|
|
569
|
+
console.error(body.slice(0, 500));
|
|
570
|
+
}
|
|
571
|
+
process.exit(1);
|
|
572
|
+
}
|
|
573
|
+
const challengeBody = await initialResponse.json();
|
|
574
|
+
const macaroon = challengeBody.macaroon;
|
|
575
|
+
const invoiceAddress = challengeBody.invoice;
|
|
576
|
+
if (!macaroon || !invoiceAddress) {
|
|
577
|
+
clearTimeout(timer);
|
|
578
|
+
if (asJson) {
|
|
579
|
+
printJsonError({
|
|
580
|
+
code: "AGENT_CALL_INVALID_CHALLENGE",
|
|
581
|
+
message: "Invalid L402 challenge: missing macaroon or invoice.",
|
|
582
|
+
recoverable: false
|
|
583
|
+
});
|
|
584
|
+
} else {
|
|
585
|
+
console.error("Error: Invalid L402 challenge response (missing macaroon or invoice).");
|
|
586
|
+
}
|
|
587
|
+
process.exit(1);
|
|
588
|
+
}
|
|
589
|
+
if (!asJson) {
|
|
590
|
+
console.log("Payment required. Paying invoice via Fiber...");
|
|
591
|
+
}
|
|
592
|
+
const rpcClient = await createReadyRpcClient(config);
|
|
593
|
+
const paymentResult = await rpcClient.sendPayment({
|
|
594
|
+
invoice: invoiceAddress
|
|
595
|
+
});
|
|
596
|
+
if (!asJson) {
|
|
597
|
+
console.log(
|
|
598
|
+
`Payment sent (hash: ${paymentResult.payment_hash}). Waiting for confirmation...`
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
const finalPayment = await rpcClient.waitForPayment(paymentResult.payment_hash, {
|
|
602
|
+
timeout: timeoutMs,
|
|
603
|
+
interval: 2e3
|
|
604
|
+
});
|
|
605
|
+
if (finalPayment.status !== "Success") {
|
|
606
|
+
clearTimeout(timer);
|
|
607
|
+
if (asJson) {
|
|
608
|
+
printJsonError({
|
|
609
|
+
code: "AGENT_CALL_PAYMENT_FAILED",
|
|
610
|
+
message: `Payment failed: ${finalPayment.failed_error ?? finalPayment.status}`,
|
|
611
|
+
recoverable: false
|
|
612
|
+
});
|
|
613
|
+
} else {
|
|
614
|
+
console.error(`Error: Payment failed: ${finalPayment.failed_error ?? finalPayment.status}`);
|
|
615
|
+
}
|
|
616
|
+
process.exit(1);
|
|
617
|
+
}
|
|
618
|
+
if (!asJson) {
|
|
619
|
+
console.log("Payment confirmed. Retrying request with L402 token...");
|
|
620
|
+
}
|
|
621
|
+
const retryResponse = await fetch(targetUrl, {
|
|
622
|
+
method: "POST",
|
|
623
|
+
headers: {
|
|
624
|
+
"Content-Type": "application/json",
|
|
625
|
+
Authorization: `L402 ${macaroon}`,
|
|
626
|
+
"X-L402-Payment-Hash": paymentResult.payment_hash
|
|
627
|
+
},
|
|
628
|
+
body: JSON.stringify({ prompt }),
|
|
629
|
+
signal: controller.signal
|
|
630
|
+
});
|
|
631
|
+
clearTimeout(timer);
|
|
632
|
+
if (!retryResponse.ok) {
|
|
633
|
+
const errBody = await retryResponse.text();
|
|
634
|
+
if (asJson) {
|
|
635
|
+
printJsonError({
|
|
636
|
+
code: "AGENT_CALL_RETRY_FAILED",
|
|
637
|
+
message: `Agent returned ${retryResponse.status} after payment.`,
|
|
638
|
+
recoverable: false,
|
|
639
|
+
details: { body: errBody.slice(0, 500) }
|
|
640
|
+
});
|
|
641
|
+
} else {
|
|
642
|
+
console.error(`Error: Agent returned ${retryResponse.status} after payment.`);
|
|
643
|
+
console.error(errBody.slice(0, 500));
|
|
644
|
+
}
|
|
645
|
+
process.exit(1);
|
|
646
|
+
}
|
|
647
|
+
const result = await retryResponse.json();
|
|
648
|
+
if (asJson) {
|
|
649
|
+
printJsonSuccess({
|
|
650
|
+
...result,
|
|
651
|
+
paymentHash: paymentResult.payment_hash
|
|
652
|
+
});
|
|
653
|
+
} else {
|
|
654
|
+
printFriendlySuccess(result, {
|
|
655
|
+
paymentRequired: true,
|
|
656
|
+
paymentHash: paymentResult.payment_hash
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
} catch (error) {
|
|
660
|
+
clearTimeout(timer);
|
|
661
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
662
|
+
const isTimeout = error instanceof Error && error.name === "AbortError";
|
|
663
|
+
if (asJson) {
|
|
664
|
+
printJsonError({
|
|
665
|
+
code: isTimeout ? "AGENT_CALL_TIMEOUT" : "AGENT_CALL_ERROR",
|
|
666
|
+
message: isTimeout ? "Request timed out." : message,
|
|
667
|
+
recoverable: !isTimeout
|
|
668
|
+
});
|
|
669
|
+
} else {
|
|
670
|
+
console.error(`Error: ${isTimeout ? "Request timed out." : message}`);
|
|
671
|
+
}
|
|
672
|
+
process.exit(1);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// src/lib/agent-serve.ts
|
|
677
|
+
import { execFileSync, spawn } from "child_process";
|
|
678
|
+
import { createServer } from "http";
|
|
679
|
+
import { createL402Middleware, FiberRpcClient as FiberRpcClient2 } from "@fiber-pay/sdk";
|
|
680
|
+
import express from "express";
|
|
681
|
+
function getClientIp(req) {
|
|
682
|
+
const forwarded = req.headers["x-forwarded-for"];
|
|
683
|
+
const forwardedValue = Array.isArray(forwarded) ? forwarded[0] : forwarded;
|
|
684
|
+
if (typeof forwardedValue === "string" && forwardedValue.trim().length > 0) {
|
|
685
|
+
return forwardedValue.split(",")[0]?.trim() || "unknown";
|
|
686
|
+
}
|
|
687
|
+
return req.ip || req.socket.remoteAddress || "unknown";
|
|
688
|
+
}
|
|
689
|
+
function summarizeL402Status(req) {
|
|
690
|
+
if (req.l402?.valid) {
|
|
691
|
+
if (req.l402.paymentHash) {
|
|
692
|
+
return `payment-verified:${req.l402.paymentHash.slice(0, 14)}...`;
|
|
693
|
+
}
|
|
694
|
+
if (req.l402.preimage) {
|
|
695
|
+
return "payment-verified:preimage";
|
|
696
|
+
}
|
|
697
|
+
return "payment-verified";
|
|
698
|
+
}
|
|
699
|
+
return "no-l402-token";
|
|
700
|
+
}
|
|
701
|
+
function getRequestId(req) {
|
|
702
|
+
return req._fiberPayRequestId ?? 0;
|
|
703
|
+
}
|
|
704
|
+
function runAcpx(agent, prompt, options) {
|
|
705
|
+
return new Promise((resolve2) => {
|
|
706
|
+
const args = ["--format", "quiet"];
|
|
707
|
+
if (options.approveAll) {
|
|
708
|
+
args.push("--approve-all");
|
|
709
|
+
}
|
|
710
|
+
if (options.cwd) {
|
|
711
|
+
args.push("--cwd", options.cwd);
|
|
712
|
+
}
|
|
713
|
+
if (options.timeoutSeconds > 0) {
|
|
714
|
+
args.push("--timeout", String(options.timeoutSeconds));
|
|
715
|
+
}
|
|
716
|
+
args.push(agent, "exec", prompt);
|
|
717
|
+
const child = spawn("acpx", args, {
|
|
718
|
+
cwd: options.cwd || process.cwd(),
|
|
719
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
720
|
+
env: { ...process.env }
|
|
721
|
+
});
|
|
722
|
+
let stdout = "";
|
|
723
|
+
let stderr = "";
|
|
724
|
+
child.stdout.on("data", (data) => {
|
|
725
|
+
stdout += data.toString();
|
|
726
|
+
});
|
|
727
|
+
child.stderr.on("data", (data) => {
|
|
728
|
+
stderr += data.toString();
|
|
729
|
+
});
|
|
730
|
+
const killTimer = setTimeout(
|
|
731
|
+
() => {
|
|
732
|
+
child.kill("SIGKILL");
|
|
733
|
+
},
|
|
734
|
+
(options.timeoutSeconds + 30) * 1e3
|
|
735
|
+
);
|
|
736
|
+
child.on("close", (code) => {
|
|
737
|
+
clearTimeout(killTimer);
|
|
738
|
+
resolve2({ stdout, stderr, exitCode: code ?? 1 });
|
|
739
|
+
});
|
|
740
|
+
child.on("error", (err) => {
|
|
741
|
+
clearTimeout(killTimer);
|
|
742
|
+
resolve2({ stdout: "", stderr: err.message, exitCode: 1 });
|
|
743
|
+
});
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
async function runAgentServeCommand(config, options) {
|
|
747
|
+
const asJson = Boolean(options.json);
|
|
748
|
+
const port = parseInt(options.port, 10);
|
|
749
|
+
const host = options.host;
|
|
750
|
+
const priceCkb = parseFloat(options.price);
|
|
751
|
+
const expirySeconds = parseInt(options.expiry, 10);
|
|
752
|
+
const timeoutSeconds = parseInt(options.timeout, 10);
|
|
753
|
+
const rootKey = options.rootKey || process.env.L402_ROOT_KEY;
|
|
754
|
+
if (Number.isNaN(port) || port < 1 || port > 65535) {
|
|
755
|
+
if (asJson) {
|
|
756
|
+
printJsonError({
|
|
757
|
+
code: "AGENT_SERVE_INVALID_PORT",
|
|
758
|
+
message: `Invalid port: ${options.port}`,
|
|
759
|
+
recoverable: true,
|
|
760
|
+
suggestion: "Provide a valid port number between 1 and 65535."
|
|
761
|
+
});
|
|
762
|
+
} else {
|
|
763
|
+
console.error(`Error: Invalid port: ${options.port}`);
|
|
764
|
+
}
|
|
765
|
+
process.exit(1);
|
|
766
|
+
}
|
|
767
|
+
if (Number.isNaN(priceCkb) || priceCkb <= 0) {
|
|
768
|
+
if (asJson) {
|
|
769
|
+
printJsonError({
|
|
770
|
+
code: "AGENT_SERVE_INVALID_PRICE",
|
|
771
|
+
message: `Invalid price: ${options.price}`,
|
|
772
|
+
recoverable: true,
|
|
773
|
+
suggestion: "Provide a positive CKB amount, e.g. --price 0.1"
|
|
774
|
+
});
|
|
775
|
+
} else {
|
|
776
|
+
console.error(`Error: Invalid price: ${options.price}`);
|
|
777
|
+
}
|
|
778
|
+
process.exit(1);
|
|
779
|
+
}
|
|
780
|
+
if (!rootKey) {
|
|
781
|
+
if (asJson) {
|
|
782
|
+
printJsonError({
|
|
783
|
+
code: "AGENT_SERVE_MISSING_ROOT_KEY",
|
|
784
|
+
message: "L402 root key is required.",
|
|
785
|
+
recoverable: true,
|
|
786
|
+
suggestion: "Provide --root-key <64-hex-chars> or set L402_ROOT_KEY env var. Generate with: openssl rand -hex 32"
|
|
787
|
+
});
|
|
788
|
+
} else {
|
|
789
|
+
console.error("Error: L402 root key is required.");
|
|
790
|
+
console.error(" Provide --root-key <64-hex-chars> or set L402_ROOT_KEY env var.");
|
|
791
|
+
console.error(" Generate one with: openssl rand -hex 32");
|
|
792
|
+
}
|
|
793
|
+
process.exit(1);
|
|
794
|
+
}
|
|
795
|
+
try {
|
|
796
|
+
execFileSync("acpx", ["--version"], { stdio: "ignore" });
|
|
797
|
+
} catch {
|
|
798
|
+
if (asJson) {
|
|
799
|
+
printJsonError({
|
|
800
|
+
code: "AGENT_SERVE_ACPX_NOT_FOUND",
|
|
801
|
+
message: "acpx is not installed or not in PATH.",
|
|
802
|
+
recoverable: true,
|
|
803
|
+
suggestion: "Install acpx globally: npm install -g acpx"
|
|
804
|
+
});
|
|
805
|
+
} else {
|
|
806
|
+
console.error("Error: acpx is not installed or not in PATH.");
|
|
807
|
+
console.error(" Install it with: npm install -g acpx");
|
|
808
|
+
console.error(" See: https://github.com/openclaw/acpx");
|
|
809
|
+
}
|
|
810
|
+
process.exit(1);
|
|
811
|
+
}
|
|
812
|
+
const rpcClient = new FiberRpcClient2({
|
|
813
|
+
url: config.rpcUrl,
|
|
814
|
+
biscuitToken: config.rpcBiscuitToken
|
|
815
|
+
});
|
|
816
|
+
const currency = config.network === "mainnet" ? "Fibb" : "Fibt";
|
|
817
|
+
const app = express();
|
|
818
|
+
app.use(express.json());
|
|
819
|
+
let requestCounter = 0;
|
|
820
|
+
app.use((req, res, next) => {
|
|
821
|
+
const requestId = ++requestCounter;
|
|
822
|
+
req._fiberPayRequestId = requestId;
|
|
823
|
+
const startTime = Date.now();
|
|
824
|
+
const clientIp = getClientIp(req);
|
|
825
|
+
if (!asJson) {
|
|
826
|
+
console.log(
|
|
827
|
+
`[REQ ${requestId}] ${req.method} ${req.path} from ${clientIp} (auth=${req.headers.authorization ? "present" : "none"})`
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
res.on("finish", () => {
|
|
831
|
+
if (asJson) {
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
const durationMs = Date.now() - startTime;
|
|
835
|
+
const l402State = summarizeL402Status(req);
|
|
836
|
+
if (res.statusCode === 402 || res.statusCode === 401) {
|
|
837
|
+
console.log(
|
|
838
|
+
`[REQ ${requestId}] challenge-issued status=${res.statusCode} duration=${durationMs}ms`
|
|
839
|
+
);
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
console.log(
|
|
843
|
+
`[REQ ${requestId}] completed status=${res.statusCode} duration=${durationMs}ms l402=${l402State}`
|
|
844
|
+
);
|
|
845
|
+
});
|
|
846
|
+
next();
|
|
847
|
+
});
|
|
848
|
+
app.use(
|
|
849
|
+
createL402Middleware({
|
|
850
|
+
rootKey,
|
|
851
|
+
priceCkb,
|
|
852
|
+
expirySeconds,
|
|
853
|
+
rpcClient,
|
|
854
|
+
currency
|
|
855
|
+
})
|
|
856
|
+
);
|
|
857
|
+
app.post("/", async (req, res) => {
|
|
858
|
+
const requestId = getRequestId(req);
|
|
859
|
+
const prompt = req.body?.prompt;
|
|
860
|
+
if (!prompt || typeof prompt !== "string") {
|
|
861
|
+
if (!asJson) {
|
|
862
|
+
console.log(`[REQ ${requestId}] invalid request body: missing string prompt`);
|
|
863
|
+
}
|
|
864
|
+
res.status(400).json({
|
|
865
|
+
error: 'Missing or invalid "prompt" field in request body.'
|
|
866
|
+
});
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
const startTime = Date.now();
|
|
870
|
+
try {
|
|
871
|
+
if (!asJson) {
|
|
872
|
+
console.log(
|
|
873
|
+
`[REQ ${requestId}] payment accepted, invoking agent=${options.agent} promptChars=${prompt.length}`
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
const result = await runAcpx(options.agent, prompt, {
|
|
877
|
+
cwd: options.cwd,
|
|
878
|
+
approveAll: options.approveAll,
|
|
879
|
+
timeoutSeconds
|
|
880
|
+
});
|
|
881
|
+
const durationMs = Date.now() - startTime;
|
|
882
|
+
if (result.exitCode !== 0) {
|
|
883
|
+
if (!asJson) {
|
|
884
|
+
console.log(
|
|
885
|
+
`[REQ ${requestId}] agent failed exit=${result.exitCode} duration=${durationMs}ms`
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
res.status(502).json({
|
|
889
|
+
error: "Agent execution failed.",
|
|
890
|
+
agent: options.agent,
|
|
891
|
+
stderr: result.stderr.slice(0, 1e3),
|
|
892
|
+
durationMs
|
|
893
|
+
});
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
if (!asJson) {
|
|
897
|
+
console.log(`[REQ ${requestId}] agent completed duration=${durationMs}ms`);
|
|
898
|
+
}
|
|
899
|
+
res.json({
|
|
900
|
+
response: result.stdout.trim(),
|
|
901
|
+
agent: options.agent,
|
|
902
|
+
durationMs
|
|
903
|
+
});
|
|
904
|
+
} catch (error) {
|
|
905
|
+
if (!asJson) {
|
|
906
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
907
|
+
console.log(`[REQ ${requestId}] agent execution error: ${message}`);
|
|
908
|
+
}
|
|
909
|
+
res.status(500).json({
|
|
910
|
+
error: "Internal server error.",
|
|
911
|
+
message: error instanceof Error ? error.message : String(error)
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
});
|
|
915
|
+
app.get("/health", (_req, res) => {
|
|
916
|
+
res.json({ status: "ok", agent: options.agent });
|
|
917
|
+
});
|
|
918
|
+
const server = createServer(app);
|
|
919
|
+
await new Promise((resolve2, reject) => {
|
|
920
|
+
server.on("error", (err) => {
|
|
921
|
+
if (err.code === "EADDRINUSE") {
|
|
922
|
+
if (asJson) {
|
|
923
|
+
printJsonError({
|
|
924
|
+
code: "AGENT_SERVE_PORT_IN_USE",
|
|
925
|
+
message: `Port ${port} is already in use.`,
|
|
926
|
+
recoverable: true,
|
|
927
|
+
suggestion: "Use a different port with --port <port>."
|
|
928
|
+
});
|
|
929
|
+
} else {
|
|
930
|
+
console.error(`Error: Port ${port} is already in use.`);
|
|
931
|
+
}
|
|
932
|
+
process.exit(1);
|
|
933
|
+
}
|
|
934
|
+
reject(err);
|
|
935
|
+
});
|
|
936
|
+
server.listen(port, host, () => {
|
|
937
|
+
resolve2();
|
|
938
|
+
});
|
|
939
|
+
});
|
|
940
|
+
const listenUrl = `http://${host}:${port}`;
|
|
941
|
+
if (asJson) {
|
|
942
|
+
printJsonSuccess({
|
|
943
|
+
status: "running",
|
|
944
|
+
listen: listenUrl,
|
|
945
|
+
agent: options.agent,
|
|
946
|
+
priceCkb,
|
|
947
|
+
expirySeconds,
|
|
948
|
+
currency,
|
|
949
|
+
fiberRpcUrl: config.rpcUrl
|
|
950
|
+
});
|
|
951
|
+
} else {
|
|
952
|
+
console.log("Agent service started");
|
|
953
|
+
console.log(` Listen: ${listenUrl}`);
|
|
954
|
+
console.log(` Agent: ${options.agent}`);
|
|
955
|
+
console.log(` Price: ${priceCkb} CKB per request`);
|
|
956
|
+
console.log(` Expiry: ${expirySeconds}s`);
|
|
957
|
+
console.log(` Timeout: ${timeoutSeconds}s per agent call`);
|
|
958
|
+
console.log(` Currency: ${currency}`);
|
|
959
|
+
console.log(` Fiber RPC: ${config.rpcUrl}`);
|
|
960
|
+
console.log("");
|
|
961
|
+
console.log("Endpoint:");
|
|
962
|
+
console.log(` POST ${listenUrl}/ {"prompt": "your question"}`);
|
|
963
|
+
console.log("");
|
|
964
|
+
console.log("Press Ctrl+C to stop.");
|
|
965
|
+
}
|
|
966
|
+
const shutdown = () => {
|
|
967
|
+
if (!asJson) {
|
|
968
|
+
console.log("\nStopping agent service...");
|
|
969
|
+
}
|
|
970
|
+
server.close(() => {
|
|
971
|
+
if (asJson) {
|
|
972
|
+
printJsonSuccess({ status: "stopped" });
|
|
973
|
+
} else {
|
|
974
|
+
console.log("Agent service stopped.");
|
|
975
|
+
}
|
|
976
|
+
process.exit(0);
|
|
977
|
+
});
|
|
978
|
+
setTimeout(() => process.exit(0), 5e3);
|
|
979
|
+
};
|
|
980
|
+
process.on("SIGINT", shutdown);
|
|
981
|
+
process.on("SIGTERM", shutdown);
|
|
982
|
+
await new Promise(() => {
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// src/commands/agent.ts
|
|
987
|
+
function createAgentCommand(config) {
|
|
988
|
+
const agent = new Command("agent").description("AI agent service with L402 payment");
|
|
989
|
+
agent.command("serve").description("Start an L402-gated AI agent HTTP service").requiredOption(
|
|
990
|
+
"--agent <name>",
|
|
991
|
+
"Agent to use (common values: codex, claude, opencode, gemini, pi; see acpx docs for full list)"
|
|
992
|
+
).option("--port <port>", "Listen port", "8402").option("--host <host>", "Listen host", "127.0.0.1").option("--price <ckb>", "Price per request in CKB", "0.1").option("--root-key <hex>", "Macaroon root key (32-byte hex, or set L402_ROOT_KEY env)").option("--expiry <seconds>", "Token expiry in seconds", "3600").option("--cwd <path>", "Working directory for agent execution").option("--approve-all", "Auto-approve all agent tool calls").option("--timeout <seconds>", "Max agent execution time per request", "300").option("--json").addHelpText(
|
|
993
|
+
"after",
|
|
994
|
+
[
|
|
995
|
+
"",
|
|
996
|
+
"Examples:",
|
|
997
|
+
" fiber-pay agent serve --agent codex --price 0.1 --root-key <hex>",
|
|
998
|
+
" fiber-pay agent serve --agent claude --price 0.2 --port 8402"
|
|
999
|
+
].join("\n")
|
|
1000
|
+
).action(async (options) => {
|
|
1001
|
+
await runAgentServeCommand(config, options);
|
|
1002
|
+
});
|
|
1003
|
+
agent.command("call").description("Call a remote L402-gated agent service (auto-pay via Fiber)").argument("<url>", "Agent service URL (e.g. http://host:8402)").option("--prompt <text>", "Prompt text to send").option("--file <path>", "Read prompt from file").option("--timeout <seconds>", "Request timeout in seconds", "300").option("--json").action(async (url, options) => {
|
|
1004
|
+
await runAgentCallCommand(config, url, options);
|
|
1005
|
+
});
|
|
1006
|
+
return agent;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// src/commands/binary.ts
|
|
1010
|
+
import { DEFAULT_FIBER_VERSION, downloadFiberBinary } from "@fiber-pay/node";
|
|
1011
|
+
import { Command as Command2 } from "commander";
|
|
1012
|
+
|
|
1013
|
+
// src/lib/binary-path.ts
|
|
1014
|
+
import { dirname, join as join3 } from "path";
|
|
1015
|
+
import { BinaryManager, getFiberBinaryInfo } from "@fiber-pay/node";
|
|
1016
|
+
|
|
1017
|
+
// src/lib/node-runtime-daemon.ts
|
|
1018
|
+
import { spawnSync } from "child_process";
|
|
1019
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1020
|
+
function getCustomBinaryState(binaryPath) {
|
|
1021
|
+
const exists = existsSync3(binaryPath);
|
|
1022
|
+
if (!exists) {
|
|
1023
|
+
return { path: binaryPath, ready: false, version: "unknown" };
|
|
1024
|
+
}
|
|
1025
|
+
try {
|
|
1026
|
+
const result = spawnSync(binaryPath, ["--version"], { encoding: "utf-8" });
|
|
1027
|
+
if (result.status !== 0) {
|
|
1028
|
+
return { path: binaryPath, ready: false, version: "unknown" };
|
|
1029
|
+
}
|
|
1030
|
+
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
1031
|
+
const firstLine = output.split("\n").find((line) => line.trim().length > 0) ?? "unknown";
|
|
1032
|
+
return { path: binaryPath, ready: true, version: firstLine.trim() };
|
|
1033
|
+
} catch {
|
|
1034
|
+
return { path: binaryPath, ready: false, version: "unknown" };
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
function getBinaryVersion(binaryPath) {
|
|
1038
|
+
try {
|
|
1039
|
+
const result = spawnSync(binaryPath, ["--version"], { encoding: "utf-8" });
|
|
1040
|
+
if (result.status !== 0) {
|
|
1041
|
+
return "unknown";
|
|
1042
|
+
}
|
|
1043
|
+
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
1044
|
+
if (!output) {
|
|
1045
|
+
return "unknown";
|
|
1046
|
+
}
|
|
1047
|
+
const firstLine = output.split("\n").find((line) => line.trim().length > 0);
|
|
1048
|
+
return firstLine?.trim() ?? "unknown";
|
|
1049
|
+
} catch {
|
|
1050
|
+
return "unknown";
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
function getCliEntrypoint() {
|
|
1054
|
+
const entrypoint = process.argv[1];
|
|
1055
|
+
if (!entrypoint) {
|
|
1056
|
+
throw new Error("Unable to resolve CLI entrypoint path");
|
|
1057
|
+
}
|
|
1058
|
+
return entrypoint;
|
|
1059
|
+
}
|
|
1060
|
+
function startRuntimeDaemonFromNode(params) {
|
|
1061
|
+
const cliEntrypoint = getCliEntrypoint();
|
|
1062
|
+
const result = spawnSync(
|
|
1063
|
+
process.execPath,
|
|
1064
|
+
[
|
|
1065
|
+
cliEntrypoint,
|
|
1066
|
+
"--data-dir",
|
|
1067
|
+
params.dataDir,
|
|
1068
|
+
"--rpc-url",
|
|
1069
|
+
params.rpcUrl,
|
|
1070
|
+
"runtime",
|
|
1071
|
+
"start",
|
|
1072
|
+
"--daemon",
|
|
1073
|
+
"--fiber-rpc-url",
|
|
1074
|
+
params.rpcUrl,
|
|
1075
|
+
"--proxy-listen",
|
|
1076
|
+
params.proxyListen,
|
|
1077
|
+
"--state-file",
|
|
1078
|
+
params.stateFilePath,
|
|
1079
|
+
"--alert-logs-base-dir",
|
|
1080
|
+
params.alertLogsBaseDir,
|
|
1081
|
+
"--json"
|
|
1082
|
+
],
|
|
1083
|
+
{ encoding: "utf-8" }
|
|
1084
|
+
);
|
|
1085
|
+
if (result.status === 0) {
|
|
1086
|
+
return { ok: true };
|
|
1087
|
+
}
|
|
1088
|
+
const stderr = (result.stderr ?? "").trim();
|
|
1089
|
+
const stdout = (result.stdout ?? "").trim();
|
|
1090
|
+
const details = stderr || stdout || `exit code ${result.status ?? "unknown"}`;
|
|
1091
|
+
return { ok: false, message: details };
|
|
1092
|
+
}
|
|
1093
|
+
function stopRuntimeDaemonFromNode(params) {
|
|
1094
|
+
const cliEntrypoint = getCliEntrypoint();
|
|
1095
|
+
spawnSync(
|
|
1096
|
+
process.execPath,
|
|
1097
|
+
[
|
|
1098
|
+
cliEntrypoint,
|
|
1099
|
+
"--data-dir",
|
|
1100
|
+
params.dataDir,
|
|
1101
|
+
"--rpc-url",
|
|
1102
|
+
params.rpcUrl,
|
|
1103
|
+
"runtime",
|
|
1104
|
+
"stop",
|
|
1105
|
+
"--json"
|
|
1106
|
+
],
|
|
1107
|
+
{ encoding: "utf-8" }
|
|
1108
|
+
);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
// src/lib/binary-path.ts
|
|
1112
|
+
function getProfileBinaryInstallDir(dataDir) {
|
|
1113
|
+
return join3(dataDir, "bin");
|
|
1114
|
+
}
|
|
1115
|
+
function getProfileManagedBinaryPath(dataDir) {
|
|
1116
|
+
return new BinaryManager(getProfileBinaryInstallDir(dataDir)).getBinaryPath();
|
|
1117
|
+
}
|
|
1118
|
+
function validateConfiguredBinaryPath(binaryPath) {
|
|
1119
|
+
const value = binaryPath.trim();
|
|
1120
|
+
if (!value) {
|
|
1121
|
+
throw new Error("Configured binaryPath cannot be empty");
|
|
1122
|
+
}
|
|
1123
|
+
if (value.includes("\0")) {
|
|
1124
|
+
throw new Error("Configured binaryPath contains an invalid null byte");
|
|
1125
|
+
}
|
|
1126
|
+
return value;
|
|
1127
|
+
}
|
|
1128
|
+
function resolveBinaryPath(config) {
|
|
1129
|
+
const managedPath = getProfileManagedBinaryPath(config.dataDir);
|
|
1130
|
+
if (config.binaryPath) {
|
|
1131
|
+
const binaryPath2 = validateConfiguredBinaryPath(config.binaryPath);
|
|
1132
|
+
const installDir2 = dirname(binaryPath2);
|
|
1133
|
+
const expectedPath = new BinaryManager(installDir2).getBinaryPath();
|
|
1134
|
+
const managedByBinaryManager = expectedPath === binaryPath2;
|
|
1135
|
+
const source = binaryPath2 === managedPath ? "profile-managed" : "configured-path";
|
|
1136
|
+
return {
|
|
1137
|
+
binaryPath: binaryPath2,
|
|
1138
|
+
installDir: managedByBinaryManager ? installDir2 : null,
|
|
1139
|
+
managedPath,
|
|
1140
|
+
managedByBinaryManager,
|
|
1141
|
+
source
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
const installDir = getProfileBinaryInstallDir(config.dataDir);
|
|
1145
|
+
const binaryPath = managedPath;
|
|
1146
|
+
return {
|
|
1147
|
+
binaryPath,
|
|
1148
|
+
installDir,
|
|
1149
|
+
managedPath,
|
|
1150
|
+
managedByBinaryManager: true,
|
|
1151
|
+
source: "profile-managed"
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
function getBinaryManagerInstallDirOrThrow(resolvedBinary) {
|
|
1155
|
+
if (resolvedBinary.installDir) {
|
|
1156
|
+
return resolvedBinary.installDir;
|
|
1157
|
+
}
|
|
1158
|
+
throw new Error(
|
|
1159
|
+
`Configured binaryPath "${resolvedBinary.binaryPath}" is incompatible with BinaryManager-managed path naming. BinaryManager expects "${new BinaryManager(dirname(resolvedBinary.binaryPath)).getBinaryPath()}". Set binaryPath to a standard managed name (fnn/fnn.exe) in the target directory, or unset binaryPath to use the profile-managed binary.`
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
async function getBinaryDetails(config) {
|
|
1163
|
+
const resolvedBinary = resolveBinaryPath(config);
|
|
1164
|
+
const info = resolvedBinary.installDir ? await getFiberBinaryInfo(resolvedBinary.installDir) : getCustomBinaryState(resolvedBinary.binaryPath);
|
|
1165
|
+
return { resolvedBinary, info };
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// src/commands/binary.ts
|
|
1169
|
+
function showProgress(progress) {
|
|
1170
|
+
const percent = progress.percent !== void 0 ? ` (${progress.percent}%)` : "";
|
|
1171
|
+
process.stdout.write(`\r[${progress.phase}]${percent} ${progress.message}`.padEnd(80));
|
|
1172
|
+
if (progress.phase === "installing") {
|
|
1173
|
+
console.log();
|
|
1174
|
+
}
|
|
668
1175
|
}
|
|
669
|
-
function
|
|
670
|
-
const
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
1176
|
+
function createBinaryCommand(config) {
|
|
1177
|
+
const binary = new Command2("binary").description("Fiber binary management");
|
|
1178
|
+
binary.command("download").option("--version <version>", "Fiber binary version", DEFAULT_FIBER_VERSION).option("--force", "Force re-download").option("--json").action(async (options) => {
|
|
1179
|
+
const resolvedBinary = resolveBinaryPath(config);
|
|
1180
|
+
let installDir;
|
|
1181
|
+
try {
|
|
1182
|
+
installDir = getBinaryManagerInstallDirOrThrow(resolvedBinary);
|
|
1183
|
+
} catch (error) {
|
|
1184
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1185
|
+
if (options.json) {
|
|
1186
|
+
printJsonError({
|
|
1187
|
+
code: "BINARY_PATH_INCOMPATIBLE",
|
|
1188
|
+
message,
|
|
1189
|
+
recoverable: true,
|
|
1190
|
+
suggestion: "Use `fiber-pay config profile unset binaryPath` or set binaryPath to a standard fnn filename in the target directory."
|
|
1191
|
+
});
|
|
1192
|
+
} else {
|
|
1193
|
+
console.error(`\u274C ${message}`);
|
|
1194
|
+
}
|
|
1195
|
+
process.exit(1);
|
|
1196
|
+
}
|
|
1197
|
+
const info = await downloadFiberBinary({
|
|
1198
|
+
installDir,
|
|
1199
|
+
version: options.version,
|
|
1200
|
+
force: Boolean(options.force),
|
|
1201
|
+
onProgress: options.json ? void 0 : showProgress
|
|
1202
|
+
});
|
|
1203
|
+
if (options.json) {
|
|
1204
|
+
printJsonSuccess({
|
|
1205
|
+
...info,
|
|
1206
|
+
source: resolvedBinary.source,
|
|
1207
|
+
resolvedPath: resolvedBinary.binaryPath
|
|
1208
|
+
});
|
|
1209
|
+
} else {
|
|
1210
|
+
console.log("\n\u2705 Binary installed successfully!");
|
|
1211
|
+
console.log(` Path: ${info.path}`);
|
|
1212
|
+
console.log(` Version: ${info.version}`);
|
|
1213
|
+
console.log(` Ready: ${info.ready ? "yes" : "no"}`);
|
|
1214
|
+
}
|
|
674
1215
|
});
|
|
1216
|
+
binary.command("info").option("--json").action(async (options) => {
|
|
1217
|
+
const { resolvedBinary, info } = await getBinaryDetails(config);
|
|
1218
|
+
if (options.json) {
|
|
1219
|
+
printJsonSuccess({
|
|
1220
|
+
...info,
|
|
1221
|
+
source: resolvedBinary.source,
|
|
1222
|
+
resolvedPath: resolvedBinary.binaryPath
|
|
1223
|
+
});
|
|
1224
|
+
} else {
|
|
1225
|
+
console.log(info.ready ? "\u2705 Binary is ready" : "\u274C Binary not found or not executable");
|
|
1226
|
+
console.log(` Path: ${info.path}`);
|
|
1227
|
+
console.log(` Version: ${info.version}`);
|
|
1228
|
+
}
|
|
1229
|
+
});
|
|
1230
|
+
return binary;
|
|
675
1231
|
}
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
return
|
|
685
|
-
url: config.rpcUrl,
|
|
686
|
-
target: "node-rpc"
|
|
687
|
-
};
|
|
688
|
-
}
|
|
689
|
-
async function createReadyRpcClient(config, options = {}) {
|
|
690
|
-
const rpc = createRpcClient(config);
|
|
691
|
-
await rpc.waitForReady({ timeout: options.timeout ?? 3e3, interval: options.interval ?? 500 });
|
|
692
|
-
return rpc;
|
|
1232
|
+
|
|
1233
|
+
// src/commands/channel.ts
|
|
1234
|
+
import { randomUUID } from "crypto";
|
|
1235
|
+
import { ckbToShannons as ckbToShannons2 } from "@fiber-pay/sdk";
|
|
1236
|
+
import { Command as Command3 } from "commander";
|
|
1237
|
+
|
|
1238
|
+
// src/lib/async.ts
|
|
1239
|
+
function sleep(ms) {
|
|
1240
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
693
1241
|
}
|
|
694
1242
|
|
|
695
1243
|
// src/lib/runtime-jobs.ts
|
|
@@ -979,7 +1527,7 @@ function registerChannelRebalanceCommand(parent, config) {
|
|
|
979
1527
|
|
|
980
1528
|
// src/commands/channel.ts
|
|
981
1529
|
function createChannelCommand(config) {
|
|
982
|
-
const channel = new
|
|
1530
|
+
const channel = new Command3("channel").description("Channel lifecycle and status commands");
|
|
983
1531
|
channel.command("list").option("--state <state>").option("--peer <peerId>").option("--include-closed").option("--json").action(async (options) => {
|
|
984
1532
|
const rpc = await createReadyRpcClient(config);
|
|
985
1533
|
const stateFilter = parseChannelState(options.state);
|
|
@@ -1400,12 +1948,14 @@ function createChannelCommand(config) {
|
|
|
1400
1948
|
}
|
|
1401
1949
|
|
|
1402
1950
|
// src/commands/config.ts
|
|
1403
|
-
import { existsSync as existsSync5, readFileSync as
|
|
1404
|
-
import {
|
|
1951
|
+
import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
1952
|
+
import { homedir } from "os";
|
|
1953
|
+
import { join as join5 } from "path";
|
|
1954
|
+
import { Command as Command4 } from "commander";
|
|
1405
1955
|
import { parseDocument, stringify as yamlStringify } from "yaml";
|
|
1406
1956
|
|
|
1407
1957
|
// src/lib/config.ts
|
|
1408
|
-
import { existsSync as existsSync4, mkdirSync, readFileSync as
|
|
1958
|
+
import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
1409
1959
|
import { join as join4 } from "path";
|
|
1410
1960
|
|
|
1411
1961
|
// src/lib/config-templates.ts
|
|
@@ -1603,7 +2153,7 @@ function loadProfileConfig(dataDir) {
|
|
|
1603
2153
|
const profilePath = getProfilePath(dataDir);
|
|
1604
2154
|
if (!existsSync4(profilePath)) return void 0;
|
|
1605
2155
|
try {
|
|
1606
|
-
const raw =
|
|
2156
|
+
const raw = readFileSync4(profilePath, "utf-8");
|
|
1607
2157
|
return JSON.parse(raw);
|
|
1608
2158
|
} catch {
|
|
1609
2159
|
return void 0;
|
|
@@ -1659,7 +2209,7 @@ function getEffectiveConfig(explicitFlags2) {
|
|
|
1659
2209
|
const dataDirSource = explicitFlags2?.has("dataDir") ? "cli" : process.env.FIBER_DATA_DIR ? "env" : "default";
|
|
1660
2210
|
const configPath = getConfigPath(dataDir);
|
|
1661
2211
|
const configExists = existsSync4(configPath);
|
|
1662
|
-
const configContent = configExists ?
|
|
2212
|
+
const configContent = configExists ? readFileSync4(configPath, "utf-8") : void 0;
|
|
1663
2213
|
const profile = loadProfileConfig(dataDir);
|
|
1664
2214
|
const cliNetwork = explicitFlags2?.has("network") ? process.env.FIBER_NETWORK : void 0;
|
|
1665
2215
|
const envNetwork = !explicitFlags2?.has("network") ? process.env.FIBER_NETWORK : void 0;
|
|
@@ -1828,7 +2378,7 @@ function normalizeHexScalarsForMutation(content) {
|
|
|
1828
2378
|
);
|
|
1829
2379
|
}
|
|
1830
2380
|
function parseConfigDocumentForMutation(configPath) {
|
|
1831
|
-
const raw =
|
|
2381
|
+
const raw = readFileSync5(configPath, "utf-8");
|
|
1832
2382
|
const normalized = normalizeHexScalarsForMutation(raw);
|
|
1833
2383
|
return parseDocument(normalized, {
|
|
1834
2384
|
keepSourceTokens: true
|
|
@@ -1864,7 +2414,7 @@ function collectConfigPaths(value, prefix = "") {
|
|
|
1864
2414
|
return prefix ? [prefix] : [];
|
|
1865
2415
|
}
|
|
1866
2416
|
function createConfigCommand(_config) {
|
|
1867
|
-
const config = new
|
|
2417
|
+
const config = new Command4("config").description("Single source configuration management");
|
|
1868
2418
|
config.command("init").option(
|
|
1869
2419
|
"--data-dir <path>",
|
|
1870
2420
|
"Target data directory (overrides FIBER_DATA_DIR for this command)"
|
|
@@ -1958,7 +2508,7 @@ function createConfigCommand(_config) {
|
|
|
1958
2508
|
}
|
|
1959
2509
|
process.exit(1);
|
|
1960
2510
|
}
|
|
1961
|
-
const content =
|
|
2511
|
+
const content = readFileSync5(effective.config.configPath, "utf-8");
|
|
1962
2512
|
const fileNetwork = parseNetworkFromConfig(content) || "unknown";
|
|
1963
2513
|
if (options.json) {
|
|
1964
2514
|
printJsonSuccess({
|
|
@@ -2099,7 +2649,7 @@ function createConfigCommand(_config) {
|
|
|
2099
2649
|
}
|
|
2100
2650
|
}
|
|
2101
2651
|
});
|
|
2102
|
-
const profile = new
|
|
2652
|
+
const profile = new Command4("profile").description(
|
|
2103
2653
|
"Manage profile.json settings (CLI-only overrides)"
|
|
2104
2654
|
);
|
|
2105
2655
|
const PROFILE_KEYS = ["binaryPath", "keyPassword", "runtimeProxyListen"];
|
|
@@ -2123,6 +2673,56 @@ function createConfigCommand(_config) {
|
|
|
2123
2673
|
}
|
|
2124
2674
|
}
|
|
2125
2675
|
});
|
|
2676
|
+
profile.command("list").description("List all available Fiber profiles").option("--json").action(async (options) => {
|
|
2677
|
+
const homeDir = homedir();
|
|
2678
|
+
const profilesDir = join5(homeDir, ".fiber-pay", "profiles");
|
|
2679
|
+
const defaultDir = join5(homeDir, ".fiber-pay");
|
|
2680
|
+
const defaultConfigPath = join5(defaultDir, "config.yml");
|
|
2681
|
+
const profiles = [];
|
|
2682
|
+
const hasDefaultConfig = existsSync5(defaultConfigPath);
|
|
2683
|
+
if (hasDefaultConfig) {
|
|
2684
|
+
profiles.push("default");
|
|
2685
|
+
}
|
|
2686
|
+
if (existsSync5(profilesDir)) {
|
|
2687
|
+
try {
|
|
2688
|
+
const entries = readdirSync(profilesDir, { withFileTypes: true });
|
|
2689
|
+
for (const entry of entries) {
|
|
2690
|
+
if (entry.isDirectory() && entry.name !== "default") {
|
|
2691
|
+
profiles.push(entry.name);
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
} catch (error) {
|
|
2695
|
+
if (options.json) {
|
|
2696
|
+
printJsonError({
|
|
2697
|
+
code: "PROFILE_LIST_ERROR",
|
|
2698
|
+
message: `Failed to read profiles directory: ${error instanceof Error ? error.message : String(error)}`,
|
|
2699
|
+
recoverable: true
|
|
2700
|
+
});
|
|
2701
|
+
process.exit(1);
|
|
2702
|
+
} else {
|
|
2703
|
+
console.error(
|
|
2704
|
+
`Warning: Could not read profiles directory: ${error instanceof Error ? error.message : String(error)}`
|
|
2705
|
+
);
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
profiles.sort((a, b) => {
|
|
2710
|
+
if (a === "default") return -1;
|
|
2711
|
+
if (b === "default") return 1;
|
|
2712
|
+
return a.localeCompare(b);
|
|
2713
|
+
});
|
|
2714
|
+
if (options.json) {
|
|
2715
|
+
printJsonSuccess({ profiles, count: profiles.length });
|
|
2716
|
+
} else {
|
|
2717
|
+
if (profiles.length === 0) {
|
|
2718
|
+
console.log("No profiles found.");
|
|
2719
|
+
} else {
|
|
2720
|
+
for (const profileName of profiles) {
|
|
2721
|
+
console.log(profileName);
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
});
|
|
2126
2726
|
profile.command("set").description("Set a profile key").argument("<key>", `One of: ${PROFILE_KEYS.join(", ")}`).argument("<value>").option("--json").action(async (key, value, options) => {
|
|
2127
2727
|
if (!PROFILE_KEYS.includes(key)) {
|
|
2128
2728
|
const msg = `Unknown profile key: ${key}. Valid keys: ${PROFILE_KEYS.join(", ")}`;
|
|
@@ -2179,7 +2779,7 @@ function createConfigCommand(_config) {
|
|
|
2179
2779
|
|
|
2180
2780
|
// src/commands/graph.ts
|
|
2181
2781
|
import { shannonsToCkb as shannonsToCkb3, toHex as toHex2 } from "@fiber-pay/sdk";
|
|
2182
|
-
import { Command as
|
|
2782
|
+
import { Command as Command5 } from "commander";
|
|
2183
2783
|
function printGraphNodeListHuman(nodes) {
|
|
2184
2784
|
if (nodes.length === 0) {
|
|
2185
2785
|
console.log("No nodes found in the graph.");
|
|
@@ -2221,7 +2821,7 @@ function printGraphChannelListHuman(channels) {
|
|
|
2221
2821
|
}
|
|
2222
2822
|
}
|
|
2223
2823
|
function createGraphCommand(config) {
|
|
2224
|
-
const graph = new
|
|
2824
|
+
const graph = new Command5("graph").description("Network graph queries (nodes & channels)");
|
|
2225
2825
|
graph.command("nodes").option("--limit <n>", "Maximum number of nodes to return", "50").option("--after <cursor>", "Pagination cursor from a previous query").option("--json").action(async (options) => {
|
|
2226
2826
|
const rpc = await createReadyRpcClient(config);
|
|
2227
2827
|
const limit = toHex2(BigInt(parseInt(options.limit, 10)));
|
|
@@ -2261,9 +2861,9 @@ Next cursor: ${result.last_cursor}`);
|
|
|
2261
2861
|
|
|
2262
2862
|
// src/commands/invoice.ts
|
|
2263
2863
|
import { ckbToShannons as ckbToShannons3, randomBytes32, shannonsToCkb as shannonsToCkb4, toHex as toHex3 } from "@fiber-pay/sdk";
|
|
2264
|
-
import { Command as
|
|
2864
|
+
import { Command as Command6 } from "commander";
|
|
2265
2865
|
function createInvoiceCommand(config) {
|
|
2266
|
-
const invoice = new
|
|
2866
|
+
const invoice = new Command6("invoice").description("Invoice lifecycle and status commands");
|
|
2267
2867
|
invoice.command("create").argument("[amount]").option("--amount <ckb>").option("--description <text>").option("--expiry <minutes>").option("--json").action(async (amountArg, options) => {
|
|
2268
2868
|
const rpc = await createReadyRpcClient(config);
|
|
2269
2869
|
const json = Boolean(options.json);
|
|
@@ -2479,21 +3079,162 @@ function createInvoiceCommand(config) {
|
|
|
2479
3079
|
|
|
2480
3080
|
// src/commands/job.ts
|
|
2481
3081
|
import { existsSync as existsSync7 } from "fs";
|
|
2482
|
-
import { Command as
|
|
3082
|
+
import { Command as Command7 } from "commander";
|
|
2483
3083
|
|
|
2484
3084
|
// src/lib/log-files.ts
|
|
2485
3085
|
import {
|
|
2486
|
-
appendFileSync,
|
|
2487
3086
|
closeSync,
|
|
2488
3087
|
createReadStream,
|
|
2489
3088
|
existsSync as existsSync6,
|
|
2490
3089
|
mkdirSync as mkdirSync2,
|
|
2491
3090
|
openSync,
|
|
2492
|
-
readdirSync,
|
|
3091
|
+
readdirSync as readdirSync2,
|
|
2493
3092
|
readSync,
|
|
2494
3093
|
statSync
|
|
2495
3094
|
} from "fs";
|
|
2496
|
-
import { join as
|
|
3095
|
+
import { join as join7 } from "path";
|
|
3096
|
+
|
|
3097
|
+
// src/lib/log-writer.ts
|
|
3098
|
+
import { createWriteStream } from "fs";
|
|
3099
|
+
import { mkdir } from "fs/promises";
|
|
3100
|
+
import { dirname as dirname2, join as join6 } from "path";
|
|
3101
|
+
var LogWriter = class {
|
|
3102
|
+
constructor(baseDir, filename) {
|
|
3103
|
+
this.baseDir = baseDir;
|
|
3104
|
+
this.filename = filename;
|
|
3105
|
+
}
|
|
3106
|
+
stream = null;
|
|
3107
|
+
pendingWrites = 0;
|
|
3108
|
+
lastErrorTime = 0;
|
|
3109
|
+
errorRateLimitMs = 1e3;
|
|
3110
|
+
isClosing = false;
|
|
3111
|
+
waitingForDrain = false;
|
|
3112
|
+
currentDate = null;
|
|
3113
|
+
async append(text) {
|
|
3114
|
+
if (this.isClosing) {
|
|
3115
|
+
throw new Error("Cannot append to closing LogWriter");
|
|
3116
|
+
}
|
|
3117
|
+
await this.ensureStream();
|
|
3118
|
+
if (!this.stream) {
|
|
3119
|
+
throw new Error("Failed to create write stream");
|
|
3120
|
+
}
|
|
3121
|
+
const stream = this.stream;
|
|
3122
|
+
return new Promise((resolve2, reject) => {
|
|
3123
|
+
if (this.waitingForDrain) {
|
|
3124
|
+
const onDrain = () => {
|
|
3125
|
+
this.waitingForDrain = false;
|
|
3126
|
+
stream.off("drain", onDrain);
|
|
3127
|
+
stream.off("error", onError);
|
|
3128
|
+
this.performWrite(text, resolve2, reject);
|
|
3129
|
+
};
|
|
3130
|
+
const onError = (err) => {
|
|
3131
|
+
this.waitingForDrain = false;
|
|
3132
|
+
stream.off("drain", onDrain);
|
|
3133
|
+
stream.off("error", onError);
|
|
3134
|
+
this.handleError(err);
|
|
3135
|
+
reject(err);
|
|
3136
|
+
};
|
|
3137
|
+
stream.once("drain", onDrain);
|
|
3138
|
+
stream.once("error", onError);
|
|
3139
|
+
return;
|
|
3140
|
+
}
|
|
3141
|
+
this.performWrite(text, resolve2, reject);
|
|
3142
|
+
});
|
|
3143
|
+
}
|
|
3144
|
+
async flush() {
|
|
3145
|
+
if (this.isClosing || !this.stream) {
|
|
3146
|
+
return;
|
|
3147
|
+
}
|
|
3148
|
+
this.isClosing = true;
|
|
3149
|
+
while (this.pendingWrites > 0) {
|
|
3150
|
+
await new Promise((resolve2) => setTimeout(resolve2, 10));
|
|
3151
|
+
}
|
|
3152
|
+
return new Promise((resolve2, reject) => {
|
|
3153
|
+
if (!this.stream) {
|
|
3154
|
+
resolve2();
|
|
3155
|
+
return;
|
|
3156
|
+
}
|
|
3157
|
+
const stream = this.stream;
|
|
3158
|
+
stream.once("finish", () => {
|
|
3159
|
+
this.stream = null;
|
|
3160
|
+
resolve2();
|
|
3161
|
+
});
|
|
3162
|
+
stream.once("error", (err) => {
|
|
3163
|
+
this.handleError(err);
|
|
3164
|
+
this.stream = null;
|
|
3165
|
+
reject(err);
|
|
3166
|
+
});
|
|
3167
|
+
stream.end();
|
|
3168
|
+
});
|
|
3169
|
+
}
|
|
3170
|
+
async ensureStream() {
|
|
3171
|
+
this.isClosing = false;
|
|
3172
|
+
const today = this.todayDateString();
|
|
3173
|
+
if (this.stream && this.currentDate !== today && !this.isClosing) {
|
|
3174
|
+
await this.flush();
|
|
3175
|
+
this.stream = null;
|
|
3176
|
+
}
|
|
3177
|
+
if (this.stream && !this.isClosing) {
|
|
3178
|
+
return;
|
|
3179
|
+
}
|
|
3180
|
+
this.currentDate = today;
|
|
3181
|
+
const logPath = this.resolveLogPath();
|
|
3182
|
+
await this.ensureDirectory(logPath);
|
|
3183
|
+
this.stream = createWriteStream(logPath, {
|
|
3184
|
+
flags: "a",
|
|
3185
|
+
highWaterMark: 16 * 1024
|
|
3186
|
+
});
|
|
3187
|
+
this.stream.on("error", (err) => {
|
|
3188
|
+
this.handleError(err);
|
|
3189
|
+
this.stream = null;
|
|
3190
|
+
this.isClosing = true;
|
|
3191
|
+
});
|
|
3192
|
+
this.isClosing = false;
|
|
3193
|
+
}
|
|
3194
|
+
performWrite(text, resolve2, reject) {
|
|
3195
|
+
if (!this.stream) {
|
|
3196
|
+
reject(new Error("Stream is not available"));
|
|
3197
|
+
return;
|
|
3198
|
+
}
|
|
3199
|
+
this.pendingWrites += 1;
|
|
3200
|
+
const canContinue = this.stream.write(text, (err) => {
|
|
3201
|
+
this.pendingWrites -= 1;
|
|
3202
|
+
if (err) {
|
|
3203
|
+
this.handleError(err);
|
|
3204
|
+
reject(err);
|
|
3205
|
+
} else {
|
|
3206
|
+
resolve2();
|
|
3207
|
+
}
|
|
3208
|
+
});
|
|
3209
|
+
if (!canContinue) {
|
|
3210
|
+
this.waitingForDrain = true;
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
async ensureDirectory(filePath) {
|
|
3214
|
+
const dir = dirname2(filePath);
|
|
3215
|
+
await mkdir(dir, { recursive: true });
|
|
3216
|
+
}
|
|
3217
|
+
resolveLogPath() {
|
|
3218
|
+
const dateStr = this.todayDateString();
|
|
3219
|
+
return join6(this.baseDir, "logs", dateStr, this.filename);
|
|
3220
|
+
}
|
|
3221
|
+
todayDateString() {
|
|
3222
|
+
const now = /* @__PURE__ */ new Date();
|
|
3223
|
+
const y = now.getUTCFullYear();
|
|
3224
|
+
const m = String(now.getUTCMonth() + 1).padStart(2, "0");
|
|
3225
|
+
const d = String(now.getUTCDate()).padStart(2, "0");
|
|
3226
|
+
return `${y}-${m}-${d}`;
|
|
3227
|
+
}
|
|
3228
|
+
handleError(error) {
|
|
3229
|
+
const now = Date.now();
|
|
3230
|
+
if (now - this.lastErrorTime >= this.errorRateLimitMs) {
|
|
3231
|
+
this.lastErrorTime = now;
|
|
3232
|
+
console.error(`[LogWriter] ${error.message}`);
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
};
|
|
3236
|
+
|
|
3237
|
+
// src/lib/log-files.ts
|
|
2497
3238
|
var DATE_DIR_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
|
2498
3239
|
function todayDateString() {
|
|
2499
3240
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -2517,11 +3258,11 @@ function resolveLogDirForDate(dataDir, date) {
|
|
|
2517
3258
|
}
|
|
2518
3259
|
function resolveLogDirForDateWithOptions(dataDir, date, options) {
|
|
2519
3260
|
const dateStr = date ?? todayDateString();
|
|
2520
|
-
const logsBaseDir = options.logsBaseDir ??
|
|
3261
|
+
const logsBaseDir = options.logsBaseDir ?? join7(dataDir, "logs");
|
|
2521
3262
|
if (date !== void 0) {
|
|
2522
3263
|
validateLogDate(dateStr);
|
|
2523
3264
|
}
|
|
2524
|
-
const dir =
|
|
3265
|
+
const dir = join7(logsBaseDir, dateStr);
|
|
2525
3266
|
const ensureExists = options.ensureExists ?? true;
|
|
2526
3267
|
if (ensureExists) {
|
|
2527
3268
|
mkdirSync2(dir, { recursive: true });
|
|
@@ -2529,16 +3270,16 @@ function resolveLogDirForDateWithOptions(dataDir, date, options) {
|
|
|
2529
3270
|
return dir;
|
|
2530
3271
|
}
|
|
2531
3272
|
function resolvePersistedLogPaths(dataDir, meta, date) {
|
|
2532
|
-
const logsBaseDir = meta?.logsBaseDir ??
|
|
3273
|
+
const logsBaseDir = meta?.logsBaseDir ?? join7(dataDir, "logs");
|
|
2533
3274
|
if (date) {
|
|
2534
3275
|
const dir2 = resolveLogDirForDateWithOptions(dataDir, date, {
|
|
2535
3276
|
logsBaseDir,
|
|
2536
3277
|
ensureExists: false
|
|
2537
3278
|
});
|
|
2538
3279
|
return {
|
|
2539
|
-
runtimeAlerts:
|
|
2540
|
-
fnnStdout:
|
|
2541
|
-
fnnStderr:
|
|
3280
|
+
runtimeAlerts: join7(dir2, "runtime.alerts.jsonl"),
|
|
3281
|
+
fnnStdout: join7(dir2, "fnn.stdout.log"),
|
|
3282
|
+
fnnStderr: join7(dir2, "fnn.stderr.log")
|
|
2542
3283
|
};
|
|
2543
3284
|
}
|
|
2544
3285
|
if (meta?.alertLogFilePath || meta?.fnnStdoutLogPath || meta?.fnnStderrLogPath) {
|
|
@@ -2547,9 +3288,9 @@ function resolvePersistedLogPaths(dataDir, meta, date) {
|
|
|
2547
3288
|
ensureExists: false
|
|
2548
3289
|
});
|
|
2549
3290
|
return {
|
|
2550
|
-
runtimeAlerts: meta.alertLogFilePath ??
|
|
2551
|
-
fnnStdout: meta.fnnStdoutLogPath ??
|
|
2552
|
-
fnnStderr: meta.fnnStderrLogPath ??
|
|
3291
|
+
runtimeAlerts: meta.alertLogFilePath ?? join7(defaultDir, "runtime.alerts.jsonl"),
|
|
3292
|
+
fnnStdout: meta.fnnStdoutLogPath ?? join7(defaultDir, "fnn.stdout.log"),
|
|
3293
|
+
fnnStderr: meta.fnnStderrLogPath ?? join7(defaultDir, "fnn.stderr.log")
|
|
2553
3294
|
};
|
|
2554
3295
|
}
|
|
2555
3296
|
const dir = resolveLogDirForDateWithOptions(dataDir, void 0, {
|
|
@@ -2557,24 +3298,35 @@ function resolvePersistedLogPaths(dataDir, meta, date) {
|
|
|
2557
3298
|
ensureExists: false
|
|
2558
3299
|
});
|
|
2559
3300
|
return {
|
|
2560
|
-
runtimeAlerts:
|
|
2561
|
-
fnnStdout:
|
|
2562
|
-
fnnStderr:
|
|
3301
|
+
runtimeAlerts: join7(dir, "runtime.alerts.jsonl"),
|
|
3302
|
+
fnnStdout: join7(dir, "fnn.stdout.log"),
|
|
3303
|
+
fnnStderr: join7(dir, "fnn.stderr.log")
|
|
2563
3304
|
};
|
|
2564
3305
|
}
|
|
2565
3306
|
function listLogDates(dataDir, logsBaseDir) {
|
|
2566
|
-
const logsDir = logsBaseDir ??
|
|
3307
|
+
const logsDir = logsBaseDir ?? join7(dataDir, "logs");
|
|
2567
3308
|
if (!existsSync6(logsDir)) {
|
|
2568
3309
|
return [];
|
|
2569
3310
|
}
|
|
2570
|
-
const entries =
|
|
3311
|
+
const entries = readdirSync2(logsDir, { withFileTypes: true });
|
|
2571
3312
|
const dates = entries.filter((entry) => entry.isDirectory() && DATE_DIR_PATTERN.test(entry.name)).map((entry) => entry.name);
|
|
2572
3313
|
dates.sort((a, b) => a > b ? -1 : a < b ? 1 : 0);
|
|
2573
3314
|
return dates;
|
|
2574
3315
|
}
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
3316
|
+
var logWriters = /* @__PURE__ */ new Map();
|
|
3317
|
+
function getLogWriter(dataDir, filename) {
|
|
3318
|
+
const key = `${dataDir}:${filename}`;
|
|
3319
|
+
if (!logWriters.has(key)) {
|
|
3320
|
+
logWriters.set(key, new LogWriter(dataDir, filename));
|
|
3321
|
+
}
|
|
3322
|
+
return logWriters.get(key);
|
|
3323
|
+
}
|
|
3324
|
+
async function appendToTodayLog(dataDir, filename, text) {
|
|
3325
|
+
const writer = getLogWriter(dataDir, filename);
|
|
3326
|
+
await writer.append(text);
|
|
3327
|
+
}
|
|
3328
|
+
async function flushPendingLogs() {
|
|
3329
|
+
await Promise.all(Array.from(logWriters.values()).map((w) => w.flush()));
|
|
2578
3330
|
}
|
|
2579
3331
|
function resolvePersistedLogTargets(paths, source) {
|
|
2580
3332
|
const all = [
|
|
@@ -2681,7 +3433,7 @@ async function readAppendedLines(filePath, offset, remainder = "") {
|
|
|
2681
3433
|
|
|
2682
3434
|
// src/commands/job.ts
|
|
2683
3435
|
function createJobCommand(config) {
|
|
2684
|
-
const job = new
|
|
3436
|
+
const job = new Command7("job").description("Runtime job management commands");
|
|
2685
3437
|
job.command("list").option("--state <state>", "Filter by job state").option("--type <type>", "Filter by job type (payment|invoice|channel)").option("--limit <n>", "Limit number of jobs").option("--offset <n>", "Offset for pagination").option("--json").action(async (options) => {
|
|
2686
3438
|
const json = Boolean(options.json);
|
|
2687
3439
|
const runtimeUrl = getRuntimeUrlOrExit(config, json);
|
|
@@ -3011,11 +3763,163 @@ ${title}: ${filePath}`);
|
|
|
3011
3763
|
}
|
|
3012
3764
|
}
|
|
3013
3765
|
|
|
3766
|
+
// src/commands/l402.ts
|
|
3767
|
+
import { Command as Command8 } from "commander";
|
|
3768
|
+
|
|
3769
|
+
// src/lib/l402-proxy.ts
|
|
3770
|
+
import { createServer as createServer2 } from "http";
|
|
3771
|
+
import { createL402Middleware as createL402Middleware2, FiberRpcClient as FiberRpcClient3 } from "@fiber-pay/sdk";
|
|
3772
|
+
import express2 from "express";
|
|
3773
|
+
import { createProxyMiddleware } from "http-proxy-middleware";
|
|
3774
|
+
async function runL402ProxyCommand(config, options) {
|
|
3775
|
+
const asJson = Boolean(options.json);
|
|
3776
|
+
const port = parseInt(options.port, 10);
|
|
3777
|
+
const host = options.host;
|
|
3778
|
+
const priceCkb = parseFloat(options.price);
|
|
3779
|
+
const expirySeconds = parseInt(options.expiry, 10);
|
|
3780
|
+
const rootKey = options.rootKey || process.env.L402_ROOT_KEY;
|
|
3781
|
+
if (Number.isNaN(port) || port < 1 || port > 65535) {
|
|
3782
|
+
if (asJson) {
|
|
3783
|
+
printJsonError({
|
|
3784
|
+
code: "L402_PROXY_INVALID_PORT",
|
|
3785
|
+
message: `Invalid port: ${options.port}`,
|
|
3786
|
+
recoverable: true,
|
|
3787
|
+
suggestion: "Provide a valid port number between 1 and 65535."
|
|
3788
|
+
});
|
|
3789
|
+
} else {
|
|
3790
|
+
console.error(`Error: Invalid port: ${options.port}`);
|
|
3791
|
+
}
|
|
3792
|
+
process.exit(1);
|
|
3793
|
+
}
|
|
3794
|
+
if (Number.isNaN(priceCkb) || priceCkb <= 0) {
|
|
3795
|
+
if (asJson) {
|
|
3796
|
+
printJsonError({
|
|
3797
|
+
code: "L402_PROXY_INVALID_PRICE",
|
|
3798
|
+
message: `Invalid price: ${options.price}`,
|
|
3799
|
+
recoverable: true,
|
|
3800
|
+
suggestion: "Provide a positive CKB amount, e.g. --price 0.1"
|
|
3801
|
+
});
|
|
3802
|
+
} else {
|
|
3803
|
+
console.error(`Error: Invalid price: ${options.price}`);
|
|
3804
|
+
}
|
|
3805
|
+
process.exit(1);
|
|
3806
|
+
}
|
|
3807
|
+
if (!rootKey) {
|
|
3808
|
+
if (asJson) {
|
|
3809
|
+
printJsonError({
|
|
3810
|
+
code: "L402_PROXY_MISSING_ROOT_KEY",
|
|
3811
|
+
message: "L402 root key is required.",
|
|
3812
|
+
recoverable: true,
|
|
3813
|
+
suggestion: "Provide --root-key <64-hex-chars> or set L402_ROOT_KEY environment variable. Generate one with: openssl rand -hex 32"
|
|
3814
|
+
});
|
|
3815
|
+
} else {
|
|
3816
|
+
console.error("Error: L402 root key is required.");
|
|
3817
|
+
console.error(" Provide --root-key <64-hex-chars> or set L402_ROOT_KEY env var.");
|
|
3818
|
+
console.error(" Generate one with: openssl rand -hex 32");
|
|
3819
|
+
}
|
|
3820
|
+
process.exit(1);
|
|
3821
|
+
}
|
|
3822
|
+
const rpcClient = new FiberRpcClient3({
|
|
3823
|
+
url: config.rpcUrl,
|
|
3824
|
+
biscuitToken: config.rpcBiscuitToken
|
|
3825
|
+
});
|
|
3826
|
+
const currency = config.network === "mainnet" ? "Fibb" : "Fibt";
|
|
3827
|
+
const app = express2();
|
|
3828
|
+
app.use(
|
|
3829
|
+
createL402Middleware2({
|
|
3830
|
+
rootKey,
|
|
3831
|
+
priceCkb,
|
|
3832
|
+
expirySeconds,
|
|
3833
|
+
rpcClient,
|
|
3834
|
+
currency
|
|
3835
|
+
})
|
|
3836
|
+
);
|
|
3837
|
+
app.use(
|
|
3838
|
+
createProxyMiddleware({
|
|
3839
|
+
target: options.target,
|
|
3840
|
+
changeOrigin: true
|
|
3841
|
+
})
|
|
3842
|
+
);
|
|
3843
|
+
const server = createServer2(app);
|
|
3844
|
+
await new Promise((resolve2, reject) => {
|
|
3845
|
+
server.on("error", (err) => {
|
|
3846
|
+
if (err.code === "EADDRINUSE") {
|
|
3847
|
+
if (asJson) {
|
|
3848
|
+
printJsonError({
|
|
3849
|
+
code: "L402_PROXY_PORT_IN_USE",
|
|
3850
|
+
message: `Port ${port} is already in use.`,
|
|
3851
|
+
recoverable: true,
|
|
3852
|
+
suggestion: `Use a different port with --port <port>.`
|
|
3853
|
+
});
|
|
3854
|
+
} else {
|
|
3855
|
+
console.error(`Error: Port ${port} is already in use.`);
|
|
3856
|
+
}
|
|
3857
|
+
process.exit(1);
|
|
3858
|
+
}
|
|
3859
|
+
reject(err);
|
|
3860
|
+
});
|
|
3861
|
+
server.listen(port, host, () => {
|
|
3862
|
+
resolve2();
|
|
3863
|
+
});
|
|
3864
|
+
});
|
|
3865
|
+
const listenUrl = `http://${host}:${port}`;
|
|
3866
|
+
if (asJson) {
|
|
3867
|
+
printJsonSuccess({
|
|
3868
|
+
status: "running",
|
|
3869
|
+
listen: listenUrl,
|
|
3870
|
+
target: options.target,
|
|
3871
|
+
priceCkb,
|
|
3872
|
+
expirySeconds,
|
|
3873
|
+
currency,
|
|
3874
|
+
fiberRpcUrl: config.rpcUrl
|
|
3875
|
+
});
|
|
3876
|
+
} else {
|
|
3877
|
+
console.log("L402 proxy started");
|
|
3878
|
+
console.log(` Listen: ${listenUrl}`);
|
|
3879
|
+
console.log(` Target: ${options.target}`);
|
|
3880
|
+
console.log(` Price: ${priceCkb} CKB per request`);
|
|
3881
|
+
console.log(` Expiry: ${expirySeconds}s`);
|
|
3882
|
+
console.log(` Currency: ${currency}`);
|
|
3883
|
+
console.log(` Fiber RPC: ${config.rpcUrl}`);
|
|
3884
|
+
console.log("");
|
|
3885
|
+
console.log("Press Ctrl+C to stop.");
|
|
3886
|
+
}
|
|
3887
|
+
const shutdown = () => {
|
|
3888
|
+
if (!asJson) {
|
|
3889
|
+
console.log("\nStopping L402 proxy...");
|
|
3890
|
+
}
|
|
3891
|
+
server.close(() => {
|
|
3892
|
+
if (asJson) {
|
|
3893
|
+
printJsonSuccess({ status: "stopped" });
|
|
3894
|
+
} else {
|
|
3895
|
+
console.log("L402 proxy stopped.");
|
|
3896
|
+
}
|
|
3897
|
+
process.exit(0);
|
|
3898
|
+
});
|
|
3899
|
+
setTimeout(() => {
|
|
3900
|
+
process.exit(0);
|
|
3901
|
+
}, 5e3);
|
|
3902
|
+
};
|
|
3903
|
+
process.on("SIGINT", shutdown);
|
|
3904
|
+
process.on("SIGTERM", shutdown);
|
|
3905
|
+
await new Promise(() => {
|
|
3906
|
+
});
|
|
3907
|
+
}
|
|
3908
|
+
|
|
3909
|
+
// src/commands/l402.ts
|
|
3910
|
+
function createL402Command(config) {
|
|
3911
|
+
const l402 = new Command8("l402").description("L402 payment protocol commands");
|
|
3912
|
+
l402.command("proxy").description("Start a reverse proxy with L402 payment gating").requiredOption("--target <url>", "Upstream server URL to forward paid requests to").option("--port <port>", "Listen port", "8402").option("--host <host>", "Listen host", "127.0.0.1").option("--price <ckb>", "Price per request in CKB", "0.1").option("--root-key <hex>", "Macaroon root key (32-byte hex, or set L402_ROOT_KEY env)").option("--expiry <seconds>", "Token expiry in seconds", "3600").option("--json").action(async (options) => {
|
|
3913
|
+
await runL402ProxyCommand(config, options);
|
|
3914
|
+
});
|
|
3915
|
+
return l402;
|
|
3916
|
+
}
|
|
3917
|
+
|
|
3014
3918
|
// src/commands/logs.ts
|
|
3015
3919
|
import { existsSync as existsSync8, statSync as statSync2 } from "fs";
|
|
3016
|
-
import { join as
|
|
3920
|
+
import { join as join8 } from "path";
|
|
3017
3921
|
import { formatRuntimeAlert } from "@fiber-pay/runtime";
|
|
3018
|
-
import { Command as
|
|
3922
|
+
import { Command as Command9 } from "commander";
|
|
3019
3923
|
var ALLOWED_SOURCES = /* @__PURE__ */ new Set([
|
|
3020
3924
|
"all",
|
|
3021
3925
|
"runtime",
|
|
@@ -3048,7 +3952,7 @@ function coerceJsonLineForOutput(source, line) {
|
|
|
3048
3952
|
return parseRuntimeAlertLine(line) ?? line;
|
|
3049
3953
|
}
|
|
3050
3954
|
function createLogsCommand(config) {
|
|
3051
|
-
return new
|
|
3955
|
+
return new Command9("logs").alias("log").description("View persisted runtime/fnn logs").option("--source <source>", "Log source: all|runtime|fnn-stdout|fnn-stderr", "all").option("--tail <n>", "Number of recent lines per source", "80").option("--date <YYYY-MM-DD>", "Date of log directory to read (default: today UTC)").option("--list-dates", "List available log dates and exit").option("--follow", "Keep streaming appended log lines (human output mode only)").option("--interval-ms <ms>", "Polling interval for --follow mode", "1000").option("--json").action(async (options) => {
|
|
3052
3956
|
const json = Boolean(options.json);
|
|
3053
3957
|
const follow = Boolean(options.follow);
|
|
3054
3958
|
const listDates = Boolean(options.listDates);
|
|
@@ -3069,7 +3973,7 @@ function createLogsCommand(config) {
|
|
|
3069
3973
|
process.exit(1);
|
|
3070
3974
|
}
|
|
3071
3975
|
if (listDates) {
|
|
3072
|
-
const logsDir = meta?.logsBaseDir ??
|
|
3976
|
+
const logsDir = meta?.logsBaseDir ?? join8(config.dataDir, "logs");
|
|
3073
3977
|
const dates = listLogDates(config.dataDir, logsDir);
|
|
3074
3978
|
if (json) {
|
|
3075
3979
|
printJsonSuccess({ dates, logsDir });
|
|
@@ -3303,7 +4207,7 @@ function inferDateFromPaths(paths) {
|
|
|
3303
4207
|
}
|
|
3304
4208
|
|
|
3305
4209
|
// src/commands/node.ts
|
|
3306
|
-
import { Command as
|
|
4210
|
+
import { Command as Command10 } from "commander";
|
|
3307
4211
|
|
|
3308
4212
|
// src/lib/node-info.ts
|
|
3309
4213
|
async function runNodeInfoCommand(config, options) {
|
|
@@ -3467,9 +4371,9 @@ function printNodeNetworkHuman(data) {
|
|
|
3467
4371
|
}
|
|
3468
4372
|
|
|
3469
4373
|
// src/lib/node-start.ts
|
|
3470
|
-
import { spawn } from "child_process";
|
|
4374
|
+
import { spawn as spawn2 } from "child_process";
|
|
3471
4375
|
import { mkdirSync as mkdirSync3 } from "fs";
|
|
3472
|
-
import { join as
|
|
4376
|
+
import { join as join9 } from "path";
|
|
3473
4377
|
import {
|
|
3474
4378
|
createKeyManager,
|
|
3475
4379
|
ensureFiberBinary,
|
|
@@ -3478,12 +4382,12 @@ import {
|
|
|
3478
4382
|
import { startRuntimeService } from "@fiber-pay/runtime";
|
|
3479
4383
|
|
|
3480
4384
|
// src/lib/bootnode.ts
|
|
3481
|
-
import { existsSync as existsSync9, readFileSync as
|
|
4385
|
+
import { existsSync as existsSync9, readFileSync as readFileSync6 } from "fs";
|
|
3482
4386
|
import { parse as parseYaml } from "yaml";
|
|
3483
4387
|
function extractBootnodeAddrs(configFilePath) {
|
|
3484
4388
|
if (!existsSync9(configFilePath)) return [];
|
|
3485
4389
|
try {
|
|
3486
|
-
const content =
|
|
4390
|
+
const content = readFileSync6(configFilePath, "utf-8");
|
|
3487
4391
|
const doc = parseYaml(content);
|
|
3488
4392
|
const addrs = doc?.fiber?.bootnode_addrs;
|
|
3489
4393
|
if (!Array.isArray(addrs)) return [];
|
|
@@ -3512,7 +4416,7 @@ async function autoConnectBootnodes(rpc, bootnodes) {
|
|
|
3512
4416
|
}
|
|
3513
4417
|
|
|
3514
4418
|
// src/lib/node-migration.ts
|
|
3515
|
-
import { dirname as
|
|
4419
|
+
import { dirname as dirname3 } from "path";
|
|
3516
4420
|
import { BinaryManager as BinaryManager2, MigrationManager } from "@fiber-pay/node";
|
|
3517
4421
|
|
|
3518
4422
|
// src/lib/migration-utils.ts
|
|
@@ -3536,7 +4440,7 @@ async function runMigrationGuard(opts) {
|
|
|
3536
4440
|
return { checked: false, skippedReason: "store does not exist" };
|
|
3537
4441
|
}
|
|
3538
4442
|
const storePath = MigrationManager.resolveStorePath(dataDir);
|
|
3539
|
-
const binaryDir =
|
|
4443
|
+
const binaryDir = dirname3(binaryPath);
|
|
3540
4444
|
const bm = new BinaryManager2(binaryDir);
|
|
3541
4445
|
const migrateBinPath = bm.getMigrateBinaryPath();
|
|
3542
4446
|
let migrationCheck;
|
|
@@ -3738,7 +4642,7 @@ async function runNodeStartCommand(config, options) {
|
|
|
3738
4642
|
process.exit(1);
|
|
3739
4643
|
}
|
|
3740
4644
|
const childArgs = process.argv.slice(2).filter((arg) => arg !== "--daemon");
|
|
3741
|
-
const child =
|
|
4645
|
+
const child = spawn2(process.execPath, [cliEntrypoint, ...childArgs], {
|
|
3742
4646
|
detached: true,
|
|
3743
4647
|
stdio: "ignore",
|
|
3744
4648
|
cwd: process.cwd(),
|
|
@@ -3796,8 +4700,8 @@ async function runNodeStartCommand(config, options) {
|
|
|
3796
4700
|
options.runtimeProxyListen ?? config.runtimeProxyListen ?? "127.0.0.1:8229"
|
|
3797
4701
|
);
|
|
3798
4702
|
const proxyListenSource = options.runtimeProxyListen ? "cli" : config.runtimeProxyListen ? "profile" : "default";
|
|
3799
|
-
const runtimeStateFilePath =
|
|
3800
|
-
const logsBaseDir =
|
|
4703
|
+
const runtimeStateFilePath = join9(config.dataDir, "runtime-state.json");
|
|
4704
|
+
const logsBaseDir = join9(config.dataDir, "logs");
|
|
3801
4705
|
mkdirSync3(logsBaseDir, { recursive: true });
|
|
3802
4706
|
resolveLogDirForDate(config.dataDir);
|
|
3803
4707
|
const resolvedBinary = resolveBinaryPath(config);
|
|
@@ -3872,11 +4776,13 @@ async function runNodeStartCommand(config, options) {
|
|
|
3872
4776
|
removePidFile(config.dataDir);
|
|
3873
4777
|
});
|
|
3874
4778
|
processManager.on("stdout", (text) => {
|
|
3875
|
-
appendToTodayLog(config.dataDir, "fnn.stdout.log", text)
|
|
4779
|
+
appendToTodayLog(config.dataDir, "fnn.stdout.log", text).catch(() => {
|
|
4780
|
+
});
|
|
3876
4781
|
emitFnnLog("stdout", text);
|
|
3877
4782
|
});
|
|
3878
4783
|
processManager.on("stderr", (text) => {
|
|
3879
|
-
appendToTodayLog(config.dataDir, "fnn.stderr.log", text)
|
|
4784
|
+
appendToTodayLog(config.dataDir, "fnn.stderr.log", text).catch(() => {
|
|
4785
|
+
});
|
|
3880
4786
|
emitFnnLog("stderr", text);
|
|
3881
4787
|
});
|
|
3882
4788
|
await processManager.start();
|
|
@@ -3970,7 +4876,7 @@ async function runNodeStartCommand(config, options) {
|
|
|
3970
4876
|
alerts: [{ type: "stdout" }, { type: "daily-file", baseLogsDir: logsBaseDir }],
|
|
3971
4877
|
jobs: {
|
|
3972
4878
|
enabled: true,
|
|
3973
|
-
dbPath:
|
|
4879
|
+
dbPath: join9(config.dataDir, "runtime-jobs.db")
|
|
3974
4880
|
}
|
|
3975
4881
|
});
|
|
3976
4882
|
const runtimeStatus = runtime.service.getStatus();
|
|
@@ -3982,9 +4888,9 @@ async function runNodeStartCommand(config, options) {
|
|
|
3982
4888
|
fiberRpcUrl: runtimeStatus.targetUrl,
|
|
3983
4889
|
proxyListen: runtimeStatus.proxyListen,
|
|
3984
4890
|
stateFilePath: runtimeStateFilePath,
|
|
3985
|
-
alertLogFilePath:
|
|
3986
|
-
fnnStdoutLogPath:
|
|
3987
|
-
fnnStderrLogPath:
|
|
4891
|
+
alertLogFilePath: join9(todayLogDir, "runtime.alerts.jsonl"),
|
|
4892
|
+
fnnStdoutLogPath: join9(todayLogDir, "fnn.stdout.log"),
|
|
4893
|
+
fnnStderrLogPath: join9(todayLogDir, "fnn.stderr.log"),
|
|
3988
4894
|
logsBaseDir,
|
|
3989
4895
|
daemon: false
|
|
3990
4896
|
});
|
|
@@ -4055,7 +4961,7 @@ async function runNodeStartCommand(config, options) {
|
|
|
4055
4961
|
process.exit(1);
|
|
4056
4962
|
}
|
|
4057
4963
|
emitStage("rpc_ready", "ok", { rpcUrl: config.rpcUrl });
|
|
4058
|
-
const bootnodes = nodeConfig.configFilePath ? extractBootnodeAddrs(nodeConfig.configFilePath) : extractBootnodeAddrs(
|
|
4964
|
+
const bootnodes = nodeConfig.configFilePath ? extractBootnodeAddrs(nodeConfig.configFilePath) : extractBootnodeAddrs(join9(config.dataDir, "config.yml"));
|
|
4059
4965
|
if (bootnodes.length > 0) {
|
|
4060
4966
|
await autoConnectBootnodes(rpc, bootnodes);
|
|
4061
4967
|
}
|
|
@@ -4117,6 +5023,23 @@ async function runNodeStartCommand(config, options) {
|
|
|
4117
5023
|
if (!json) {
|
|
4118
5024
|
console.log("\n\u{1F6D1} Shutting down...");
|
|
4119
5025
|
}
|
|
5026
|
+
let flushTimeout;
|
|
5027
|
+
try {
|
|
5028
|
+
await Promise.race([
|
|
5029
|
+
flushPendingLogs(),
|
|
5030
|
+
new Promise((_, reject) => {
|
|
5031
|
+
flushTimeout = setTimeout(() => reject(new Error("Flush timeout")), 5e3);
|
|
5032
|
+
})
|
|
5033
|
+
]);
|
|
5034
|
+
} catch (_err) {
|
|
5035
|
+
if (!json) {
|
|
5036
|
+
console.log("\u26A0\uFE0F Log flush timed out, continuing shutdown...");
|
|
5037
|
+
}
|
|
5038
|
+
} finally {
|
|
5039
|
+
if (flushTimeout !== void 0) {
|
|
5040
|
+
clearTimeout(flushTimeout);
|
|
5041
|
+
}
|
|
5042
|
+
}
|
|
4120
5043
|
if (runtimeDaemon) {
|
|
4121
5044
|
stopRuntimeDaemonFromNode({ dataDir: config.dataDir, rpcUrl: config.rpcUrl });
|
|
4122
5045
|
} else if (runtime) {
|
|
@@ -5003,7 +5926,7 @@ async function runMigrationAndReport(opts) {
|
|
|
5003
5926
|
|
|
5004
5927
|
// src/commands/node.ts
|
|
5005
5928
|
function createNodeCommand(config) {
|
|
5006
|
-
const node = new
|
|
5929
|
+
const node = new Command10("node").description("Node management");
|
|
5007
5930
|
node.command("start").option("--daemon", "Start node in detached background mode (node + runtime)").option("--runtime-proxy-listen <host:port>", "Runtime monitor proxy listen address").option("--event-stream <format>", "Event stream format for --json mode (jsonl)", "jsonl").option("--quiet-fnn", "Do not mirror fnn stdout/stderr to console; keep file persistence").option("--json").action(async (options) => {
|
|
5008
5931
|
await runNodeStartCommand(config, options);
|
|
5009
5932
|
});
|
|
@@ -5033,9 +5956,9 @@ function createNodeCommand(config) {
|
|
|
5033
5956
|
|
|
5034
5957
|
// src/commands/payment.ts
|
|
5035
5958
|
import { ckbToShannons as ckbToShannons4, shannonsToCkb as shannonsToCkb6 } from "@fiber-pay/sdk";
|
|
5036
|
-
import { Command as
|
|
5959
|
+
import { Command as Command11 } from "commander";
|
|
5037
5960
|
function createPaymentCommand(config) {
|
|
5038
|
-
const payment = new
|
|
5961
|
+
const payment = new Command11("payment").description("Payment lifecycle and status commands");
|
|
5039
5962
|
payment.command("send").argument("[invoice]").option("--invoice <invoice>").option("--to <nodeId>").option("--amount <ckb>").option("--max-fee <ckb>").option("--wait", "Wait for runtime job terminal status when runtime proxy is active").option("--timeout <seconds>", "Wait timeout for --wait mode", "120").option("--json").action(async (invoiceArg, options) => {
|
|
5040
5963
|
const rpc = await createReadyRpcClient(config);
|
|
5041
5964
|
const json = Boolean(options.json);
|
|
@@ -5369,7 +6292,7 @@ function getJobFailure(job) {
|
|
|
5369
6292
|
}
|
|
5370
6293
|
|
|
5371
6294
|
// src/commands/peer.ts
|
|
5372
|
-
import { Command as
|
|
6295
|
+
import { Command as Command12 } from "commander";
|
|
5373
6296
|
function extractPeerIdFromMultiaddr(address) {
|
|
5374
6297
|
const match = address.match(/\/p2p\/([^/]+)$/);
|
|
5375
6298
|
return match?.[1];
|
|
@@ -5386,7 +6309,7 @@ async function waitForPeerConnected(rpc, peerId, timeoutMs) {
|
|
|
5386
6309
|
return false;
|
|
5387
6310
|
}
|
|
5388
6311
|
function createPeerCommand(config) {
|
|
5389
|
-
const peer = new
|
|
6312
|
+
const peer = new Command12("peer").description("Peer management");
|
|
5390
6313
|
peer.command("list").option("--json").action(async (options) => {
|
|
5391
6314
|
const rpc = await createReadyRpcClient(config);
|
|
5392
6315
|
const peers = await rpc.listPeers();
|
|
@@ -5432,8 +6355,8 @@ function createPeerCommand(config) {
|
|
|
5432
6355
|
}
|
|
5433
6356
|
|
|
5434
6357
|
// src/commands/runtime.ts
|
|
5435
|
-
import { spawn as
|
|
5436
|
-
import { join as
|
|
6358
|
+
import { spawn as spawn3 } from "child_process";
|
|
6359
|
+
import { join as join10, resolve } from "path";
|
|
5437
6360
|
import {
|
|
5438
6361
|
alertPriorityOrder,
|
|
5439
6362
|
formatRuntimeAlert as formatRuntimeAlert2,
|
|
@@ -5441,7 +6364,7 @@ import {
|
|
|
5441
6364
|
isAlertType,
|
|
5442
6365
|
startRuntimeService as startRuntimeService2
|
|
5443
6366
|
} from "@fiber-pay/runtime";
|
|
5444
|
-
import { Command as
|
|
6367
|
+
import { Command as Command13 } from "commander";
|
|
5445
6368
|
|
|
5446
6369
|
// src/lib/parse-options.ts
|
|
5447
6370
|
function parseIntegerOption(value, name) {
|
|
@@ -5512,7 +6435,7 @@ function resolveRuntimeRecoveryListen(config) {
|
|
|
5512
6435
|
return meta?.proxyListen ?? config.runtimeProxyListen ?? "127.0.0.1:8229";
|
|
5513
6436
|
}
|
|
5514
6437
|
function createRuntimeCommand(config) {
|
|
5515
|
-
const runtime = new
|
|
6438
|
+
const runtime = new Command13("runtime").description("Polling monitor and alert runtime service");
|
|
5516
6439
|
runtime.command("start").description("Start runtime monitor service in foreground").option("--daemon", "Start runtime monitor in detached background mode").option("--fiber-rpc-url <url>", "Target fiber rpc URL (defaults to --rpc-url/global config)").option("--proxy-listen <host:port>", "Monitor proxy listen address").option("--channel-poll-ms <ms>", "Channel polling interval in milliseconds").option("--invoice-poll-ms <ms>", "Invoice polling interval in milliseconds").option("--payment-poll-ms <ms>", "Payment polling interval in milliseconds").option("--peer-poll-ms <ms>", "Peer polling interval in milliseconds").option("--health-poll-ms <ms>", "RPC health polling interval in milliseconds").option("--include-closed <bool>", "Monitor closed channels (true|false)").option("--completed-ttl-seconds <seconds>", "TTL for completed invoices/payments in tracker").option("--state-file <path>", "State file path for snapshots and history").option("--alert-log-file <path>", "Path to runtime alert JSONL log file (legacy static path)").option("--alert-logs-base-dir <dir>", "Base logs directory for daily-rotated alert files").option("--flush-ms <ms>", "State flush interval in milliseconds").option("--webhook <url>", "Webhook URL to receive alert POST payloads").option("--websocket <host:port>", "WebSocket alert broadcast listen address").option(
|
|
5517
6440
|
"--log-min-priority <priority>",
|
|
5518
6441
|
"Minimum runtime log priority (critical|high|medium|low)"
|
|
@@ -5570,7 +6493,7 @@ function createRuntimeCommand(config) {
|
|
|
5570
6493
|
}
|
|
5571
6494
|
if (daemon && !isRuntimeChild) {
|
|
5572
6495
|
const childArgv = process.argv.filter((arg) => arg !== "--daemon");
|
|
5573
|
-
const child =
|
|
6496
|
+
const child = spawn3(process.execPath, childArgv.slice(1), {
|
|
5574
6497
|
detached: true,
|
|
5575
6498
|
stdio: "ignore",
|
|
5576
6499
|
cwd: process.cwd(),
|
|
@@ -5662,7 +6585,7 @@ function createRuntimeCommand(config) {
|
|
|
5662
6585
|
logsBaseDir,
|
|
5663
6586
|
ensureExists: false
|
|
5664
6587
|
});
|
|
5665
|
-
const effectiveAlertLogPath = alertLogsBaseDir ?
|
|
6588
|
+
const effectiveAlertLogPath = alertLogsBaseDir ? join10(todayLogDir, "runtime.alerts.jsonl") : alertLogFile ?? join10(todayLogDir, "runtime.alerts.jsonl");
|
|
5666
6589
|
writeRuntimePid(config.dataDir, process.pid);
|
|
5667
6590
|
writeRuntimeMeta(config.dataDir, {
|
|
5668
6591
|
pid: process.pid,
|
|
@@ -5671,8 +6594,8 @@ function createRuntimeCommand(config) {
|
|
|
5671
6594
|
proxyListen: status.proxyListen,
|
|
5672
6595
|
stateFilePath: runtimeConfig.storage?.stateFilePath,
|
|
5673
6596
|
alertLogFilePath: effectiveAlertLogPath,
|
|
5674
|
-
fnnStdoutLogPath:
|
|
5675
|
-
fnnStderrLogPath:
|
|
6597
|
+
fnnStdoutLogPath: join10(todayLogDir, "fnn.stdout.log"),
|
|
6598
|
+
fnnStderrLogPath: join10(todayLogDir, "fnn.stderr.log"),
|
|
5676
6599
|
logsBaseDir,
|
|
5677
6600
|
daemon: daemon || isRuntimeChild
|
|
5678
6601
|
});
|
|
@@ -5921,15 +6844,15 @@ function createRuntimeCommand(config) {
|
|
|
5921
6844
|
}
|
|
5922
6845
|
|
|
5923
6846
|
// src/commands/version.ts
|
|
5924
|
-
import { Command as
|
|
6847
|
+
import { Command as Command14 } from "commander";
|
|
5925
6848
|
|
|
5926
6849
|
// src/lib/build-info.ts
|
|
5927
|
-
var CLI_VERSION = "0.1.
|
|
5928
|
-
var CLI_COMMIT = "
|
|
6850
|
+
var CLI_VERSION = "0.1.1";
|
|
6851
|
+
var CLI_COMMIT = "4b099d434842d019d12ba4df4031314860de25be";
|
|
5929
6852
|
|
|
5930
6853
|
// src/commands/version.ts
|
|
5931
6854
|
function createVersionCommand() {
|
|
5932
|
-
return new
|
|
6855
|
+
return new Command14("version").description("Show CLI version and commit id").option("--json", "Output JSON").action((options) => {
|
|
5933
6856
|
const payload = {
|
|
5934
6857
|
version: CLI_VERSION,
|
|
5935
6858
|
commit: CLI_COMMIT
|
|
@@ -5944,7 +6867,7 @@ function createVersionCommand() {
|
|
|
5944
6867
|
}
|
|
5945
6868
|
|
|
5946
6869
|
// src/commands/wallet.ts
|
|
5947
|
-
import { Command as
|
|
6870
|
+
import { Command as Command15, Option } from "commander";
|
|
5948
6871
|
|
|
5949
6872
|
// src/lib/wallet-address.ts
|
|
5950
6873
|
import { scriptToAddress as scriptToAddress2 } from "@fiber-pay/sdk";
|
|
@@ -5959,6 +6882,14 @@ async function runWalletAddressCommand(config, options) {
|
|
|
5959
6882
|
printJsonSuccess({ address });
|
|
5960
6883
|
return;
|
|
5961
6884
|
}
|
|
6885
|
+
if (options.qrcode) {
|
|
6886
|
+
const qrcode = await import("qrcode");
|
|
6887
|
+
console.log("\u2705 Funding address retrieved\n");
|
|
6888
|
+
const qrString = await qrcode.toString(address, { type: "terminal", small: true });
|
|
6889
|
+
console.log(qrString);
|
|
6890
|
+
console.log(` ${truncateMiddle(address, 8, 6)}`);
|
|
6891
|
+
return;
|
|
6892
|
+
}
|
|
5962
6893
|
console.log("\u2705 Funding address retrieved");
|
|
5963
6894
|
console.log(` Address: ${address}`);
|
|
5964
6895
|
}
|
|
@@ -5991,8 +6922,8 @@ async function runWalletBalanceCommand(config, options) {
|
|
|
5991
6922
|
|
|
5992
6923
|
// src/commands/wallet.ts
|
|
5993
6924
|
function createWalletCommand(config) {
|
|
5994
|
-
const wallet = new
|
|
5995
|
-
wallet.command("address").description("Display the funding address").
|
|
6925
|
+
const wallet = new Command15("wallet").description("Wallet management");
|
|
6926
|
+
wallet.command("address").description("Display the funding address").addOption(new Option("--json", "Output as JSON").conflicts("qrcode")).option("--qrcode", "Display address as QR code in terminal").action(async (options) => {
|
|
5996
6927
|
await runWalletAddressCommand(config, options);
|
|
5997
6928
|
});
|
|
5998
6929
|
wallet.command("balance").description("Display the CKB balance").option("--json").action(async (options) => {
|
|
@@ -6138,7 +7069,7 @@ function applyGlobalOverrides(argv) {
|
|
|
6138
7069
|
}
|
|
6139
7070
|
if (!explicitDataDir && profileName) {
|
|
6140
7071
|
const homeDir = process.env.HOME ?? process.cwd();
|
|
6141
|
-
process.env.FIBER_DATA_DIR =
|
|
7072
|
+
process.env.FIBER_DATA_DIR = join11(homeDir, ".fiber-pay", "profiles", profileName);
|
|
6142
7073
|
}
|
|
6143
7074
|
}
|
|
6144
7075
|
function printFatal(error) {
|
|
@@ -6166,7 +7097,7 @@ async function main() {
|
|
|
6166
7097
|
}
|
|
6167
7098
|
applyGlobalOverrides(argv);
|
|
6168
7099
|
const config = getEffectiveConfig(explicitFlags).config;
|
|
6169
|
-
const program = new
|
|
7100
|
+
const program = new Command16();
|
|
6170
7101
|
program.name("fiber-pay").description("AI Agent Payment SDK for CKB Lightning Network").option("--profile <name>", "Use profile at ~/.fiber-pay/profiles/<name>").option("--data-dir <path>", "Override data directory for all commands").option("--rpc-url <url>", "Override RPC URL for all commands").option("--rpc-biscuit-token <token>", "Set RPC Authorization Bearer token for all commands").option("--network <network>", "Override network for all commands (testnet|mainnet)").option("--key-password <password>", "Override key password for all commands").option("--binary-path <path>", "Override fiber binary path for all commands").showHelpAfterError().showSuggestionAfterError();
|
|
6171
7102
|
program.exitOverride();
|
|
6172
7103
|
program.configureOutput({
|
|
@@ -6178,6 +7109,7 @@ async function main() {
|
|
|
6178
7109
|
}
|
|
6179
7110
|
});
|
|
6180
7111
|
program.addCommand(createNodeCommand(config));
|
|
7112
|
+
program.addCommand(createAgentCommand(config));
|
|
6181
7113
|
program.addCommand(createChannelCommand(config));
|
|
6182
7114
|
program.addCommand(createInvoiceCommand(config));
|
|
6183
7115
|
program.addCommand(createPaymentCommand(config));
|
|
@@ -6187,6 +7119,7 @@ async function main() {
|
|
|
6187
7119
|
program.addCommand(createGraphCommand(config));
|
|
6188
7120
|
program.addCommand(createBinaryCommand(config));
|
|
6189
7121
|
program.addCommand(createConfigCommand(config));
|
|
7122
|
+
program.addCommand(createL402Command(config));
|
|
6190
7123
|
program.addCommand(createRuntimeCommand(config));
|
|
6191
7124
|
program.addCommand(createVersionCommand());
|
|
6192
7125
|
program.addCommand(createWalletCommand(config));
|