@fiber-pay/cli 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/dist/cli.js +1159 -404
- package/dist/cli.js.map +1 -1
- package/package.json +7 -4
package/dist/cli.js
CHANGED
|
@@ -2,166 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { join as join11 } from "path";
|
|
5
|
-
import { Command as
|
|
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 {
|
|
@@ -300,8 +147,8 @@ function formatChannel(channel) {
|
|
|
300
147
|
return {
|
|
301
148
|
channelId: channel.channel_id,
|
|
302
149
|
channelIdShort: truncateMiddle(channel.channel_id, 10, 8),
|
|
303
|
-
peerId: channel.
|
|
304
|
-
peerIdShort: truncateMiddle(channel.
|
|
150
|
+
peerId: channel.pubkey,
|
|
151
|
+
peerIdShort: truncateMiddle(channel.pubkey, 10, 8),
|
|
305
152
|
state: channel.state.state_name,
|
|
306
153
|
stateLabel: stateLabel(channel.state.state_name),
|
|
307
154
|
stateFlags: channel.state.state_flags,
|
|
@@ -338,10 +185,10 @@ function extractInvoiceMetadata(invoice) {
|
|
|
338
185
|
let description;
|
|
339
186
|
let expirySeconds;
|
|
340
187
|
for (const attr of invoice.data.attrs) {
|
|
341
|
-
if ("
|
|
342
|
-
if ("
|
|
188
|
+
if ("description" in attr) description = attr.description;
|
|
189
|
+
if ("expiry_time" in attr) {
|
|
343
190
|
try {
|
|
344
|
-
expirySeconds = Number(BigInt(attr.
|
|
191
|
+
expirySeconds = Number(BigInt(attr.expiry_time));
|
|
345
192
|
} catch {
|
|
346
193
|
}
|
|
347
194
|
}
|
|
@@ -388,7 +235,7 @@ function printChannelDetailHuman(channel) {
|
|
|
388
235
|
const capacity = local + remote;
|
|
389
236
|
console.log("Channel");
|
|
390
237
|
console.log(` ID: ${channel.channel_id}`);
|
|
391
|
-
console.log(` Peer: ${channel.
|
|
238
|
+
console.log(` Peer: ${channel.pubkey}`);
|
|
392
239
|
console.log(
|
|
393
240
|
` State: ${stateLabel(channel.state.state_name)} (${channel.state.state_name})`
|
|
394
241
|
);
|
|
@@ -461,7 +308,7 @@ function printChannelListHuman(channels) {
|
|
|
461
308
|
);
|
|
462
309
|
for (const channel of channels) {
|
|
463
310
|
const id = truncateMiddle(channel.channel_id, 10, 8).padEnd(22, " ");
|
|
464
|
-
const peer = truncateMiddle(channel.
|
|
311
|
+
const peer = truncateMiddle(channel.pubkey, 10, 8).padEnd(22, " ");
|
|
465
312
|
const state = channel.state.state_name.padEnd(24, " ");
|
|
466
313
|
const local = `${shannonsToCkb(channel.local_balance)}`.padStart(8, " ");
|
|
467
314
|
const remote = `${shannonsToCkb(channel.remote_balance)}`.padStart(8, " ");
|
|
@@ -476,13 +323,845 @@ function printPeerListHuman(peers) {
|
|
|
476
323
|
}
|
|
477
324
|
console.log(`Peers: ${peers.length}`);
|
|
478
325
|
console.log("");
|
|
479
|
-
console.log("
|
|
480
|
-
console.log("
|
|
326
|
+
console.log("PUBKEY ADDRESS");
|
|
327
|
+
console.log("----------------------------------------------------------");
|
|
481
328
|
for (const peer of peers) {
|
|
482
|
-
const peerId = truncateMiddle(peer.peer_id, 10, 8).padEnd(22, " ");
|
|
483
329
|
const pubkey = truncateMiddle(peer.pubkey, 10, 8).padEnd(22, " ");
|
|
484
|
-
console.log(`${
|
|
330
|
+
console.log(`${pubkey} ${peer.address}`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// src/lib/rpc.ts
|
|
335
|
+
import { FiberRpcClient } from "@fiber-pay/sdk";
|
|
336
|
+
|
|
337
|
+
// src/lib/pid.ts
|
|
338
|
+
import { existsSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
339
|
+
import { join } from "path";
|
|
340
|
+
function getPidFilePath(dataDir) {
|
|
341
|
+
return join(dataDir, "fiber.pid");
|
|
342
|
+
}
|
|
343
|
+
function writePidFile(dataDir, pid) {
|
|
344
|
+
writeFileSync(getPidFilePath(dataDir), String(pid));
|
|
345
|
+
}
|
|
346
|
+
function readPidFile(dataDir) {
|
|
347
|
+
const pidPath = getPidFilePath(dataDir);
|
|
348
|
+
if (!existsSync(pidPath)) return null;
|
|
349
|
+
try {
|
|
350
|
+
return parseInt(readFileSync(pidPath, "utf-8").trim(), 10);
|
|
351
|
+
} catch {
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
function removePidFile(dataDir) {
|
|
356
|
+
const pidPath = getPidFilePath(dataDir);
|
|
357
|
+
if (existsSync(pidPath)) {
|
|
358
|
+
unlinkSync(pidPath);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
function isProcessRunning(pid) {
|
|
362
|
+
try {
|
|
363
|
+
process.kill(pid, 0);
|
|
364
|
+
return true;
|
|
365
|
+
} catch {
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// src/lib/runtime-meta.ts
|
|
371
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
372
|
+
import { join as join2 } from "path";
|
|
373
|
+
function getRuntimePidFilePath(dataDir) {
|
|
374
|
+
return join2(dataDir, "runtime.pid");
|
|
375
|
+
}
|
|
376
|
+
function getRuntimeMetaFilePath(dataDir) {
|
|
377
|
+
return join2(dataDir, "runtime.meta.json");
|
|
378
|
+
}
|
|
379
|
+
function writeRuntimePid(dataDir, pid) {
|
|
380
|
+
writeFileSync2(getRuntimePidFilePath(dataDir), String(pid));
|
|
381
|
+
}
|
|
382
|
+
function readRuntimePid(dataDir) {
|
|
383
|
+
const pidPath = getRuntimePidFilePath(dataDir);
|
|
384
|
+
if (!existsSync2(pidPath)) return null;
|
|
385
|
+
try {
|
|
386
|
+
return Number.parseInt(readFileSync2(pidPath, "utf-8").trim(), 10);
|
|
387
|
+
} catch {
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
function writeRuntimeMeta(dataDir, meta) {
|
|
392
|
+
writeFileSync2(getRuntimeMetaFilePath(dataDir), JSON.stringify(meta, null, 2));
|
|
393
|
+
}
|
|
394
|
+
function readRuntimeMeta(dataDir) {
|
|
395
|
+
const metaPath = getRuntimeMetaFilePath(dataDir);
|
|
396
|
+
if (!existsSync2(metaPath)) return null;
|
|
397
|
+
try {
|
|
398
|
+
return JSON.parse(readFileSync2(metaPath, "utf-8"));
|
|
399
|
+
} catch {
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
function removeRuntimeFiles(dataDir) {
|
|
404
|
+
const pidPath = getRuntimePidFilePath(dataDir);
|
|
405
|
+
const metaPath = getRuntimeMetaFilePath(dataDir);
|
|
406
|
+
if (existsSync2(pidPath)) {
|
|
407
|
+
unlinkSync2(pidPath);
|
|
408
|
+
}
|
|
409
|
+
if (existsSync2(metaPath)) {
|
|
410
|
+
unlinkSync2(metaPath);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// src/lib/rpc.ts
|
|
415
|
+
function normalizeUrl(url) {
|
|
416
|
+
try {
|
|
417
|
+
const normalized = new URL(url).toString();
|
|
418
|
+
return normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
|
|
419
|
+
} catch {
|
|
420
|
+
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
function resolveRuntimeProxyUrl(config) {
|
|
424
|
+
const runtimeMeta = readRuntimeMeta(config.dataDir);
|
|
425
|
+
const runtimePid = readRuntimePid(config.dataDir);
|
|
426
|
+
if (!runtimeMeta || !runtimePid || !isProcessRunning(runtimePid)) {
|
|
427
|
+
return void 0;
|
|
428
|
+
}
|
|
429
|
+
if (!runtimeMeta.proxyListen || !runtimeMeta.fiberRpcUrl) {
|
|
430
|
+
return void 0;
|
|
431
|
+
}
|
|
432
|
+
if (normalizeUrl(runtimeMeta.fiberRpcUrl) !== normalizeUrl(config.rpcUrl)) {
|
|
433
|
+
return void 0;
|
|
434
|
+
}
|
|
435
|
+
if (runtimeMeta.proxyListen.startsWith("http://") || runtimeMeta.proxyListen.startsWith("https://")) {
|
|
436
|
+
return runtimeMeta.proxyListen;
|
|
437
|
+
}
|
|
438
|
+
return `http://${runtimeMeta.proxyListen}`;
|
|
439
|
+
}
|
|
440
|
+
function createRpcClient(config) {
|
|
441
|
+
const resolved = resolveRpcEndpoint(config);
|
|
442
|
+
return new FiberRpcClient({
|
|
443
|
+
url: resolved.url,
|
|
444
|
+
biscuitToken: config.rpcBiscuitToken
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
function resolveRpcEndpoint(config) {
|
|
448
|
+
const runtimeProxyUrl = resolveRuntimeProxyUrl(config);
|
|
449
|
+
if (runtimeProxyUrl) {
|
|
450
|
+
return {
|
|
451
|
+
url: runtimeProxyUrl,
|
|
452
|
+
target: "runtime-proxy"
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
return {
|
|
456
|
+
url: config.rpcUrl,
|
|
457
|
+
target: "node-rpc"
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
async function createReadyRpcClient(config, options = {}) {
|
|
461
|
+
const rpc = createRpcClient(config);
|
|
462
|
+
await rpc.waitForReady({ timeout: options.timeout ?? 3e3, interval: options.interval ?? 500 });
|
|
463
|
+
return rpc;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// src/lib/agent-call.ts
|
|
467
|
+
function formatDuration(durationMs) {
|
|
468
|
+
if (typeof durationMs !== "number" || Number.isNaN(durationMs)) {
|
|
469
|
+
return "unknown";
|
|
470
|
+
}
|
|
471
|
+
return `${durationMs}ms`;
|
|
472
|
+
}
|
|
473
|
+
function printFriendlySuccess(result, options) {
|
|
474
|
+
const agent = typeof result.agent === "string" ? result.agent : "unknown";
|
|
475
|
+
const duration = formatDuration(result.durationMs);
|
|
476
|
+
const response = typeof result.response === "string" ? result.response : JSON.stringify(result.response, null, 2);
|
|
477
|
+
console.log("Agent call succeeded");
|
|
478
|
+
console.log(` Agent: ${agent}`);
|
|
479
|
+
console.log(` Duration: ${duration}`);
|
|
480
|
+
console.log(` Payment: ${options.paymentRequired ? "required" : "not required"}`);
|
|
481
|
+
if (options.paymentHash) {
|
|
482
|
+
console.log(` Payment hash: ${options.paymentHash}`);
|
|
483
|
+
}
|
|
484
|
+
console.log("");
|
|
485
|
+
console.log("Agent response:");
|
|
486
|
+
console.log(response ?? "");
|
|
487
|
+
}
|
|
488
|
+
async function runAgentCallCommand(config, url, options) {
|
|
489
|
+
const asJson = Boolean(options.json);
|
|
490
|
+
const timeoutMs = parseInt(options.timeout, 10) * 1e3;
|
|
491
|
+
let prompt = options.prompt;
|
|
492
|
+
if (!prompt && options.file) {
|
|
493
|
+
try {
|
|
494
|
+
prompt = readFileSync3(options.file, "utf-8").trim();
|
|
495
|
+
} catch (err) {
|
|
496
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
497
|
+
if (asJson) {
|
|
498
|
+
printJsonError({
|
|
499
|
+
code: "AGENT_CALL_FILE_READ_ERROR",
|
|
500
|
+
message: `Failed to read prompt file: ${message}`,
|
|
501
|
+
recoverable: true,
|
|
502
|
+
suggestion: "Check the file path and try again."
|
|
503
|
+
});
|
|
504
|
+
} else {
|
|
505
|
+
console.error(`Error: Failed to read prompt file: ${message}`);
|
|
506
|
+
}
|
|
507
|
+
process.exit(1);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
if (!prompt) {
|
|
511
|
+
if (!process.stdin.isTTY) {
|
|
512
|
+
const chunks = [];
|
|
513
|
+
for await (const chunk of process.stdin) {
|
|
514
|
+
chunks.push(chunk);
|
|
515
|
+
}
|
|
516
|
+
prompt = Buffer.concat(chunks).toString("utf-8").trim();
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
if (!prompt) {
|
|
520
|
+
if (asJson) {
|
|
521
|
+
printJsonError({
|
|
522
|
+
code: "AGENT_CALL_NO_PROMPT",
|
|
523
|
+
message: "No prompt provided.",
|
|
524
|
+
recoverable: true,
|
|
525
|
+
suggestion: "Use --prompt <text>, --file <path>, or pipe via stdin."
|
|
526
|
+
});
|
|
527
|
+
} else {
|
|
528
|
+
console.error("Error: No prompt provided.");
|
|
529
|
+
console.error(" Use --prompt <text>, --file <path>, or pipe via stdin.");
|
|
530
|
+
}
|
|
531
|
+
process.exit(1);
|
|
532
|
+
}
|
|
533
|
+
const targetUrl = url.endsWith("/") ? url : `${url}/`;
|
|
534
|
+
const controller = new AbortController();
|
|
535
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
536
|
+
try {
|
|
537
|
+
if (!asJson) {
|
|
538
|
+
console.log(`Calling agent at ${targetUrl}...`);
|
|
539
|
+
}
|
|
540
|
+
const initialResponse = await fetch(targetUrl, {
|
|
541
|
+
method: "POST",
|
|
542
|
+
headers: { "Content-Type": "application/json" },
|
|
543
|
+
body: JSON.stringify({ prompt }),
|
|
544
|
+
signal: controller.signal
|
|
545
|
+
});
|
|
546
|
+
if (initialResponse.ok) {
|
|
547
|
+
const body = await initialResponse.json();
|
|
548
|
+
clearTimeout(timer);
|
|
549
|
+
if (asJson) {
|
|
550
|
+
printJsonSuccess(body);
|
|
551
|
+
} else {
|
|
552
|
+
printFriendlySuccess(body, { paymentRequired: false });
|
|
553
|
+
}
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
if (initialResponse.status !== 402 && initialResponse.status !== 401) {
|
|
557
|
+
const body = await initialResponse.text();
|
|
558
|
+
clearTimeout(timer);
|
|
559
|
+
if (asJson) {
|
|
560
|
+
printJsonError({
|
|
561
|
+
code: "AGENT_CALL_UNEXPECTED_STATUS",
|
|
562
|
+
message: `Unexpected status ${initialResponse.status} from agent.`,
|
|
563
|
+
recoverable: false,
|
|
564
|
+
details: { body: body.slice(0, 500) }
|
|
565
|
+
});
|
|
566
|
+
} else {
|
|
567
|
+
console.error(`Error: Unexpected status ${initialResponse.status}`);
|
|
568
|
+
console.error(body.slice(0, 500));
|
|
569
|
+
}
|
|
570
|
+
process.exit(1);
|
|
571
|
+
}
|
|
572
|
+
const challengeBody = await initialResponse.json();
|
|
573
|
+
const macaroon = challengeBody.macaroon;
|
|
574
|
+
const invoiceAddress = challengeBody.invoice;
|
|
575
|
+
if (!macaroon || !invoiceAddress) {
|
|
576
|
+
clearTimeout(timer);
|
|
577
|
+
if (asJson) {
|
|
578
|
+
printJsonError({
|
|
579
|
+
code: "AGENT_CALL_INVALID_CHALLENGE",
|
|
580
|
+
message: "Invalid L402 challenge: missing macaroon or invoice.",
|
|
581
|
+
recoverable: false
|
|
582
|
+
});
|
|
583
|
+
} else {
|
|
584
|
+
console.error("Error: Invalid L402 challenge response (missing macaroon or invoice).");
|
|
585
|
+
}
|
|
586
|
+
process.exit(1);
|
|
587
|
+
}
|
|
588
|
+
if (!asJson) {
|
|
589
|
+
console.log("Payment required. Paying invoice via Fiber...");
|
|
590
|
+
}
|
|
591
|
+
const rpcClient = await createReadyRpcClient(config);
|
|
592
|
+
const paymentResult = await rpcClient.sendPayment({
|
|
593
|
+
invoice: invoiceAddress
|
|
594
|
+
});
|
|
595
|
+
if (!asJson) {
|
|
596
|
+
console.log(
|
|
597
|
+
`Payment sent (hash: ${paymentResult.payment_hash}). Waiting for confirmation...`
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
const finalPayment = await rpcClient.waitForPayment(paymentResult.payment_hash, {
|
|
601
|
+
timeout: timeoutMs,
|
|
602
|
+
interval: 2e3
|
|
603
|
+
});
|
|
604
|
+
if (finalPayment.status !== "Success") {
|
|
605
|
+
clearTimeout(timer);
|
|
606
|
+
if (asJson) {
|
|
607
|
+
printJsonError({
|
|
608
|
+
code: "AGENT_CALL_PAYMENT_FAILED",
|
|
609
|
+
message: `Payment failed: ${finalPayment.failed_error ?? finalPayment.status}`,
|
|
610
|
+
recoverable: false
|
|
611
|
+
});
|
|
612
|
+
} else {
|
|
613
|
+
console.error(`Error: Payment failed: ${finalPayment.failed_error ?? finalPayment.status}`);
|
|
614
|
+
}
|
|
615
|
+
process.exit(1);
|
|
616
|
+
}
|
|
617
|
+
if (!asJson) {
|
|
618
|
+
console.log("Payment confirmed. Retrying request with L402 token...");
|
|
619
|
+
}
|
|
620
|
+
const retryResponse = await fetch(targetUrl, {
|
|
621
|
+
method: "POST",
|
|
622
|
+
headers: {
|
|
623
|
+
"Content-Type": "application/json",
|
|
624
|
+
Authorization: `L402 ${macaroon}`,
|
|
625
|
+
"X-L402-Payment-Hash": paymentResult.payment_hash
|
|
626
|
+
},
|
|
627
|
+
body: JSON.stringify({ prompt }),
|
|
628
|
+
signal: controller.signal
|
|
629
|
+
});
|
|
630
|
+
clearTimeout(timer);
|
|
631
|
+
if (!retryResponse.ok) {
|
|
632
|
+
const errBody = await retryResponse.text();
|
|
633
|
+
if (asJson) {
|
|
634
|
+
printJsonError({
|
|
635
|
+
code: "AGENT_CALL_RETRY_FAILED",
|
|
636
|
+
message: `Agent returned ${retryResponse.status} after payment.`,
|
|
637
|
+
recoverable: false,
|
|
638
|
+
details: { body: errBody.slice(0, 500) }
|
|
639
|
+
});
|
|
640
|
+
} else {
|
|
641
|
+
console.error(`Error: Agent returned ${retryResponse.status} after payment.`);
|
|
642
|
+
console.error(errBody.slice(0, 500));
|
|
643
|
+
}
|
|
644
|
+
process.exit(1);
|
|
645
|
+
}
|
|
646
|
+
const result = await retryResponse.json();
|
|
647
|
+
if (asJson) {
|
|
648
|
+
printJsonSuccess({
|
|
649
|
+
...result,
|
|
650
|
+
paymentHash: paymentResult.payment_hash
|
|
651
|
+
});
|
|
652
|
+
} else {
|
|
653
|
+
printFriendlySuccess(result, {
|
|
654
|
+
paymentRequired: true,
|
|
655
|
+
paymentHash: paymentResult.payment_hash
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
} catch (error) {
|
|
659
|
+
clearTimeout(timer);
|
|
660
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
661
|
+
const isTimeout = error instanceof Error && error.name === "AbortError";
|
|
662
|
+
if (asJson) {
|
|
663
|
+
printJsonError({
|
|
664
|
+
code: isTimeout ? "AGENT_CALL_TIMEOUT" : "AGENT_CALL_ERROR",
|
|
665
|
+
message: isTimeout ? "Request timed out." : message,
|
|
666
|
+
recoverable: !isTimeout
|
|
667
|
+
});
|
|
668
|
+
} else {
|
|
669
|
+
console.error(`Error: ${isTimeout ? "Request timed out." : message}`);
|
|
670
|
+
}
|
|
671
|
+
process.exit(1);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// src/lib/agent-serve.ts
|
|
676
|
+
import { execFileSync, spawn } from "child_process";
|
|
677
|
+
import { createServer } from "http";
|
|
678
|
+
import { createL402Middleware, FiberRpcClient as FiberRpcClient2 } from "@fiber-pay/sdk";
|
|
679
|
+
import express from "express";
|
|
680
|
+
function getClientIp(req) {
|
|
681
|
+
const forwarded = req.headers["x-forwarded-for"];
|
|
682
|
+
const forwardedValue = Array.isArray(forwarded) ? forwarded[0] : forwarded;
|
|
683
|
+
if (typeof forwardedValue === "string" && forwardedValue.trim().length > 0) {
|
|
684
|
+
return forwardedValue.split(",")[0]?.trim() || "unknown";
|
|
685
|
+
}
|
|
686
|
+
return req.ip || req.socket.remoteAddress || "unknown";
|
|
687
|
+
}
|
|
688
|
+
function summarizeL402Status(req) {
|
|
689
|
+
if (req.l402?.valid) {
|
|
690
|
+
if (req.l402.paymentHash) {
|
|
691
|
+
return `payment-verified:${req.l402.paymentHash.slice(0, 14)}...`;
|
|
692
|
+
}
|
|
693
|
+
if (req.l402.preimage) {
|
|
694
|
+
return "payment-verified:preimage";
|
|
695
|
+
}
|
|
696
|
+
return "payment-verified";
|
|
697
|
+
}
|
|
698
|
+
return "no-l402-token";
|
|
699
|
+
}
|
|
700
|
+
function getRequestId(req) {
|
|
701
|
+
return req._fiberPayRequestId ?? 0;
|
|
702
|
+
}
|
|
703
|
+
function runAcpx(agent, prompt, options) {
|
|
704
|
+
return new Promise((resolve2) => {
|
|
705
|
+
const args = ["--format", "quiet"];
|
|
706
|
+
if (options.approveAll) {
|
|
707
|
+
args.push("--approve-all");
|
|
708
|
+
}
|
|
709
|
+
if (options.cwd) {
|
|
710
|
+
args.push("--cwd", options.cwd);
|
|
711
|
+
}
|
|
712
|
+
if (options.timeoutSeconds > 0) {
|
|
713
|
+
args.push("--timeout", String(options.timeoutSeconds));
|
|
714
|
+
}
|
|
715
|
+
args.push(agent, "exec", prompt);
|
|
716
|
+
const child = spawn("acpx", args, {
|
|
717
|
+
cwd: options.cwd || process.cwd(),
|
|
718
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
719
|
+
env: { ...process.env }
|
|
720
|
+
});
|
|
721
|
+
let stdout = "";
|
|
722
|
+
let stderr = "";
|
|
723
|
+
child.stdout.on("data", (data) => {
|
|
724
|
+
stdout += data.toString();
|
|
725
|
+
});
|
|
726
|
+
child.stderr.on("data", (data) => {
|
|
727
|
+
stderr += data.toString();
|
|
728
|
+
});
|
|
729
|
+
const killTimer = setTimeout(
|
|
730
|
+
() => {
|
|
731
|
+
child.kill("SIGKILL");
|
|
732
|
+
},
|
|
733
|
+
(options.timeoutSeconds + 30) * 1e3
|
|
734
|
+
);
|
|
735
|
+
child.on("close", (code) => {
|
|
736
|
+
clearTimeout(killTimer);
|
|
737
|
+
resolve2({ stdout, stderr, exitCode: code ?? 1 });
|
|
738
|
+
});
|
|
739
|
+
child.on("error", (err) => {
|
|
740
|
+
clearTimeout(killTimer);
|
|
741
|
+
resolve2({ stdout: "", stderr: err.message, exitCode: 1 });
|
|
742
|
+
});
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
async function runAgentServeCommand(config, options) {
|
|
746
|
+
const asJson = Boolean(options.json);
|
|
747
|
+
const port = parseInt(options.port, 10);
|
|
748
|
+
const host = options.host;
|
|
749
|
+
const priceCkb = parseFloat(options.price);
|
|
750
|
+
const expirySeconds = parseInt(options.expiry, 10);
|
|
751
|
+
const timeoutSeconds = parseInt(options.timeout, 10);
|
|
752
|
+
const rootKey = options.rootKey || process.env.L402_ROOT_KEY;
|
|
753
|
+
if (Number.isNaN(port) || port < 1 || port > 65535) {
|
|
754
|
+
if (asJson) {
|
|
755
|
+
printJsonError({
|
|
756
|
+
code: "AGENT_SERVE_INVALID_PORT",
|
|
757
|
+
message: `Invalid port: ${options.port}`,
|
|
758
|
+
recoverable: true,
|
|
759
|
+
suggestion: "Provide a valid port number between 1 and 65535."
|
|
760
|
+
});
|
|
761
|
+
} else {
|
|
762
|
+
console.error(`Error: Invalid port: ${options.port}`);
|
|
763
|
+
}
|
|
764
|
+
process.exit(1);
|
|
765
|
+
}
|
|
766
|
+
if (Number.isNaN(priceCkb) || priceCkb <= 0) {
|
|
767
|
+
if (asJson) {
|
|
768
|
+
printJsonError({
|
|
769
|
+
code: "AGENT_SERVE_INVALID_PRICE",
|
|
770
|
+
message: `Invalid price: ${options.price}`,
|
|
771
|
+
recoverable: true,
|
|
772
|
+
suggestion: "Provide a positive CKB amount, e.g. --price 0.1"
|
|
773
|
+
});
|
|
774
|
+
} else {
|
|
775
|
+
console.error(`Error: Invalid price: ${options.price}`);
|
|
776
|
+
}
|
|
777
|
+
process.exit(1);
|
|
778
|
+
}
|
|
779
|
+
if (!rootKey) {
|
|
780
|
+
if (asJson) {
|
|
781
|
+
printJsonError({
|
|
782
|
+
code: "AGENT_SERVE_MISSING_ROOT_KEY",
|
|
783
|
+
message: "L402 root key is required.",
|
|
784
|
+
recoverable: true,
|
|
785
|
+
suggestion: "Provide --root-key <64-hex-chars> or set L402_ROOT_KEY env var. Generate with: openssl rand -hex 32"
|
|
786
|
+
});
|
|
787
|
+
} else {
|
|
788
|
+
console.error("Error: L402 root key is required.");
|
|
789
|
+
console.error(" Provide --root-key <64-hex-chars> or set L402_ROOT_KEY env var.");
|
|
790
|
+
console.error(" Generate one with: openssl rand -hex 32");
|
|
791
|
+
}
|
|
792
|
+
process.exit(1);
|
|
793
|
+
}
|
|
794
|
+
try {
|
|
795
|
+
execFileSync("acpx", ["--version"], { stdio: "ignore" });
|
|
796
|
+
} catch {
|
|
797
|
+
if (asJson) {
|
|
798
|
+
printJsonError({
|
|
799
|
+
code: "AGENT_SERVE_ACPX_NOT_FOUND",
|
|
800
|
+
message: "acpx is not installed or not in PATH.",
|
|
801
|
+
recoverable: true,
|
|
802
|
+
suggestion: "Install acpx globally: npm install -g acpx"
|
|
803
|
+
});
|
|
804
|
+
} else {
|
|
805
|
+
console.error("Error: acpx is not installed or not in PATH.");
|
|
806
|
+
console.error(" Install it with: npm install -g acpx");
|
|
807
|
+
console.error(" See: https://github.com/openclaw/acpx");
|
|
808
|
+
}
|
|
809
|
+
process.exit(1);
|
|
810
|
+
}
|
|
811
|
+
const rpcClient = new FiberRpcClient2({
|
|
812
|
+
url: config.rpcUrl,
|
|
813
|
+
biscuitToken: config.rpcBiscuitToken
|
|
814
|
+
});
|
|
815
|
+
const currency = config.network === "mainnet" ? "Fibb" : "Fibt";
|
|
816
|
+
const app = express();
|
|
817
|
+
app.use(express.json());
|
|
818
|
+
let requestCounter = 0;
|
|
819
|
+
app.use((req, res, next) => {
|
|
820
|
+
const requestId = ++requestCounter;
|
|
821
|
+
req._fiberPayRequestId = requestId;
|
|
822
|
+
const startTime = Date.now();
|
|
823
|
+
const clientIp = getClientIp(req);
|
|
824
|
+
if (!asJson) {
|
|
825
|
+
console.log(
|
|
826
|
+
`[REQ ${requestId}] ${req.method} ${req.path} from ${clientIp} (auth=${req.headers.authorization ? "present" : "none"})`
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
res.on("finish", () => {
|
|
830
|
+
if (asJson) {
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
const durationMs = Date.now() - startTime;
|
|
834
|
+
const l402State = summarizeL402Status(req);
|
|
835
|
+
if (res.statusCode === 402 || res.statusCode === 401) {
|
|
836
|
+
console.log(
|
|
837
|
+
`[REQ ${requestId}] challenge-issued status=${res.statusCode} duration=${durationMs}ms`
|
|
838
|
+
);
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
console.log(
|
|
842
|
+
`[REQ ${requestId}] completed status=${res.statusCode} duration=${durationMs}ms l402=${l402State}`
|
|
843
|
+
);
|
|
844
|
+
});
|
|
845
|
+
next();
|
|
846
|
+
});
|
|
847
|
+
app.use(
|
|
848
|
+
createL402Middleware({
|
|
849
|
+
rootKey,
|
|
850
|
+
priceCkb,
|
|
851
|
+
expirySeconds,
|
|
852
|
+
rpcClient,
|
|
853
|
+
currency
|
|
854
|
+
})
|
|
855
|
+
);
|
|
856
|
+
app.post("/", async (req, res) => {
|
|
857
|
+
const requestId = getRequestId(req);
|
|
858
|
+
const prompt = req.body?.prompt;
|
|
859
|
+
if (!prompt || typeof prompt !== "string") {
|
|
860
|
+
if (!asJson) {
|
|
861
|
+
console.log(`[REQ ${requestId}] invalid request body: missing string prompt`);
|
|
862
|
+
}
|
|
863
|
+
res.status(400).json({
|
|
864
|
+
error: 'Missing or invalid "prompt" field in request body.'
|
|
865
|
+
});
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
const startTime = Date.now();
|
|
869
|
+
try {
|
|
870
|
+
if (!asJson) {
|
|
871
|
+
console.log(
|
|
872
|
+
`[REQ ${requestId}] payment accepted, invoking agent=${options.agent} promptChars=${prompt.length}`
|
|
873
|
+
);
|
|
874
|
+
}
|
|
875
|
+
const result = await runAcpx(options.agent, prompt, {
|
|
876
|
+
cwd: options.cwd,
|
|
877
|
+
approveAll: options.approveAll,
|
|
878
|
+
timeoutSeconds
|
|
879
|
+
});
|
|
880
|
+
const durationMs = Date.now() - startTime;
|
|
881
|
+
if (result.exitCode !== 0) {
|
|
882
|
+
if (!asJson) {
|
|
883
|
+
console.log(
|
|
884
|
+
`[REQ ${requestId}] agent failed exit=${result.exitCode} duration=${durationMs}ms`
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
res.status(502).json({
|
|
888
|
+
error: "Agent execution failed.",
|
|
889
|
+
agent: options.agent,
|
|
890
|
+
stderr: result.stderr.slice(0, 1e3),
|
|
891
|
+
durationMs
|
|
892
|
+
});
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
if (!asJson) {
|
|
896
|
+
console.log(`[REQ ${requestId}] agent completed duration=${durationMs}ms`);
|
|
897
|
+
}
|
|
898
|
+
res.json({
|
|
899
|
+
response: result.stdout.trim(),
|
|
900
|
+
agent: options.agent,
|
|
901
|
+
durationMs
|
|
902
|
+
});
|
|
903
|
+
} catch (error) {
|
|
904
|
+
if (!asJson) {
|
|
905
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
906
|
+
console.log(`[REQ ${requestId}] agent execution error: ${message}`);
|
|
907
|
+
}
|
|
908
|
+
res.status(500).json({
|
|
909
|
+
error: "Internal server error.",
|
|
910
|
+
message: error instanceof Error ? error.message : String(error)
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
});
|
|
914
|
+
app.get("/health", (_req, res) => {
|
|
915
|
+
res.json({ status: "ok", agent: options.agent });
|
|
916
|
+
});
|
|
917
|
+
const server = createServer(app);
|
|
918
|
+
await new Promise((resolve2, reject) => {
|
|
919
|
+
server.on("error", (err) => {
|
|
920
|
+
if (err.code === "EADDRINUSE") {
|
|
921
|
+
if (asJson) {
|
|
922
|
+
printJsonError({
|
|
923
|
+
code: "AGENT_SERVE_PORT_IN_USE",
|
|
924
|
+
message: `Port ${port} is already in use.`,
|
|
925
|
+
recoverable: true,
|
|
926
|
+
suggestion: "Use a different port with --port <port>."
|
|
927
|
+
});
|
|
928
|
+
} else {
|
|
929
|
+
console.error(`Error: Port ${port} is already in use.`);
|
|
930
|
+
}
|
|
931
|
+
process.exit(1);
|
|
932
|
+
}
|
|
933
|
+
reject(err);
|
|
934
|
+
});
|
|
935
|
+
server.listen(port, host, () => {
|
|
936
|
+
resolve2();
|
|
937
|
+
});
|
|
938
|
+
});
|
|
939
|
+
const listenUrl = `http://${host}:${port}`;
|
|
940
|
+
if (asJson) {
|
|
941
|
+
printJsonSuccess({
|
|
942
|
+
status: "running",
|
|
943
|
+
listen: listenUrl,
|
|
944
|
+
agent: options.agent,
|
|
945
|
+
priceCkb,
|
|
946
|
+
expirySeconds,
|
|
947
|
+
currency,
|
|
948
|
+
fiberRpcUrl: config.rpcUrl
|
|
949
|
+
});
|
|
950
|
+
} else {
|
|
951
|
+
console.log("Agent service started");
|
|
952
|
+
console.log(` Listen: ${listenUrl}`);
|
|
953
|
+
console.log(` Agent: ${options.agent}`);
|
|
954
|
+
console.log(` Price: ${priceCkb} CKB per request`);
|
|
955
|
+
console.log(` Expiry: ${expirySeconds}s`);
|
|
956
|
+
console.log(` Timeout: ${timeoutSeconds}s per agent call`);
|
|
957
|
+
console.log(` Currency: ${currency}`);
|
|
958
|
+
console.log(` Fiber RPC: ${config.rpcUrl}`);
|
|
959
|
+
console.log("");
|
|
960
|
+
console.log("Endpoint:");
|
|
961
|
+
console.log(` POST ${listenUrl}/ {"prompt": "your question"}`);
|
|
962
|
+
console.log("");
|
|
963
|
+
console.log("Press Ctrl+C to stop.");
|
|
964
|
+
}
|
|
965
|
+
const shutdown = () => {
|
|
966
|
+
if (!asJson) {
|
|
967
|
+
console.log("\nStopping agent service...");
|
|
968
|
+
}
|
|
969
|
+
server.close(() => {
|
|
970
|
+
if (asJson) {
|
|
971
|
+
printJsonSuccess({ status: "stopped" });
|
|
972
|
+
} else {
|
|
973
|
+
console.log("Agent service stopped.");
|
|
974
|
+
}
|
|
975
|
+
process.exit(0);
|
|
976
|
+
});
|
|
977
|
+
setTimeout(() => process.exit(0), 5e3);
|
|
978
|
+
};
|
|
979
|
+
process.on("SIGINT", shutdown);
|
|
980
|
+
process.on("SIGTERM", shutdown);
|
|
981
|
+
await new Promise(() => {
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
// src/commands/agent.ts
|
|
986
|
+
function createAgentCommand(config) {
|
|
987
|
+
const agent = new Command("agent").description("AI agent service with L402 payment");
|
|
988
|
+
agent.command("serve").description("Start an L402-gated AI agent HTTP service").requiredOption(
|
|
989
|
+
"--agent <name>",
|
|
990
|
+
"Agent to use (common values: codex, claude, opencode, gemini, pi; see acpx docs for full list)"
|
|
991
|
+
).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(
|
|
992
|
+
"after",
|
|
993
|
+
[
|
|
994
|
+
"",
|
|
995
|
+
"Examples:",
|
|
996
|
+
" fiber-pay agent serve --agent codex --price 0.1 --root-key <hex>",
|
|
997
|
+
" fiber-pay agent serve --agent claude --price 0.2 --port 8402"
|
|
998
|
+
].join("\n")
|
|
999
|
+
).action(async (options) => {
|
|
1000
|
+
await runAgentServeCommand(config, options);
|
|
1001
|
+
});
|
|
1002
|
+
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) => {
|
|
1003
|
+
await runAgentCallCommand(config, url, options);
|
|
1004
|
+
});
|
|
1005
|
+
return agent;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// src/commands/binary.ts
|
|
1009
|
+
import { DEFAULT_FIBER_VERSION, downloadFiberBinary } from "@fiber-pay/node";
|
|
1010
|
+
import { Command as Command2 } from "commander";
|
|
1011
|
+
|
|
1012
|
+
// src/lib/binary-path.ts
|
|
1013
|
+
import { dirname, join as join3 } from "path";
|
|
1014
|
+
import { BinaryManager, getFiberBinaryInfo } from "@fiber-pay/node";
|
|
1015
|
+
|
|
1016
|
+
// src/lib/node-runtime-daemon.ts
|
|
1017
|
+
import { spawnSync } from "child_process";
|
|
1018
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1019
|
+
function getCustomBinaryState(binaryPath) {
|
|
1020
|
+
const exists = existsSync3(binaryPath);
|
|
1021
|
+
if (!exists) {
|
|
1022
|
+
return { path: binaryPath, ready: false, version: "unknown" };
|
|
1023
|
+
}
|
|
1024
|
+
try {
|
|
1025
|
+
const result = spawnSync(binaryPath, ["--version"], { encoding: "utf-8" });
|
|
1026
|
+
if (result.status !== 0) {
|
|
1027
|
+
return { path: binaryPath, ready: false, version: "unknown" };
|
|
1028
|
+
}
|
|
1029
|
+
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
1030
|
+
const firstLine = output.split("\n").find((line) => line.trim().length > 0) ?? "unknown";
|
|
1031
|
+
return { path: binaryPath, ready: true, version: firstLine.trim() };
|
|
1032
|
+
} catch {
|
|
1033
|
+
return { path: binaryPath, ready: false, version: "unknown" };
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
function getBinaryVersion(binaryPath) {
|
|
1037
|
+
try {
|
|
1038
|
+
const result = spawnSync(binaryPath, ["--version"], { encoding: "utf-8" });
|
|
1039
|
+
if (result.status !== 0) {
|
|
1040
|
+
return "unknown";
|
|
1041
|
+
}
|
|
1042
|
+
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
1043
|
+
if (!output) {
|
|
1044
|
+
return "unknown";
|
|
1045
|
+
}
|
|
1046
|
+
const firstLine = output.split("\n").find((line) => line.trim().length > 0);
|
|
1047
|
+
return firstLine?.trim() ?? "unknown";
|
|
1048
|
+
} catch {
|
|
1049
|
+
return "unknown";
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
function getCliEntrypoint() {
|
|
1053
|
+
const entrypoint = process.argv[1];
|
|
1054
|
+
if (!entrypoint) {
|
|
1055
|
+
throw new Error("Unable to resolve CLI entrypoint path");
|
|
1056
|
+
}
|
|
1057
|
+
return entrypoint;
|
|
1058
|
+
}
|
|
1059
|
+
function startRuntimeDaemonFromNode(params) {
|
|
1060
|
+
const cliEntrypoint = getCliEntrypoint();
|
|
1061
|
+
const result = spawnSync(
|
|
1062
|
+
process.execPath,
|
|
1063
|
+
[
|
|
1064
|
+
cliEntrypoint,
|
|
1065
|
+
"--data-dir",
|
|
1066
|
+
params.dataDir,
|
|
1067
|
+
"--rpc-url",
|
|
1068
|
+
params.rpcUrl,
|
|
1069
|
+
"runtime",
|
|
1070
|
+
"start",
|
|
1071
|
+
"--daemon",
|
|
1072
|
+
"--fiber-rpc-url",
|
|
1073
|
+
params.rpcUrl,
|
|
1074
|
+
"--proxy-listen",
|
|
1075
|
+
params.proxyListen,
|
|
1076
|
+
"--state-file",
|
|
1077
|
+
params.stateFilePath,
|
|
1078
|
+
"--alert-logs-base-dir",
|
|
1079
|
+
params.alertLogsBaseDir,
|
|
1080
|
+
"--json"
|
|
1081
|
+
],
|
|
1082
|
+
{ encoding: "utf-8" }
|
|
1083
|
+
);
|
|
1084
|
+
if (result.status === 0) {
|
|
1085
|
+
return { ok: true };
|
|
1086
|
+
}
|
|
1087
|
+
const stderr = (result.stderr ?? "").trim();
|
|
1088
|
+
const stdout = (result.stdout ?? "").trim();
|
|
1089
|
+
const details = stderr || stdout || `exit code ${result.status ?? "unknown"}`;
|
|
1090
|
+
return { ok: false, message: details };
|
|
1091
|
+
}
|
|
1092
|
+
function stopRuntimeDaemonFromNode(params) {
|
|
1093
|
+
const cliEntrypoint = getCliEntrypoint();
|
|
1094
|
+
spawnSync(
|
|
1095
|
+
process.execPath,
|
|
1096
|
+
[
|
|
1097
|
+
cliEntrypoint,
|
|
1098
|
+
"--data-dir",
|
|
1099
|
+
params.dataDir,
|
|
1100
|
+
"--rpc-url",
|
|
1101
|
+
params.rpcUrl,
|
|
1102
|
+
"runtime",
|
|
1103
|
+
"stop",
|
|
1104
|
+
"--json"
|
|
1105
|
+
],
|
|
1106
|
+
{ encoding: "utf-8" }
|
|
1107
|
+
);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
// src/lib/binary-path.ts
|
|
1111
|
+
function getProfileBinaryInstallDir(dataDir) {
|
|
1112
|
+
return join3(dataDir, "bin");
|
|
1113
|
+
}
|
|
1114
|
+
function getProfileManagedBinaryPath(dataDir) {
|
|
1115
|
+
return new BinaryManager(getProfileBinaryInstallDir(dataDir)).getBinaryPath();
|
|
1116
|
+
}
|
|
1117
|
+
function validateConfiguredBinaryPath(binaryPath) {
|
|
1118
|
+
const value = binaryPath.trim();
|
|
1119
|
+
if (!value) {
|
|
1120
|
+
throw new Error("Configured binaryPath cannot be empty");
|
|
1121
|
+
}
|
|
1122
|
+
if (value.includes("\0")) {
|
|
1123
|
+
throw new Error("Configured binaryPath contains an invalid null byte");
|
|
1124
|
+
}
|
|
1125
|
+
return value;
|
|
1126
|
+
}
|
|
1127
|
+
function resolveBinaryPath(config) {
|
|
1128
|
+
const managedPath = getProfileManagedBinaryPath(config.dataDir);
|
|
1129
|
+
if (config.binaryPath) {
|
|
1130
|
+
const binaryPath2 = validateConfiguredBinaryPath(config.binaryPath);
|
|
1131
|
+
const installDir2 = dirname(binaryPath2);
|
|
1132
|
+
const expectedPath = new BinaryManager(installDir2).getBinaryPath();
|
|
1133
|
+
const managedByBinaryManager = expectedPath === binaryPath2;
|
|
1134
|
+
const source = binaryPath2 === managedPath ? "profile-managed" : "configured-path";
|
|
1135
|
+
return {
|
|
1136
|
+
binaryPath: binaryPath2,
|
|
1137
|
+
installDir: managedByBinaryManager ? installDir2 : null,
|
|
1138
|
+
managedPath,
|
|
1139
|
+
managedByBinaryManager,
|
|
1140
|
+
source
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
const installDir = getProfileBinaryInstallDir(config.dataDir);
|
|
1144
|
+
const binaryPath = managedPath;
|
|
1145
|
+
return {
|
|
1146
|
+
binaryPath,
|
|
1147
|
+
installDir,
|
|
1148
|
+
managedPath,
|
|
1149
|
+
managedByBinaryManager: true,
|
|
1150
|
+
source: "profile-managed"
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
function getBinaryManagerInstallDirOrThrow(resolvedBinary) {
|
|
1154
|
+
if (resolvedBinary.installDir) {
|
|
1155
|
+
return resolvedBinary.installDir;
|
|
485
1156
|
}
|
|
1157
|
+
throw new Error(
|
|
1158
|
+
`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.`
|
|
1159
|
+
);
|
|
1160
|
+
}
|
|
1161
|
+
async function getBinaryDetails(config) {
|
|
1162
|
+
const resolvedBinary = resolveBinaryPath(config);
|
|
1163
|
+
const info = resolvedBinary.installDir ? await getFiberBinaryInfo(resolvedBinary.installDir) : getCustomBinaryState(resolvedBinary.binaryPath);
|
|
1164
|
+
return { resolvedBinary, info };
|
|
486
1165
|
}
|
|
487
1166
|
|
|
488
1167
|
// src/commands/binary.ts
|
|
@@ -494,7 +1173,7 @@ function showProgress(progress) {
|
|
|
494
1173
|
}
|
|
495
1174
|
}
|
|
496
1175
|
function createBinaryCommand(config) {
|
|
497
|
-
const binary = new
|
|
1176
|
+
const binary = new Command2("binary").description("Fiber binary management");
|
|
498
1177
|
binary.command("download").option("--version <version>", "Fiber binary version", DEFAULT_FIBER_VERSION).option("--force", "Force re-download").option("--json").action(async (options) => {
|
|
499
1178
|
const resolvedBinary = resolveBinaryPath(config);
|
|
500
1179
|
let installDir;
|
|
@@ -552,146 +1231,14 @@ function createBinaryCommand(config) {
|
|
|
552
1231
|
|
|
553
1232
|
// src/commands/channel.ts
|
|
554
1233
|
import { randomUUID } from "crypto";
|
|
555
|
-
import { ckbToShannons as ckbToShannons2 } from "@fiber-pay/sdk";
|
|
556
|
-
import { Command as
|
|
1234
|
+
import { ckbToShannons as ckbToShannons2, nodeIdToPeerId } from "@fiber-pay/sdk";
|
|
1235
|
+
import { Command as Command3 } from "commander";
|
|
557
1236
|
|
|
558
1237
|
// src/lib/async.ts
|
|
559
1238
|
function sleep(ms) {
|
|
560
1239
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
561
1240
|
}
|
|
562
1241
|
|
|
563
|
-
// src/lib/rpc.ts
|
|
564
|
-
import { FiberRpcClient } from "@fiber-pay/sdk";
|
|
565
|
-
|
|
566
|
-
// src/lib/pid.ts
|
|
567
|
-
import { existsSync as existsSync2, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
568
|
-
import { join as join2 } from "path";
|
|
569
|
-
function getPidFilePath(dataDir) {
|
|
570
|
-
return join2(dataDir, "fiber.pid");
|
|
571
|
-
}
|
|
572
|
-
function writePidFile(dataDir, pid) {
|
|
573
|
-
writeFileSync(getPidFilePath(dataDir), String(pid));
|
|
574
|
-
}
|
|
575
|
-
function readPidFile(dataDir) {
|
|
576
|
-
const pidPath = getPidFilePath(dataDir);
|
|
577
|
-
if (!existsSync2(pidPath)) return null;
|
|
578
|
-
try {
|
|
579
|
-
return parseInt(readFileSync(pidPath, "utf-8").trim(), 10);
|
|
580
|
-
} catch {
|
|
581
|
-
return null;
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
function removePidFile(dataDir) {
|
|
585
|
-
const pidPath = getPidFilePath(dataDir);
|
|
586
|
-
if (existsSync2(pidPath)) {
|
|
587
|
-
unlinkSync(pidPath);
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
function isProcessRunning(pid) {
|
|
591
|
-
try {
|
|
592
|
-
process.kill(pid, 0);
|
|
593
|
-
return true;
|
|
594
|
-
} catch {
|
|
595
|
-
return false;
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
// src/lib/runtime-meta.ts
|
|
600
|
-
import { existsSync as existsSync3, readFileSync as readFileSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
601
|
-
import { join as join3 } from "path";
|
|
602
|
-
function getRuntimePidFilePath(dataDir) {
|
|
603
|
-
return join3(dataDir, "runtime.pid");
|
|
604
|
-
}
|
|
605
|
-
function getRuntimeMetaFilePath(dataDir) {
|
|
606
|
-
return join3(dataDir, "runtime.meta.json");
|
|
607
|
-
}
|
|
608
|
-
function writeRuntimePid(dataDir, pid) {
|
|
609
|
-
writeFileSync2(getRuntimePidFilePath(dataDir), String(pid));
|
|
610
|
-
}
|
|
611
|
-
function readRuntimePid(dataDir) {
|
|
612
|
-
const pidPath = getRuntimePidFilePath(dataDir);
|
|
613
|
-
if (!existsSync3(pidPath)) return null;
|
|
614
|
-
try {
|
|
615
|
-
return Number.parseInt(readFileSync2(pidPath, "utf-8").trim(), 10);
|
|
616
|
-
} catch {
|
|
617
|
-
return null;
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
function writeRuntimeMeta(dataDir, meta) {
|
|
621
|
-
writeFileSync2(getRuntimeMetaFilePath(dataDir), JSON.stringify(meta, null, 2));
|
|
622
|
-
}
|
|
623
|
-
function readRuntimeMeta(dataDir) {
|
|
624
|
-
const metaPath = getRuntimeMetaFilePath(dataDir);
|
|
625
|
-
if (!existsSync3(metaPath)) return null;
|
|
626
|
-
try {
|
|
627
|
-
return JSON.parse(readFileSync2(metaPath, "utf-8"));
|
|
628
|
-
} catch {
|
|
629
|
-
return null;
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
function removeRuntimeFiles(dataDir) {
|
|
633
|
-
const pidPath = getRuntimePidFilePath(dataDir);
|
|
634
|
-
const metaPath = getRuntimeMetaFilePath(dataDir);
|
|
635
|
-
if (existsSync3(pidPath)) {
|
|
636
|
-
unlinkSync2(pidPath);
|
|
637
|
-
}
|
|
638
|
-
if (existsSync3(metaPath)) {
|
|
639
|
-
unlinkSync2(metaPath);
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
// src/lib/rpc.ts
|
|
644
|
-
function normalizeUrl(url) {
|
|
645
|
-
try {
|
|
646
|
-
const normalized = new URL(url).toString();
|
|
647
|
-
return normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
|
|
648
|
-
} catch {
|
|
649
|
-
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
function resolveRuntimeProxyUrl(config) {
|
|
653
|
-
const runtimeMeta = readRuntimeMeta(config.dataDir);
|
|
654
|
-
const runtimePid = readRuntimePid(config.dataDir);
|
|
655
|
-
if (!runtimeMeta || !runtimePid || !isProcessRunning(runtimePid)) {
|
|
656
|
-
return void 0;
|
|
657
|
-
}
|
|
658
|
-
if (!runtimeMeta.proxyListen || !runtimeMeta.fiberRpcUrl) {
|
|
659
|
-
return void 0;
|
|
660
|
-
}
|
|
661
|
-
if (normalizeUrl(runtimeMeta.fiberRpcUrl) !== normalizeUrl(config.rpcUrl)) {
|
|
662
|
-
return void 0;
|
|
663
|
-
}
|
|
664
|
-
if (runtimeMeta.proxyListen.startsWith("http://") || runtimeMeta.proxyListen.startsWith("https://")) {
|
|
665
|
-
return runtimeMeta.proxyListen;
|
|
666
|
-
}
|
|
667
|
-
return `http://${runtimeMeta.proxyListen}`;
|
|
668
|
-
}
|
|
669
|
-
function createRpcClient(config) {
|
|
670
|
-
const resolved = resolveRpcEndpoint(config);
|
|
671
|
-
return new FiberRpcClient({
|
|
672
|
-
url: resolved.url,
|
|
673
|
-
biscuitToken: config.rpcBiscuitToken
|
|
674
|
-
});
|
|
675
|
-
}
|
|
676
|
-
function resolveRpcEndpoint(config) {
|
|
677
|
-
const runtimeProxyUrl = resolveRuntimeProxyUrl(config);
|
|
678
|
-
if (runtimeProxyUrl) {
|
|
679
|
-
return {
|
|
680
|
-
url: runtimeProxyUrl,
|
|
681
|
-
target: "runtime-proxy"
|
|
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;
|
|
693
|
-
}
|
|
694
|
-
|
|
695
1242
|
// src/lib/runtime-jobs.ts
|
|
696
1243
|
async function tryCreateRuntimePaymentJob(runtimeUrl, body) {
|
|
697
1244
|
try {
|
|
@@ -785,7 +1332,7 @@ async function executeRebalance(config, params) {
|
|
|
785
1332
|
}
|
|
786
1333
|
process.exit(1);
|
|
787
1334
|
}
|
|
788
|
-
const selfPubkey = (await rpc.nodeInfo()).
|
|
1335
|
+
const selfPubkey = (await rpc.nodeInfo()).pubkey;
|
|
789
1336
|
const amount = ckbToShannons(amountCkb);
|
|
790
1337
|
const isManual = manualHops.length > 0;
|
|
791
1338
|
let routeHopCount;
|
|
@@ -920,7 +1467,7 @@ function registerChannelRebalanceCommand(parent, config) {
|
|
|
920
1467
|
}
|
|
921
1468
|
process.exit(1);
|
|
922
1469
|
}
|
|
923
|
-
if (fromChannel.
|
|
1470
|
+
if (fromChannel.pubkey === toChannel.pubkey) {
|
|
924
1471
|
const message = "Source and target channels point to the same peer; choose two different channel peers.";
|
|
925
1472
|
if (json) {
|
|
926
1473
|
printJsonError({
|
|
@@ -931,7 +1478,7 @@ function registerChannelRebalanceCommand(parent, config) {
|
|
|
931
1478
|
details: {
|
|
932
1479
|
fromChannel: fromChannelId,
|
|
933
1480
|
toChannel: toChannelId,
|
|
934
|
-
peerId: fromChannel.
|
|
1481
|
+
peerId: fromChannel.pubkey
|
|
935
1482
|
}
|
|
936
1483
|
});
|
|
937
1484
|
} else {
|
|
@@ -939,24 +1486,21 @@ function registerChannelRebalanceCommand(parent, config) {
|
|
|
939
1486
|
}
|
|
940
1487
|
process.exit(1);
|
|
941
1488
|
}
|
|
942
|
-
const
|
|
943
|
-
const
|
|
944
|
-
const fromPubkey = pubkeyByPeerId.get(fromChannel.peer_id);
|
|
945
|
-
const toPubkey = pubkeyByPeerId.get(toChannel.peer_id);
|
|
1489
|
+
const fromPubkey = fromChannel.pubkey;
|
|
1490
|
+
const toPubkey = toChannel.pubkey;
|
|
946
1491
|
if (!fromPubkey || !toPubkey) {
|
|
947
|
-
const message = "Unable to resolve selected channel
|
|
1492
|
+
const message = "Unable to resolve selected channel pubkey for guided rebalance route.";
|
|
948
1493
|
if (json) {
|
|
949
1494
|
printJsonError({
|
|
950
1495
|
code: "CHANNEL_REBALANCE_INPUT_INVALID",
|
|
951
1496
|
message,
|
|
952
1497
|
recoverable: true,
|
|
953
|
-
suggestion: "Ensure
|
|
1498
|
+
suggestion: "Ensure channel details are up to date and retry guided mode, or use `payment rebalance --hops`.",
|
|
954
1499
|
details: {
|
|
955
1500
|
fromChannel: fromChannelId,
|
|
956
1501
|
toChannel: toChannelId,
|
|
957
|
-
fromPeerId: fromChannel.
|
|
958
|
-
toPeerId: toChannel.
|
|
959
|
-
resolvedPeers: peers.length
|
|
1502
|
+
fromPeerId: fromChannel.pubkey,
|
|
1503
|
+
toPeerId: toChannel.pubkey
|
|
960
1504
|
}
|
|
961
1505
|
});
|
|
962
1506
|
} else {
|
|
@@ -978,13 +1522,42 @@ function registerChannelRebalanceCommand(parent, config) {
|
|
|
978
1522
|
}
|
|
979
1523
|
|
|
980
1524
|
// src/commands/channel.ts
|
|
1525
|
+
function extractPeerIdFromMultiaddr(address) {
|
|
1526
|
+
const match = address.match(/\/p2p\/([^/]+)$/);
|
|
1527
|
+
return match?.[1];
|
|
1528
|
+
}
|
|
1529
|
+
async function resolvePeerPubkeyFromMultiaddr(rpc, address, timeoutMs) {
|
|
1530
|
+
const expectedPeerId = extractPeerIdFromMultiaddr(address);
|
|
1531
|
+
if (!expectedPeerId) {
|
|
1532
|
+
throw new Error("Invalid peer multiaddr: missing /p2p/<peerId> suffix");
|
|
1533
|
+
}
|
|
1534
|
+
const start = Date.now();
|
|
1535
|
+
while (Date.now() - start < timeoutMs) {
|
|
1536
|
+
const peers = await rpc.listPeers();
|
|
1537
|
+
for (const peer of peers.peers) {
|
|
1538
|
+
if (extractPeerIdFromMultiaddr(peer.address) === expectedPeerId) {
|
|
1539
|
+
return peer.pubkey;
|
|
1540
|
+
}
|
|
1541
|
+
try {
|
|
1542
|
+
if (await nodeIdToPeerId(peer.pubkey) === expectedPeerId) {
|
|
1543
|
+
return peer.pubkey;
|
|
1544
|
+
}
|
|
1545
|
+
} catch {
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
await sleep(500);
|
|
1549
|
+
}
|
|
1550
|
+
throw new Error(
|
|
1551
|
+
`connect_peer accepted but peer pubkey not resolved within ${Math.floor(timeoutMs / 1e3)}s (${expectedPeerId})`
|
|
1552
|
+
);
|
|
1553
|
+
}
|
|
981
1554
|
function createChannelCommand(config) {
|
|
982
|
-
const channel = new
|
|
983
|
-
channel.command("list").option("--state <state>").option("--peer <
|
|
1555
|
+
const channel = new Command3("channel").description("Channel lifecycle and status commands");
|
|
1556
|
+
channel.command("list").option("--state <state>").option("--peer <pubkey>").option("--include-closed").option("--json").action(async (options) => {
|
|
984
1557
|
const rpc = await createReadyRpcClient(config);
|
|
985
1558
|
const stateFilter = parseChannelState(options.state);
|
|
986
1559
|
const response = await rpc.listChannels(
|
|
987
|
-
options.peer ? {
|
|
1560
|
+
options.peer ? { pubkey: options.peer, include_closed: Boolean(options.includeClosed) } : { include_closed: Boolean(options.includeClosed) }
|
|
988
1561
|
);
|
|
989
1562
|
const channels = stateFilter ? response.channels.filter((item) => item.state.state_name === stateFilter) : response.channels;
|
|
990
1563
|
if (options.json) {
|
|
@@ -1017,7 +1590,7 @@ function createChannelCommand(config) {
|
|
|
1017
1590
|
printChannelDetailHuman(found);
|
|
1018
1591
|
}
|
|
1019
1592
|
});
|
|
1020
|
-
channel.command("watch").option("--interval <seconds>", "Refresh interval", "5").option("--timeout <seconds>").option("--on-timeout <behavior>", "fail | success", "fail").option("--channel <channelId>").option("--peer <
|
|
1593
|
+
channel.command("watch").option("--interval <seconds>", "Refresh interval", "5").option("--timeout <seconds>").option("--on-timeout <behavior>", "fail | success", "fail").option("--channel <channelId>").option("--peer <pubkey>").option("--state <state>").option("--until <state>").option("--include-closed").option("--no-clear").option("--json").action(async (options) => {
|
|
1021
1594
|
const intervalSeconds = parseInt(options.interval, 10);
|
|
1022
1595
|
const timeoutSeconds = options.timeout ? parseInt(options.timeout, 10) : void 0;
|
|
1023
1596
|
const onTimeout = String(options.onTimeout ?? "fail").trim().toLowerCase();
|
|
@@ -1046,7 +1619,7 @@ function createChannelCommand(config) {
|
|
|
1046
1619
|
const previousStates = /* @__PURE__ */ new Map();
|
|
1047
1620
|
while (true) {
|
|
1048
1621
|
const response = await rpc.listChannels(
|
|
1049
|
-
options.peer ? {
|
|
1622
|
+
options.peer ? { pubkey: options.peer, include_closed: Boolean(options.includeClosed) } : { include_closed: Boolean(options.includeClosed) }
|
|
1050
1623
|
);
|
|
1051
1624
|
let channels = response.channels;
|
|
1052
1625
|
if (options.channel) {
|
|
@@ -1128,7 +1701,7 @@ function createChannelCommand(config) {
|
|
|
1128
1701
|
await sleep(intervalSeconds * 1e3);
|
|
1129
1702
|
}
|
|
1130
1703
|
});
|
|
1131
|
-
channel.command("open").requiredOption("--peer <
|
|
1704
|
+
channel.command("open").requiredOption("--peer <pubkeyOrMultiaddr>").requiredOption("--funding <ckb>").option("--private").option(
|
|
1132
1705
|
"--idempotency-key <key>",
|
|
1133
1706
|
"Reuse this key only when retrying the exact same open intent"
|
|
1134
1707
|
).option("--json").action(async (options) => {
|
|
@@ -1136,21 +1709,23 @@ function createChannelCommand(config) {
|
|
|
1136
1709
|
const json = Boolean(options.json);
|
|
1137
1710
|
const peerInput = options.peer;
|
|
1138
1711
|
const fundingCkb = parseFloat(options.funding);
|
|
1139
|
-
let
|
|
1712
|
+
let peerPubkey = peerInput;
|
|
1140
1713
|
if (peerInput.includes("/")) {
|
|
1141
1714
|
await rpc.connectPeer({ address: peerInput });
|
|
1142
|
-
|
|
1143
|
-
if (peerIdMatch) peerId = peerIdMatch[1];
|
|
1715
|
+
peerPubkey = await resolvePeerPubkeyFromMultiaddr(rpc, peerInput, 8e3);
|
|
1144
1716
|
}
|
|
1145
|
-
|
|
1717
|
+
if (!peerPubkey.startsWith("0x")) {
|
|
1718
|
+
throw new Error(`Invalid peer pubkey: ${peerPubkey}`);
|
|
1719
|
+
}
|
|
1720
|
+
const idempotencyKey = typeof options.idempotencyKey === "string" && options.idempotencyKey.trim().length > 0 ? options.idempotencyKey.trim() : `open:${peerPubkey}:${randomUUID()}`;
|
|
1146
1721
|
const endpoint = resolveRpcEndpoint(config);
|
|
1147
1722
|
if (endpoint.target === "runtime-proxy") {
|
|
1148
1723
|
const created = await tryCreateRuntimeChannelJob(endpoint.url, {
|
|
1149
1724
|
params: {
|
|
1150
1725
|
action: "open",
|
|
1151
|
-
peerId,
|
|
1726
|
+
peerId: peerPubkey,
|
|
1152
1727
|
openChannelParams: {
|
|
1153
|
-
|
|
1728
|
+
pubkey: peerPubkey,
|
|
1154
1729
|
funding_amount: ckbToShannons2(fundingCkb),
|
|
1155
1730
|
public: !options.private
|
|
1156
1731
|
},
|
|
@@ -1165,7 +1740,7 @@ function createChannelCommand(config) {
|
|
|
1165
1740
|
const payload2 = {
|
|
1166
1741
|
jobId: created.id,
|
|
1167
1742
|
jobState: created.state,
|
|
1168
|
-
peer:
|
|
1743
|
+
peer: peerPubkey,
|
|
1169
1744
|
fundingCkb,
|
|
1170
1745
|
idempotencyKey
|
|
1171
1746
|
};
|
|
@@ -1183,11 +1758,15 @@ function createChannelCommand(config) {
|
|
|
1183
1758
|
}
|
|
1184
1759
|
}
|
|
1185
1760
|
const result = await rpc.openChannel({
|
|
1186
|
-
|
|
1761
|
+
pubkey: peerPubkey,
|
|
1187
1762
|
funding_amount: ckbToShannons2(fundingCkb),
|
|
1188
1763
|
public: !options.private
|
|
1189
1764
|
});
|
|
1190
|
-
const payload = {
|
|
1765
|
+
const payload = {
|
|
1766
|
+
temporaryChannelId: result.temporary_channel_id,
|
|
1767
|
+
peer: peerPubkey,
|
|
1768
|
+
fundingCkb
|
|
1769
|
+
};
|
|
1191
1770
|
if (json) {
|
|
1192
1771
|
printJsonSuccess(payload);
|
|
1193
1772
|
} else {
|
|
@@ -1400,14 +1979,14 @@ function createChannelCommand(config) {
|
|
|
1400
1979
|
}
|
|
1401
1980
|
|
|
1402
1981
|
// src/commands/config.ts
|
|
1403
|
-
import { existsSync as existsSync5, readdirSync, readFileSync as
|
|
1982
|
+
import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
1404
1983
|
import { homedir } from "os";
|
|
1405
1984
|
import { join as join5 } from "path";
|
|
1406
|
-
import { Command as
|
|
1985
|
+
import { Command as Command4 } from "commander";
|
|
1407
1986
|
import { parseDocument, stringify as yamlStringify } from "yaml";
|
|
1408
1987
|
|
|
1409
1988
|
// src/lib/config.ts
|
|
1410
|
-
import { existsSync as existsSync4, mkdirSync, readFileSync as
|
|
1989
|
+
import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
1411
1990
|
import { join as join4 } from "path";
|
|
1412
1991
|
|
|
1413
1992
|
// src/lib/config-templates.ts
|
|
@@ -1605,7 +2184,7 @@ function loadProfileConfig(dataDir) {
|
|
|
1605
2184
|
const profilePath = getProfilePath(dataDir);
|
|
1606
2185
|
if (!existsSync4(profilePath)) return void 0;
|
|
1607
2186
|
try {
|
|
1608
|
-
const raw =
|
|
2187
|
+
const raw = readFileSync4(profilePath, "utf-8");
|
|
1609
2188
|
return JSON.parse(raw);
|
|
1610
2189
|
} catch {
|
|
1611
2190
|
return void 0;
|
|
@@ -1661,7 +2240,7 @@ function getEffectiveConfig(explicitFlags2) {
|
|
|
1661
2240
|
const dataDirSource = explicitFlags2?.has("dataDir") ? "cli" : process.env.FIBER_DATA_DIR ? "env" : "default";
|
|
1662
2241
|
const configPath = getConfigPath(dataDir);
|
|
1663
2242
|
const configExists = existsSync4(configPath);
|
|
1664
|
-
const configContent = configExists ?
|
|
2243
|
+
const configContent = configExists ? readFileSync4(configPath, "utf-8") : void 0;
|
|
1665
2244
|
const profile = loadProfileConfig(dataDir);
|
|
1666
2245
|
const cliNetwork = explicitFlags2?.has("network") ? process.env.FIBER_NETWORK : void 0;
|
|
1667
2246
|
const envNetwork = !explicitFlags2?.has("network") ? process.env.FIBER_NETWORK : void 0;
|
|
@@ -1830,7 +2409,7 @@ function normalizeHexScalarsForMutation(content) {
|
|
|
1830
2409
|
);
|
|
1831
2410
|
}
|
|
1832
2411
|
function parseConfigDocumentForMutation(configPath) {
|
|
1833
|
-
const raw =
|
|
2412
|
+
const raw = readFileSync5(configPath, "utf-8");
|
|
1834
2413
|
const normalized = normalizeHexScalarsForMutation(raw);
|
|
1835
2414
|
return parseDocument(normalized, {
|
|
1836
2415
|
keepSourceTokens: true
|
|
@@ -1866,7 +2445,7 @@ function collectConfigPaths(value, prefix = "") {
|
|
|
1866
2445
|
return prefix ? [prefix] : [];
|
|
1867
2446
|
}
|
|
1868
2447
|
function createConfigCommand(_config) {
|
|
1869
|
-
const config = new
|
|
2448
|
+
const config = new Command4("config").description("Single source configuration management");
|
|
1870
2449
|
config.command("init").option(
|
|
1871
2450
|
"--data-dir <path>",
|
|
1872
2451
|
"Target data directory (overrides FIBER_DATA_DIR for this command)"
|
|
@@ -1960,7 +2539,7 @@ function createConfigCommand(_config) {
|
|
|
1960
2539
|
}
|
|
1961
2540
|
process.exit(1);
|
|
1962
2541
|
}
|
|
1963
|
-
const content =
|
|
2542
|
+
const content = readFileSync5(effective.config.configPath, "utf-8");
|
|
1964
2543
|
const fileNetwork = parseNetworkFromConfig(content) || "unknown";
|
|
1965
2544
|
if (options.json) {
|
|
1966
2545
|
printJsonSuccess({
|
|
@@ -2101,7 +2680,7 @@ function createConfigCommand(_config) {
|
|
|
2101
2680
|
}
|
|
2102
2681
|
}
|
|
2103
2682
|
});
|
|
2104
|
-
const profile = new
|
|
2683
|
+
const profile = new Command4("profile").description(
|
|
2105
2684
|
"Manage profile.json settings (CLI-only overrides)"
|
|
2106
2685
|
);
|
|
2107
2686
|
const PROFILE_KEYS = ["binaryPath", "keyPassword", "runtimeProxyListen"];
|
|
@@ -2231,7 +2810,7 @@ function createConfigCommand(_config) {
|
|
|
2231
2810
|
|
|
2232
2811
|
// src/commands/graph.ts
|
|
2233
2812
|
import { shannonsToCkb as shannonsToCkb3, toHex as toHex2 } from "@fiber-pay/sdk";
|
|
2234
|
-
import { Command as
|
|
2813
|
+
import { Command as Command5 } from "commander";
|
|
2235
2814
|
function printGraphNodeListHuman(nodes) {
|
|
2236
2815
|
if (nodes.length === 0) {
|
|
2237
2816
|
console.log("No nodes found in the graph.");
|
|
@@ -2242,7 +2821,7 @@ function printGraphNodeListHuman(nodes) {
|
|
|
2242
2821
|
console.log("NODE ID ALIAS VERSION MIN FUNDING AGE");
|
|
2243
2822
|
console.log("---------------------------------------------------------------------------------");
|
|
2244
2823
|
for (const node of nodes) {
|
|
2245
|
-
const nodeId = truncateMiddle(node.
|
|
2824
|
+
const nodeId = truncateMiddle(node.pubkey, 10, 8).padEnd(22, " ");
|
|
2246
2825
|
const alias = (node.node_name || "(unnamed)").slice(0, 20).padEnd(20, " ");
|
|
2247
2826
|
const version = (node.version || "?").slice(0, 10).padEnd(10, " ");
|
|
2248
2827
|
const minFunding = shannonsToCkb3(node.auto_accept_min_ckb_funding_amount).toString().padStart(12, " ");
|
|
@@ -2273,7 +2852,7 @@ function printGraphChannelListHuman(channels) {
|
|
|
2273
2852
|
}
|
|
2274
2853
|
}
|
|
2275
2854
|
function createGraphCommand(config) {
|
|
2276
|
-
const graph = new
|
|
2855
|
+
const graph = new Command5("graph").description("Network graph queries (nodes & channels)");
|
|
2277
2856
|
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) => {
|
|
2278
2857
|
const rpc = await createReadyRpcClient(config);
|
|
2279
2858
|
const limit = toHex2(BigInt(parseInt(options.limit, 10)));
|
|
@@ -2313,9 +2892,9 @@ Next cursor: ${result.last_cursor}`);
|
|
|
2313
2892
|
|
|
2314
2893
|
// src/commands/invoice.ts
|
|
2315
2894
|
import { ckbToShannons as ckbToShannons3, randomBytes32, shannonsToCkb as shannonsToCkb4, toHex as toHex3 } from "@fiber-pay/sdk";
|
|
2316
|
-
import { Command as
|
|
2895
|
+
import { Command as Command6 } from "commander";
|
|
2317
2896
|
function createInvoiceCommand(config) {
|
|
2318
|
-
const invoice = new
|
|
2897
|
+
const invoice = new Command6("invoice").description("Invoice lifecycle and status commands");
|
|
2319
2898
|
invoice.command("create").argument("[amount]").option("--amount <ckb>").option("--description <text>").option("--expiry <minutes>").option("--json").action(async (amountArg, options) => {
|
|
2320
2899
|
const rpc = await createReadyRpcClient(config);
|
|
2321
2900
|
const json = Boolean(options.json);
|
|
@@ -2531,7 +3110,7 @@ function createInvoiceCommand(config) {
|
|
|
2531
3110
|
|
|
2532
3111
|
// src/commands/job.ts
|
|
2533
3112
|
import { existsSync as existsSync7 } from "fs";
|
|
2534
|
-
import { Command as
|
|
3113
|
+
import { Command as Command7 } from "commander";
|
|
2535
3114
|
|
|
2536
3115
|
// src/lib/log-files.ts
|
|
2537
3116
|
import {
|
|
@@ -2570,8 +3149,8 @@ var LogWriter = class {
|
|
|
2570
3149
|
if (!this.stream) {
|
|
2571
3150
|
throw new Error("Failed to create write stream");
|
|
2572
3151
|
}
|
|
3152
|
+
const stream = this.stream;
|
|
2573
3153
|
return new Promise((resolve2, reject) => {
|
|
2574
|
-
const stream = this.stream;
|
|
2575
3154
|
if (this.waitingForDrain) {
|
|
2576
3155
|
const onDrain = () => {
|
|
2577
3156
|
this.waitingForDrain = false;
|
|
@@ -2885,7 +3464,7 @@ async function readAppendedLines(filePath, offset, remainder = "") {
|
|
|
2885
3464
|
|
|
2886
3465
|
// src/commands/job.ts
|
|
2887
3466
|
function createJobCommand(config) {
|
|
2888
|
-
const job = new
|
|
3467
|
+
const job = new Command7("job").description("Runtime job management commands");
|
|
2889
3468
|
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) => {
|
|
2890
3469
|
const json = Boolean(options.json);
|
|
2891
3470
|
const runtimeUrl = getRuntimeUrlOrExit(config, json);
|
|
@@ -3215,11 +3794,163 @@ ${title}: ${filePath}`);
|
|
|
3215
3794
|
}
|
|
3216
3795
|
}
|
|
3217
3796
|
|
|
3797
|
+
// src/commands/l402.ts
|
|
3798
|
+
import { Command as Command8 } from "commander";
|
|
3799
|
+
|
|
3800
|
+
// src/lib/l402-proxy.ts
|
|
3801
|
+
import { createServer as createServer2 } from "http";
|
|
3802
|
+
import { createL402Middleware as createL402Middleware2, FiberRpcClient as FiberRpcClient3 } from "@fiber-pay/sdk";
|
|
3803
|
+
import express2 from "express";
|
|
3804
|
+
import { createProxyMiddleware } from "http-proxy-middleware";
|
|
3805
|
+
async function runL402ProxyCommand(config, options) {
|
|
3806
|
+
const asJson = Boolean(options.json);
|
|
3807
|
+
const port = parseInt(options.port, 10);
|
|
3808
|
+
const host = options.host;
|
|
3809
|
+
const priceCkb = parseFloat(options.price);
|
|
3810
|
+
const expirySeconds = parseInt(options.expiry, 10);
|
|
3811
|
+
const rootKey = options.rootKey || process.env.L402_ROOT_KEY;
|
|
3812
|
+
if (Number.isNaN(port) || port < 1 || port > 65535) {
|
|
3813
|
+
if (asJson) {
|
|
3814
|
+
printJsonError({
|
|
3815
|
+
code: "L402_PROXY_INVALID_PORT",
|
|
3816
|
+
message: `Invalid port: ${options.port}`,
|
|
3817
|
+
recoverable: true,
|
|
3818
|
+
suggestion: "Provide a valid port number between 1 and 65535."
|
|
3819
|
+
});
|
|
3820
|
+
} else {
|
|
3821
|
+
console.error(`Error: Invalid port: ${options.port}`);
|
|
3822
|
+
}
|
|
3823
|
+
process.exit(1);
|
|
3824
|
+
}
|
|
3825
|
+
if (Number.isNaN(priceCkb) || priceCkb <= 0) {
|
|
3826
|
+
if (asJson) {
|
|
3827
|
+
printJsonError({
|
|
3828
|
+
code: "L402_PROXY_INVALID_PRICE",
|
|
3829
|
+
message: `Invalid price: ${options.price}`,
|
|
3830
|
+
recoverable: true,
|
|
3831
|
+
suggestion: "Provide a positive CKB amount, e.g. --price 0.1"
|
|
3832
|
+
});
|
|
3833
|
+
} else {
|
|
3834
|
+
console.error(`Error: Invalid price: ${options.price}`);
|
|
3835
|
+
}
|
|
3836
|
+
process.exit(1);
|
|
3837
|
+
}
|
|
3838
|
+
if (!rootKey) {
|
|
3839
|
+
if (asJson) {
|
|
3840
|
+
printJsonError({
|
|
3841
|
+
code: "L402_PROXY_MISSING_ROOT_KEY",
|
|
3842
|
+
message: "L402 root key is required.",
|
|
3843
|
+
recoverable: true,
|
|
3844
|
+
suggestion: "Provide --root-key <64-hex-chars> or set L402_ROOT_KEY environment variable. Generate one with: openssl rand -hex 32"
|
|
3845
|
+
});
|
|
3846
|
+
} else {
|
|
3847
|
+
console.error("Error: L402 root key is required.");
|
|
3848
|
+
console.error(" Provide --root-key <64-hex-chars> or set L402_ROOT_KEY env var.");
|
|
3849
|
+
console.error(" Generate one with: openssl rand -hex 32");
|
|
3850
|
+
}
|
|
3851
|
+
process.exit(1);
|
|
3852
|
+
}
|
|
3853
|
+
const rpcClient = new FiberRpcClient3({
|
|
3854
|
+
url: config.rpcUrl,
|
|
3855
|
+
biscuitToken: config.rpcBiscuitToken
|
|
3856
|
+
});
|
|
3857
|
+
const currency = config.network === "mainnet" ? "Fibb" : "Fibt";
|
|
3858
|
+
const app = express2();
|
|
3859
|
+
app.use(
|
|
3860
|
+
createL402Middleware2({
|
|
3861
|
+
rootKey,
|
|
3862
|
+
priceCkb,
|
|
3863
|
+
expirySeconds,
|
|
3864
|
+
rpcClient,
|
|
3865
|
+
currency
|
|
3866
|
+
})
|
|
3867
|
+
);
|
|
3868
|
+
app.use(
|
|
3869
|
+
createProxyMiddleware({
|
|
3870
|
+
target: options.target,
|
|
3871
|
+
changeOrigin: true
|
|
3872
|
+
})
|
|
3873
|
+
);
|
|
3874
|
+
const server = createServer2(app);
|
|
3875
|
+
await new Promise((resolve2, reject) => {
|
|
3876
|
+
server.on("error", (err) => {
|
|
3877
|
+
if (err.code === "EADDRINUSE") {
|
|
3878
|
+
if (asJson) {
|
|
3879
|
+
printJsonError({
|
|
3880
|
+
code: "L402_PROXY_PORT_IN_USE",
|
|
3881
|
+
message: `Port ${port} is already in use.`,
|
|
3882
|
+
recoverable: true,
|
|
3883
|
+
suggestion: `Use a different port with --port <port>.`
|
|
3884
|
+
});
|
|
3885
|
+
} else {
|
|
3886
|
+
console.error(`Error: Port ${port} is already in use.`);
|
|
3887
|
+
}
|
|
3888
|
+
process.exit(1);
|
|
3889
|
+
}
|
|
3890
|
+
reject(err);
|
|
3891
|
+
});
|
|
3892
|
+
server.listen(port, host, () => {
|
|
3893
|
+
resolve2();
|
|
3894
|
+
});
|
|
3895
|
+
});
|
|
3896
|
+
const listenUrl = `http://${host}:${port}`;
|
|
3897
|
+
if (asJson) {
|
|
3898
|
+
printJsonSuccess({
|
|
3899
|
+
status: "running",
|
|
3900
|
+
listen: listenUrl,
|
|
3901
|
+
target: options.target,
|
|
3902
|
+
priceCkb,
|
|
3903
|
+
expirySeconds,
|
|
3904
|
+
currency,
|
|
3905
|
+
fiberRpcUrl: config.rpcUrl
|
|
3906
|
+
});
|
|
3907
|
+
} else {
|
|
3908
|
+
console.log("L402 proxy started");
|
|
3909
|
+
console.log(` Listen: ${listenUrl}`);
|
|
3910
|
+
console.log(` Target: ${options.target}`);
|
|
3911
|
+
console.log(` Price: ${priceCkb} CKB per request`);
|
|
3912
|
+
console.log(` Expiry: ${expirySeconds}s`);
|
|
3913
|
+
console.log(` Currency: ${currency}`);
|
|
3914
|
+
console.log(` Fiber RPC: ${config.rpcUrl}`);
|
|
3915
|
+
console.log("");
|
|
3916
|
+
console.log("Press Ctrl+C to stop.");
|
|
3917
|
+
}
|
|
3918
|
+
const shutdown = () => {
|
|
3919
|
+
if (!asJson) {
|
|
3920
|
+
console.log("\nStopping L402 proxy...");
|
|
3921
|
+
}
|
|
3922
|
+
server.close(() => {
|
|
3923
|
+
if (asJson) {
|
|
3924
|
+
printJsonSuccess({ status: "stopped" });
|
|
3925
|
+
} else {
|
|
3926
|
+
console.log("L402 proxy stopped.");
|
|
3927
|
+
}
|
|
3928
|
+
process.exit(0);
|
|
3929
|
+
});
|
|
3930
|
+
setTimeout(() => {
|
|
3931
|
+
process.exit(0);
|
|
3932
|
+
}, 5e3);
|
|
3933
|
+
};
|
|
3934
|
+
process.on("SIGINT", shutdown);
|
|
3935
|
+
process.on("SIGTERM", shutdown);
|
|
3936
|
+
await new Promise(() => {
|
|
3937
|
+
});
|
|
3938
|
+
}
|
|
3939
|
+
|
|
3940
|
+
// src/commands/l402.ts
|
|
3941
|
+
function createL402Command(config) {
|
|
3942
|
+
const l402 = new Command8("l402").description("L402 payment protocol commands");
|
|
3943
|
+
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) => {
|
|
3944
|
+
await runL402ProxyCommand(config, options);
|
|
3945
|
+
});
|
|
3946
|
+
return l402;
|
|
3947
|
+
}
|
|
3948
|
+
|
|
3218
3949
|
// src/commands/logs.ts
|
|
3219
3950
|
import { existsSync as existsSync8, statSync as statSync2 } from "fs";
|
|
3220
3951
|
import { join as join8 } from "path";
|
|
3221
3952
|
import { formatRuntimeAlert } from "@fiber-pay/runtime";
|
|
3222
|
-
import { Command as
|
|
3953
|
+
import { Command as Command9 } from "commander";
|
|
3223
3954
|
var ALLOWED_SOURCES = /* @__PURE__ */ new Set([
|
|
3224
3955
|
"all",
|
|
3225
3956
|
"runtime",
|
|
@@ -3252,7 +3983,7 @@ function coerceJsonLineForOutput(source, line) {
|
|
|
3252
3983
|
return parseRuntimeAlertLine(line) ?? line;
|
|
3253
3984
|
}
|
|
3254
3985
|
function createLogsCommand(config) {
|
|
3255
|
-
return new
|
|
3986
|
+
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) => {
|
|
3256
3987
|
const json = Boolean(options.json);
|
|
3257
3988
|
const follow = Boolean(options.follow);
|
|
3258
3989
|
const listDates = Boolean(options.listDates);
|
|
@@ -3507,7 +4238,7 @@ function inferDateFromPaths(paths) {
|
|
|
3507
4238
|
}
|
|
3508
4239
|
|
|
3509
4240
|
// src/commands/node.ts
|
|
3510
|
-
import { Command as
|
|
4241
|
+
import { Command as Command10 } from "commander";
|
|
3511
4242
|
|
|
3512
4243
|
// src/lib/node-info.ts
|
|
3513
4244
|
async function runNodeInfoCommand(config, options) {
|
|
@@ -3520,7 +4251,7 @@ async function runNodeInfoCommand(config, options) {
|
|
|
3520
4251
|
console.log("\u2705 Node info retrieved");
|
|
3521
4252
|
console.log(` Version: ${nodeInfo.version}`);
|
|
3522
4253
|
console.log(` Commit: ${nodeInfo.commit_hash}`);
|
|
3523
|
-
console.log(` Node
|
|
4254
|
+
console.log(` Node Pubkey: ${nodeInfo.pubkey}`);
|
|
3524
4255
|
if (nodeInfo.features.length > 0) {
|
|
3525
4256
|
console.log(" Features:");
|
|
3526
4257
|
for (const feature of nodeInfo.features) {
|
|
@@ -3572,11 +4303,7 @@ async function runNodeNetworkCommand(config, options) {
|
|
|
3572
4303
|
]);
|
|
3573
4304
|
const graphNodesMap = /* @__PURE__ */ new Map();
|
|
3574
4305
|
for (const node of graphNodes.nodes) {
|
|
3575
|
-
graphNodesMap.set(node.
|
|
3576
|
-
}
|
|
3577
|
-
const peerIdToNodeIdMap = /* @__PURE__ */ new Map();
|
|
3578
|
-
for (const peer of localPeers.peers) {
|
|
3579
|
-
peerIdToNodeIdMap.set(peer.peer_id, peer.pubkey);
|
|
4306
|
+
graphNodesMap.set(node.pubkey, node);
|
|
3580
4307
|
}
|
|
3581
4308
|
const graphChannelsMap = /* @__PURE__ */ new Map();
|
|
3582
4309
|
for (const channel of graphChannels.channels) {
|
|
@@ -3590,8 +4317,7 @@ async function runNodeNetworkCommand(config, options) {
|
|
|
3590
4317
|
nodeInfo: graphNodesMap.get(peer.pubkey)
|
|
3591
4318
|
}));
|
|
3592
4319
|
const enrichedChannels = localChannels.channels.map((channel) => {
|
|
3593
|
-
const
|
|
3594
|
-
const peerNodeInfo = graphNodesMap.get(nodeId);
|
|
4320
|
+
const peerNodeInfo = graphNodesMap.get(channel.pubkey);
|
|
3595
4321
|
let graphChannelInfo;
|
|
3596
4322
|
if (channel.channel_outpoint) {
|
|
3597
4323
|
const outpointKey = `${channel.channel_outpoint.tx_hash}:${channel.channel_outpoint.index}`;
|
|
@@ -3610,7 +4336,7 @@ async function runNodeNetworkCommand(config, options) {
|
|
|
3610
4336
|
}, 0n);
|
|
3611
4337
|
const totalChannelCapacity = formatShannonsAsCkb(totalChannelCapacityShannons, 1);
|
|
3612
4338
|
const networkData = {
|
|
3613
|
-
localNodeId: nodeInfo.
|
|
4339
|
+
localNodeId: nodeInfo.pubkey,
|
|
3614
4340
|
peers: enrichedPeers,
|
|
3615
4341
|
channels: enrichedChannels,
|
|
3616
4342
|
graphNodes: graphNodes.nodes,
|
|
@@ -3637,16 +4363,16 @@ function printNodeNetworkHuman(data) {
|
|
|
3637
4363
|
console.log("");
|
|
3638
4364
|
if (data.peers.length > 0) {
|
|
3639
4365
|
console.log("Peers:");
|
|
3640
|
-
console.log("
|
|
4366
|
+
console.log(" PUBKEY ALIAS ADDRESS VERSION");
|
|
3641
4367
|
console.log(
|
|
3642
4368
|
" --------------------------------------------------------------------------------"
|
|
3643
4369
|
);
|
|
3644
4370
|
for (const peer of data.peers) {
|
|
3645
|
-
const
|
|
4371
|
+
const pubkey = truncateMiddle(peer.pubkey, 10, 8).padEnd(22, " ");
|
|
3646
4372
|
const alias = sanitizeForTerminal(peer.nodeInfo?.node_name || "(unnamed)").slice(0, 20).padEnd(20, " ");
|
|
3647
4373
|
const address = truncateMiddle(sanitizeForTerminal(peer.address), 15, 8).padEnd(25, " ");
|
|
3648
4374
|
const version = sanitizeForTerminal(peer.nodeInfo?.version || "?").slice(0, 8).padEnd(8, " ");
|
|
3649
|
-
console.log(` ${
|
|
4375
|
+
console.log(` ${pubkey} ${alias} ${address} ${version}`);
|
|
3650
4376
|
}
|
|
3651
4377
|
console.log("");
|
|
3652
4378
|
}
|
|
@@ -3671,7 +4397,7 @@ function printNodeNetworkHuman(data) {
|
|
|
3671
4397
|
}
|
|
3672
4398
|
|
|
3673
4399
|
// src/lib/node-start.ts
|
|
3674
|
-
import { spawn } from "child_process";
|
|
4400
|
+
import { spawn as spawn2 } from "child_process";
|
|
3675
4401
|
import { mkdirSync as mkdirSync3 } from "fs";
|
|
3676
4402
|
import { join as join9 } from "path";
|
|
3677
4403
|
import {
|
|
@@ -3682,12 +4408,12 @@ import {
|
|
|
3682
4408
|
import { startRuntimeService } from "@fiber-pay/runtime";
|
|
3683
4409
|
|
|
3684
4410
|
// src/lib/bootnode.ts
|
|
3685
|
-
import { existsSync as existsSync9, readFileSync as
|
|
4411
|
+
import { existsSync as existsSync9, readFileSync as readFileSync6 } from "fs";
|
|
3686
4412
|
import { parse as parseYaml } from "yaml";
|
|
3687
4413
|
function extractBootnodeAddrs(configFilePath) {
|
|
3688
4414
|
if (!existsSync9(configFilePath)) return [];
|
|
3689
4415
|
try {
|
|
3690
|
-
const content =
|
|
4416
|
+
const content = readFileSync6(configFilePath, "utf-8");
|
|
3691
4417
|
const doc = parseYaml(content);
|
|
3692
4418
|
const addrs = doc?.fiber?.bootnode_addrs;
|
|
3693
4419
|
if (!Array.isArray(addrs)) return [];
|
|
@@ -3942,7 +4668,7 @@ async function runNodeStartCommand(config, options) {
|
|
|
3942
4668
|
process.exit(1);
|
|
3943
4669
|
}
|
|
3944
4670
|
const childArgs = process.argv.slice(2).filter((arg) => arg !== "--daemon");
|
|
3945
|
-
const child =
|
|
4671
|
+
const child = spawn2(process.execPath, [cliEntrypoint, ...childArgs], {
|
|
3946
4672
|
detached: true,
|
|
3947
4673
|
stdio: "ignore",
|
|
3948
4674
|
cwd: process.cwd(),
|
|
@@ -4331,7 +5057,7 @@ async function runNodeStartCommand(config, options) {
|
|
|
4331
5057
|
flushTimeout = setTimeout(() => reject(new Error("Flush timeout")), 5e3);
|
|
4332
5058
|
})
|
|
4333
5059
|
]);
|
|
4334
|
-
} catch (
|
|
5060
|
+
} catch (_err) {
|
|
4335
5061
|
if (!json) {
|
|
4336
5062
|
console.log("\u26A0\uFE0F Log flush timed out, continuing shutdown...");
|
|
4337
5063
|
}
|
|
@@ -4402,7 +5128,7 @@ import {
|
|
|
4402
5128
|
buildMultiaddrFromNodeId,
|
|
4403
5129
|
buildMultiaddrFromRpcUrl,
|
|
4404
5130
|
ChannelState as ChannelState2,
|
|
4405
|
-
nodeIdToPeerId,
|
|
5131
|
+
nodeIdToPeerId as nodeIdToPeerId2,
|
|
4406
5132
|
scriptToAddress
|
|
4407
5133
|
} from "@fiber-pay/sdk";
|
|
4408
5134
|
|
|
@@ -4596,21 +5322,21 @@ async function runNodeStatusCommand(config, options) {
|
|
|
4596
5322
|
const nodeInfo = await rpc.nodeInfo();
|
|
4597
5323
|
const channels = await rpc.listChannels({ include_closed: false });
|
|
4598
5324
|
rpcResponsive = true;
|
|
4599
|
-
nodeId = nodeInfo.
|
|
5325
|
+
nodeId = nodeInfo.pubkey;
|
|
4600
5326
|
nodeName = nodeInfo.node_name;
|
|
4601
5327
|
addresses = nodeInfo.addresses;
|
|
4602
5328
|
chainHash = nodeInfo.chain_hash;
|
|
4603
5329
|
version = nodeInfo.version;
|
|
4604
5330
|
peersCount = parseInt(nodeInfo.peers_count, 16);
|
|
4605
5331
|
try {
|
|
4606
|
-
peerId = await
|
|
5332
|
+
peerId = await nodeIdToPeerId2(nodeInfo.pubkey);
|
|
4607
5333
|
} catch (error) {
|
|
4608
5334
|
peerIdError = error instanceof Error ? error.message : String(error);
|
|
4609
5335
|
}
|
|
4610
5336
|
const baseAddress = nodeInfo.addresses[0];
|
|
4611
5337
|
if (baseAddress) {
|
|
4612
5338
|
try {
|
|
4613
|
-
multiaddr = await buildMultiaddrFromNodeId(baseAddress, nodeInfo.
|
|
5339
|
+
multiaddr = await buildMultiaddrFromNodeId(baseAddress, nodeInfo.pubkey);
|
|
4614
5340
|
} catch (error) {
|
|
4615
5341
|
multiaddrError = error instanceof Error ? error.message : String(error);
|
|
4616
5342
|
}
|
|
@@ -5148,7 +5874,12 @@ async function runMigrationAndReport(opts) {
|
|
|
5148
5874
|
}
|
|
5149
5875
|
process.exit(0);
|
|
5150
5876
|
}
|
|
5151
|
-
|
|
5877
|
+
const precheckUnsupported = migrationCheck.precheckUnsupported;
|
|
5878
|
+
if (precheckUnsupported && forceMigrateAttempt && !json) {
|
|
5879
|
+
console.log("\u26A0\uFE0F Migration pre-check is unavailable for this fnn-migrate version.");
|
|
5880
|
+
console.log(" --force-migrate is set, so migration will be attempted directly.");
|
|
5881
|
+
}
|
|
5882
|
+
if (!migrationCheck.needed && !(precheckUnsupported && forceMigrateAttempt)) {
|
|
5152
5883
|
if (!json) console.log(" Store is compatible, no migration needed.");
|
|
5153
5884
|
return normalizeMigrationCheck(migrationCheck);
|
|
5154
5885
|
}
|
|
@@ -5226,7 +5957,7 @@ async function runMigrationAndReport(opts) {
|
|
|
5226
5957
|
|
|
5227
5958
|
// src/commands/node.ts
|
|
5228
5959
|
function createNodeCommand(config) {
|
|
5229
|
-
const node = new
|
|
5960
|
+
const node = new Command10("node").description("Node management");
|
|
5230
5961
|
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) => {
|
|
5231
5962
|
await runNodeStartCommand(config, options);
|
|
5232
5963
|
});
|
|
@@ -5256,9 +5987,9 @@ function createNodeCommand(config) {
|
|
|
5256
5987
|
|
|
5257
5988
|
// src/commands/payment.ts
|
|
5258
5989
|
import { ckbToShannons as ckbToShannons4, shannonsToCkb as shannonsToCkb6 } from "@fiber-pay/sdk";
|
|
5259
|
-
import { Command as
|
|
5990
|
+
import { Command as Command11 } from "commander";
|
|
5260
5991
|
function createPaymentCommand(config) {
|
|
5261
|
-
const payment = new
|
|
5992
|
+
const payment = new Command11("payment").description("Payment lifecycle and status commands");
|
|
5262
5993
|
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) => {
|
|
5263
5994
|
const rpc = await createReadyRpcClient(config);
|
|
5264
5995
|
const json = Boolean(options.json);
|
|
@@ -5592,24 +6323,40 @@ function getJobFailure(job) {
|
|
|
5592
6323
|
}
|
|
5593
6324
|
|
|
5594
6325
|
// src/commands/peer.ts
|
|
5595
|
-
import {
|
|
5596
|
-
|
|
6326
|
+
import { nodeIdToPeerId as nodeIdToPeerId3 } from "@fiber-pay/sdk";
|
|
6327
|
+
import { Command as Command12 } from "commander";
|
|
6328
|
+
function extractPeerIdFromMultiaddr2(address) {
|
|
5597
6329
|
const match = address.match(/\/p2p\/([^/]+)$/);
|
|
5598
6330
|
return match?.[1];
|
|
5599
6331
|
}
|
|
5600
|
-
async function
|
|
6332
|
+
async function findPeerByPeerId(rpc, expectedPeerId) {
|
|
6333
|
+
const peers = await rpc.listPeers();
|
|
6334
|
+
for (const peer of peers.peers) {
|
|
6335
|
+
if (extractPeerIdFromMultiaddr2(peer.address) === expectedPeerId) {
|
|
6336
|
+
return peer;
|
|
6337
|
+
}
|
|
6338
|
+
try {
|
|
6339
|
+
if (await nodeIdToPeerId3(peer.pubkey) === expectedPeerId) {
|
|
6340
|
+
return peer;
|
|
6341
|
+
}
|
|
6342
|
+
} catch {
|
|
6343
|
+
}
|
|
6344
|
+
}
|
|
6345
|
+
return void 0;
|
|
6346
|
+
}
|
|
6347
|
+
async function waitForPeerConnected(rpc, expectedPeerId, timeoutMs) {
|
|
5601
6348
|
const start = Date.now();
|
|
5602
6349
|
while (Date.now() - start < timeoutMs) {
|
|
5603
|
-
const
|
|
5604
|
-
if (
|
|
5605
|
-
return
|
|
6350
|
+
const match = await findPeerByPeerId(rpc, expectedPeerId);
|
|
6351
|
+
if (match) {
|
|
6352
|
+
return { pubkey: match.pubkey, address: match.address };
|
|
5606
6353
|
}
|
|
5607
6354
|
await new Promise((resolve2) => setTimeout(resolve2, 500));
|
|
5608
6355
|
}
|
|
5609
|
-
return
|
|
6356
|
+
return void 0;
|
|
5610
6357
|
}
|
|
5611
6358
|
function createPeerCommand(config) {
|
|
5612
|
-
const peer = new
|
|
6359
|
+
const peer = new Command12("peer").description("Peer management");
|
|
5613
6360
|
peer.command("list").option("--json").action(async (options) => {
|
|
5614
6361
|
const rpc = await createReadyRpcClient(config);
|
|
5615
6362
|
const peers = await rpc.listPeers();
|
|
@@ -5621,41 +6368,47 @@ function createPeerCommand(config) {
|
|
|
5621
6368
|
});
|
|
5622
6369
|
peer.command("connect").argument("<multiaddr>").option("--timeout <sec>", "Wait timeout for peer to appear in peer list", "8").option("--json").action(async (address, options) => {
|
|
5623
6370
|
const rpc = await createReadyRpcClient(config);
|
|
5624
|
-
const
|
|
5625
|
-
if (!
|
|
6371
|
+
const expectedPeerId = extractPeerIdFromMultiaddr2(address);
|
|
6372
|
+
if (!expectedPeerId) {
|
|
5626
6373
|
throw new Error("Invalid multiaddr: missing /p2p/<peerId> suffix");
|
|
5627
6374
|
}
|
|
5628
6375
|
await rpc.connectPeer({ address });
|
|
5629
6376
|
const timeoutMs = Math.max(1, Number.parseInt(String(options.timeout), 10) || 8) * 1e3;
|
|
5630
|
-
const connected = await waitForPeerConnected(rpc,
|
|
6377
|
+
const connected = await waitForPeerConnected(rpc, expectedPeerId, timeoutMs);
|
|
5631
6378
|
if (!connected) {
|
|
5632
6379
|
throw new Error(
|
|
5633
|
-
`connect_peer accepted but peer not found in list within ${Math.floor(timeoutMs / 1e3)}s (${
|
|
6380
|
+
`connect_peer accepted but peer not found in list within ${Math.floor(timeoutMs / 1e3)}s (${expectedPeerId})`
|
|
5634
6381
|
);
|
|
5635
6382
|
}
|
|
5636
6383
|
if (options.json) {
|
|
5637
|
-
printJsonSuccess({
|
|
6384
|
+
printJsonSuccess({
|
|
6385
|
+
address,
|
|
6386
|
+
peerId: expectedPeerId,
|
|
6387
|
+
pubkey: connected.pubkey,
|
|
6388
|
+
message: "Connected"
|
|
6389
|
+
});
|
|
5638
6390
|
} else {
|
|
5639
6391
|
console.log("\u2705 Connected to peer");
|
|
5640
6392
|
console.log(` Address: ${address}`);
|
|
5641
|
-
console.log(` Peer ID: ${
|
|
6393
|
+
console.log(` Peer ID: ${expectedPeerId}`);
|
|
6394
|
+
console.log(` Peer Pubkey: ${connected.pubkey}`);
|
|
5642
6395
|
}
|
|
5643
6396
|
});
|
|
5644
|
-
peer.command("disconnect").argument("<
|
|
6397
|
+
peer.command("disconnect").argument("<pubkey>").option("--json").action(async (pubkey, options) => {
|
|
5645
6398
|
const rpc = await createReadyRpcClient(config);
|
|
5646
|
-
await rpc.disconnectPeer({
|
|
6399
|
+
await rpc.disconnectPeer({ pubkey });
|
|
5647
6400
|
if (options.json) {
|
|
5648
|
-
printJsonSuccess({
|
|
6401
|
+
printJsonSuccess({ pubkey, message: "Disconnected" });
|
|
5649
6402
|
} else {
|
|
5650
6403
|
console.log("\u2705 Disconnected peer");
|
|
5651
|
-
console.log(` Peer
|
|
6404
|
+
console.log(` Peer Pubkey: ${pubkey}`);
|
|
5652
6405
|
}
|
|
5653
6406
|
});
|
|
5654
6407
|
return peer;
|
|
5655
6408
|
}
|
|
5656
6409
|
|
|
5657
6410
|
// src/commands/runtime.ts
|
|
5658
|
-
import { spawn as
|
|
6411
|
+
import { spawn as spawn3 } from "child_process";
|
|
5659
6412
|
import { join as join10, resolve } from "path";
|
|
5660
6413
|
import {
|
|
5661
6414
|
alertPriorityOrder,
|
|
@@ -5664,7 +6417,7 @@ import {
|
|
|
5664
6417
|
isAlertType,
|
|
5665
6418
|
startRuntimeService as startRuntimeService2
|
|
5666
6419
|
} from "@fiber-pay/runtime";
|
|
5667
|
-
import { Command as
|
|
6420
|
+
import { Command as Command13 } from "commander";
|
|
5668
6421
|
|
|
5669
6422
|
// src/lib/parse-options.ts
|
|
5670
6423
|
function parseIntegerOption(value, name) {
|
|
@@ -5735,7 +6488,7 @@ function resolveRuntimeRecoveryListen(config) {
|
|
|
5735
6488
|
return meta?.proxyListen ?? config.runtimeProxyListen ?? "127.0.0.1:8229";
|
|
5736
6489
|
}
|
|
5737
6490
|
function createRuntimeCommand(config) {
|
|
5738
|
-
const runtime = new
|
|
6491
|
+
const runtime = new Command13("runtime").description("Polling monitor and alert runtime service");
|
|
5739
6492
|
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(
|
|
5740
6493
|
"--log-min-priority <priority>",
|
|
5741
6494
|
"Minimum runtime log priority (critical|high|medium|low)"
|
|
@@ -5793,7 +6546,7 @@ function createRuntimeCommand(config) {
|
|
|
5793
6546
|
}
|
|
5794
6547
|
if (daemon && !isRuntimeChild) {
|
|
5795
6548
|
const childArgv = process.argv.filter((arg) => arg !== "--daemon");
|
|
5796
|
-
const child =
|
|
6549
|
+
const child = spawn3(process.execPath, childArgv.slice(1), {
|
|
5797
6550
|
detached: true,
|
|
5798
6551
|
stdio: "ignore",
|
|
5799
6552
|
cwd: process.cwd(),
|
|
@@ -6144,15 +6897,15 @@ function createRuntimeCommand(config) {
|
|
|
6144
6897
|
}
|
|
6145
6898
|
|
|
6146
6899
|
// src/commands/version.ts
|
|
6147
|
-
import { Command as
|
|
6900
|
+
import { Command as Command14 } from "commander";
|
|
6148
6901
|
|
|
6149
6902
|
// src/lib/build-info.ts
|
|
6150
|
-
var CLI_VERSION = "0.
|
|
6151
|
-
var CLI_COMMIT = "
|
|
6903
|
+
var CLI_VERSION = "0.2.0";
|
|
6904
|
+
var CLI_COMMIT = "f9635524f49ce13667b606fcb9a0d1aef35bcb2d";
|
|
6152
6905
|
|
|
6153
6906
|
// src/commands/version.ts
|
|
6154
6907
|
function createVersionCommand() {
|
|
6155
|
-
return new
|
|
6908
|
+
return new Command14("version").description("Show CLI version and commit id").option("--json", "Output JSON").action((options) => {
|
|
6156
6909
|
const payload = {
|
|
6157
6910
|
version: CLI_VERSION,
|
|
6158
6911
|
commit: CLI_COMMIT
|
|
@@ -6167,7 +6920,7 @@ function createVersionCommand() {
|
|
|
6167
6920
|
}
|
|
6168
6921
|
|
|
6169
6922
|
// src/commands/wallet.ts
|
|
6170
|
-
import { Command as
|
|
6923
|
+
import { Command as Command15, Option } from "commander";
|
|
6171
6924
|
|
|
6172
6925
|
// src/lib/wallet-address.ts
|
|
6173
6926
|
import { scriptToAddress as scriptToAddress2 } from "@fiber-pay/sdk";
|
|
@@ -6222,7 +6975,7 @@ async function runWalletBalanceCommand(config, options) {
|
|
|
6222
6975
|
|
|
6223
6976
|
// src/commands/wallet.ts
|
|
6224
6977
|
function createWalletCommand(config) {
|
|
6225
|
-
const wallet = new
|
|
6978
|
+
const wallet = new Command15("wallet").description("Wallet management");
|
|
6226
6979
|
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) => {
|
|
6227
6980
|
await runWalletAddressCommand(config, options);
|
|
6228
6981
|
});
|
|
@@ -6397,7 +7150,7 @@ async function main() {
|
|
|
6397
7150
|
}
|
|
6398
7151
|
applyGlobalOverrides(argv);
|
|
6399
7152
|
const config = getEffectiveConfig(explicitFlags).config;
|
|
6400
|
-
const program = new
|
|
7153
|
+
const program = new Command16();
|
|
6401
7154
|
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();
|
|
6402
7155
|
program.exitOverride();
|
|
6403
7156
|
program.configureOutput({
|
|
@@ -6409,6 +7162,7 @@ async function main() {
|
|
|
6409
7162
|
}
|
|
6410
7163
|
});
|
|
6411
7164
|
program.addCommand(createNodeCommand(config));
|
|
7165
|
+
program.addCommand(createAgentCommand(config));
|
|
6412
7166
|
program.addCommand(createChannelCommand(config));
|
|
6413
7167
|
program.addCommand(createInvoiceCommand(config));
|
|
6414
7168
|
program.addCommand(createPaymentCommand(config));
|
|
@@ -6418,6 +7172,7 @@ async function main() {
|
|
|
6418
7172
|
program.addCommand(createGraphCommand(config));
|
|
6419
7173
|
program.addCommand(createBinaryCommand(config));
|
|
6420
7174
|
program.addCommand(createConfigCommand(config));
|
|
7175
|
+
program.addCommand(createL402Command(config));
|
|
6421
7176
|
program.addCommand(createRuntimeCommand(config));
|
|
6422
7177
|
program.addCommand(createVersionCommand());
|
|
6423
7178
|
program.addCommand(createWalletCommand(config));
|