@fiber-pay/cli 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/cli.js +1072 -370
- 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 {
|
|
@@ -442,47 +289,880 @@ function printPaymentDetailHuman(payment) {
|
|
|
442
289
|
}
|
|
443
290
|
}
|
|
444
291
|
}
|
|
445
|
-
function printChannelListHuman(channels) {
|
|
446
|
-
if (channels.length === 0) {
|
|
447
|
-
console.log("No channels found.");
|
|
448
|
-
return;
|
|
292
|
+
function printChannelListHuman(channels) {
|
|
293
|
+
if (channels.length === 0) {
|
|
294
|
+
console.log("No channels found.");
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
const summary = getChannelSummary(channels);
|
|
298
|
+
console.log(`Channels: ${summary.count} total, ${summary.activeCount} ready`);
|
|
299
|
+
console.log(
|
|
300
|
+
`Liquidity: local ${summary.totalLocalCkb} CKB | remote ${summary.totalRemoteCkb} CKB | capacity ${summary.totalCapacityCkb} CKB`
|
|
301
|
+
);
|
|
302
|
+
console.log("");
|
|
303
|
+
console.log(
|
|
304
|
+
"ID PEER STATE LOCAL REMOTE TLC"
|
|
305
|
+
);
|
|
306
|
+
console.log(
|
|
307
|
+
"---------------------------------------------------------------------------------------------------"
|
|
308
|
+
);
|
|
309
|
+
for (const channel of channels) {
|
|
310
|
+
const id = truncateMiddle(channel.channel_id, 10, 8).padEnd(22, " ");
|
|
311
|
+
const peer = truncateMiddle(channel.peer_id, 10, 8).padEnd(22, " ");
|
|
312
|
+
const state = channel.state.state_name.padEnd(24, " ");
|
|
313
|
+
const local = `${shannonsToCkb(channel.local_balance)}`.padStart(8, " ");
|
|
314
|
+
const remote = `${shannonsToCkb(channel.remote_balance)}`.padStart(8, " ");
|
|
315
|
+
const tlcs = `${channel.pending_tlcs.length}`.padStart(4, " ");
|
|
316
|
+
console.log(`${id} ${peer} ${state} ${local} ${remote} ${tlcs}`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function printPeerListHuman(peers) {
|
|
320
|
+
if (peers.length === 0) {
|
|
321
|
+
console.log("No connected peers.");
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
console.log(`Peers: ${peers.length}`);
|
|
325
|
+
console.log("");
|
|
326
|
+
console.log("PEER ID PUBKEY ADDRESS");
|
|
327
|
+
console.log("--------------------------------------------------------------------------");
|
|
328
|
+
for (const peer of peers) {
|
|
329
|
+
const peerId = truncateMiddle(peer.peer_id, 10, 8).padEnd(22, " ");
|
|
330
|
+
const pubkey = truncateMiddle(peer.pubkey, 10, 8).padEnd(22, " ");
|
|
331
|
+
console.log(`${peerId} ${pubkey} ${peer.address}`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// src/lib/rpc.ts
|
|
336
|
+
import { FiberRpcClient } from "@fiber-pay/sdk";
|
|
337
|
+
|
|
338
|
+
// src/lib/pid.ts
|
|
339
|
+
import { existsSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
340
|
+
import { join } from "path";
|
|
341
|
+
function getPidFilePath(dataDir) {
|
|
342
|
+
return join(dataDir, "fiber.pid");
|
|
343
|
+
}
|
|
344
|
+
function writePidFile(dataDir, pid) {
|
|
345
|
+
writeFileSync(getPidFilePath(dataDir), String(pid));
|
|
346
|
+
}
|
|
347
|
+
function readPidFile(dataDir) {
|
|
348
|
+
const pidPath = getPidFilePath(dataDir);
|
|
349
|
+
if (!existsSync(pidPath)) return null;
|
|
350
|
+
try {
|
|
351
|
+
return parseInt(readFileSync(pidPath, "utf-8").trim(), 10);
|
|
352
|
+
} catch {
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function removePidFile(dataDir) {
|
|
357
|
+
const pidPath = getPidFilePath(dataDir);
|
|
358
|
+
if (existsSync(pidPath)) {
|
|
359
|
+
unlinkSync(pidPath);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
function isProcessRunning(pid) {
|
|
363
|
+
try {
|
|
364
|
+
process.kill(pid, 0);
|
|
365
|
+
return true;
|
|
366
|
+
} catch {
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// src/lib/runtime-meta.ts
|
|
372
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
373
|
+
import { join as join2 } from "path";
|
|
374
|
+
function getRuntimePidFilePath(dataDir) {
|
|
375
|
+
return join2(dataDir, "runtime.pid");
|
|
376
|
+
}
|
|
377
|
+
function getRuntimeMetaFilePath(dataDir) {
|
|
378
|
+
return join2(dataDir, "runtime.meta.json");
|
|
379
|
+
}
|
|
380
|
+
function writeRuntimePid(dataDir, pid) {
|
|
381
|
+
writeFileSync2(getRuntimePidFilePath(dataDir), String(pid));
|
|
382
|
+
}
|
|
383
|
+
function readRuntimePid(dataDir) {
|
|
384
|
+
const pidPath = getRuntimePidFilePath(dataDir);
|
|
385
|
+
if (!existsSync2(pidPath)) return null;
|
|
386
|
+
try {
|
|
387
|
+
return Number.parseInt(readFileSync2(pidPath, "utf-8").trim(), 10);
|
|
388
|
+
} catch {
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function writeRuntimeMeta(dataDir, meta) {
|
|
393
|
+
writeFileSync2(getRuntimeMetaFilePath(dataDir), JSON.stringify(meta, null, 2));
|
|
394
|
+
}
|
|
395
|
+
function readRuntimeMeta(dataDir) {
|
|
396
|
+
const metaPath = getRuntimeMetaFilePath(dataDir);
|
|
397
|
+
if (!existsSync2(metaPath)) return null;
|
|
398
|
+
try {
|
|
399
|
+
return JSON.parse(readFileSync2(metaPath, "utf-8"));
|
|
400
|
+
} catch {
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function removeRuntimeFiles(dataDir) {
|
|
405
|
+
const pidPath = getRuntimePidFilePath(dataDir);
|
|
406
|
+
const metaPath = getRuntimeMetaFilePath(dataDir);
|
|
407
|
+
if (existsSync2(pidPath)) {
|
|
408
|
+
unlinkSync2(pidPath);
|
|
409
|
+
}
|
|
410
|
+
if (existsSync2(metaPath)) {
|
|
411
|
+
unlinkSync2(metaPath);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// src/lib/rpc.ts
|
|
416
|
+
function normalizeUrl(url) {
|
|
417
|
+
try {
|
|
418
|
+
const normalized = new URL(url).toString();
|
|
419
|
+
return normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
|
|
420
|
+
} catch {
|
|
421
|
+
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
function resolveRuntimeProxyUrl(config) {
|
|
425
|
+
const runtimeMeta = readRuntimeMeta(config.dataDir);
|
|
426
|
+
const runtimePid = readRuntimePid(config.dataDir);
|
|
427
|
+
if (!runtimeMeta || !runtimePid || !isProcessRunning(runtimePid)) {
|
|
428
|
+
return void 0;
|
|
429
|
+
}
|
|
430
|
+
if (!runtimeMeta.proxyListen || !runtimeMeta.fiberRpcUrl) {
|
|
431
|
+
return void 0;
|
|
432
|
+
}
|
|
433
|
+
if (normalizeUrl(runtimeMeta.fiberRpcUrl) !== normalizeUrl(config.rpcUrl)) {
|
|
434
|
+
return void 0;
|
|
435
|
+
}
|
|
436
|
+
if (runtimeMeta.proxyListen.startsWith("http://") || runtimeMeta.proxyListen.startsWith("https://")) {
|
|
437
|
+
return runtimeMeta.proxyListen;
|
|
438
|
+
}
|
|
439
|
+
return `http://${runtimeMeta.proxyListen}`;
|
|
440
|
+
}
|
|
441
|
+
function createRpcClient(config) {
|
|
442
|
+
const resolved = resolveRpcEndpoint(config);
|
|
443
|
+
return new FiberRpcClient({
|
|
444
|
+
url: resolved.url,
|
|
445
|
+
biscuitToken: config.rpcBiscuitToken
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
function resolveRpcEndpoint(config) {
|
|
449
|
+
const runtimeProxyUrl = resolveRuntimeProxyUrl(config);
|
|
450
|
+
if (runtimeProxyUrl) {
|
|
451
|
+
return {
|
|
452
|
+
url: runtimeProxyUrl,
|
|
453
|
+
target: "runtime-proxy"
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
return {
|
|
457
|
+
url: config.rpcUrl,
|
|
458
|
+
target: "node-rpc"
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
async function createReadyRpcClient(config, options = {}) {
|
|
462
|
+
const rpc = createRpcClient(config);
|
|
463
|
+
await rpc.waitForReady({ timeout: options.timeout ?? 3e3, interval: options.interval ?? 500 });
|
|
464
|
+
return rpc;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// src/lib/agent-call.ts
|
|
468
|
+
function formatDuration(durationMs) {
|
|
469
|
+
if (typeof durationMs !== "number" || Number.isNaN(durationMs)) {
|
|
470
|
+
return "unknown";
|
|
471
|
+
}
|
|
472
|
+
return `${durationMs}ms`;
|
|
473
|
+
}
|
|
474
|
+
function printFriendlySuccess(result, options) {
|
|
475
|
+
const agent = typeof result.agent === "string" ? result.agent : "unknown";
|
|
476
|
+
const duration = formatDuration(result.durationMs);
|
|
477
|
+
const response = typeof result.response === "string" ? result.response : JSON.stringify(result.response, null, 2);
|
|
478
|
+
console.log("Agent call succeeded");
|
|
479
|
+
console.log(` Agent: ${agent}`);
|
|
480
|
+
console.log(` Duration: ${duration}`);
|
|
481
|
+
console.log(` Payment: ${options.paymentRequired ? "required" : "not required"}`);
|
|
482
|
+
if (options.paymentHash) {
|
|
483
|
+
console.log(` Payment hash: ${options.paymentHash}`);
|
|
484
|
+
}
|
|
485
|
+
console.log("");
|
|
486
|
+
console.log("Agent response:");
|
|
487
|
+
console.log(response ?? "");
|
|
488
|
+
}
|
|
489
|
+
async function runAgentCallCommand(config, url, options) {
|
|
490
|
+
const asJson = Boolean(options.json);
|
|
491
|
+
const timeoutMs = parseInt(options.timeout, 10) * 1e3;
|
|
492
|
+
let prompt = options.prompt;
|
|
493
|
+
if (!prompt && options.file) {
|
|
494
|
+
try {
|
|
495
|
+
prompt = readFileSync3(options.file, "utf-8").trim();
|
|
496
|
+
} catch (err) {
|
|
497
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
498
|
+
if (asJson) {
|
|
499
|
+
printJsonError({
|
|
500
|
+
code: "AGENT_CALL_FILE_READ_ERROR",
|
|
501
|
+
message: `Failed to read prompt file: ${message}`,
|
|
502
|
+
recoverable: true,
|
|
503
|
+
suggestion: "Check the file path and try again."
|
|
504
|
+
});
|
|
505
|
+
} else {
|
|
506
|
+
console.error(`Error: Failed to read prompt file: ${message}`);
|
|
507
|
+
}
|
|
508
|
+
process.exit(1);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
if (!prompt) {
|
|
512
|
+
if (!process.stdin.isTTY) {
|
|
513
|
+
const chunks = [];
|
|
514
|
+
for await (const chunk of process.stdin) {
|
|
515
|
+
chunks.push(chunk);
|
|
516
|
+
}
|
|
517
|
+
prompt = Buffer.concat(chunks).toString("utf-8").trim();
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
if (!prompt) {
|
|
521
|
+
if (asJson) {
|
|
522
|
+
printJsonError({
|
|
523
|
+
code: "AGENT_CALL_NO_PROMPT",
|
|
524
|
+
message: "No prompt provided.",
|
|
525
|
+
recoverable: true,
|
|
526
|
+
suggestion: "Use --prompt <text>, --file <path>, or pipe via stdin."
|
|
527
|
+
});
|
|
528
|
+
} else {
|
|
529
|
+
console.error("Error: No prompt provided.");
|
|
530
|
+
console.error(" Use --prompt <text>, --file <path>, or pipe via stdin.");
|
|
531
|
+
}
|
|
532
|
+
process.exit(1);
|
|
533
|
+
}
|
|
534
|
+
const targetUrl = url.endsWith("/") ? url : `${url}/`;
|
|
535
|
+
const controller = new AbortController();
|
|
536
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
537
|
+
try {
|
|
538
|
+
if (!asJson) {
|
|
539
|
+
console.log(`Calling agent at ${targetUrl}...`);
|
|
540
|
+
}
|
|
541
|
+
const initialResponse = await fetch(targetUrl, {
|
|
542
|
+
method: "POST",
|
|
543
|
+
headers: { "Content-Type": "application/json" },
|
|
544
|
+
body: JSON.stringify({ prompt }),
|
|
545
|
+
signal: controller.signal
|
|
546
|
+
});
|
|
547
|
+
if (initialResponse.ok) {
|
|
548
|
+
const body = await initialResponse.json();
|
|
549
|
+
clearTimeout(timer);
|
|
550
|
+
if (asJson) {
|
|
551
|
+
printJsonSuccess(body);
|
|
552
|
+
} else {
|
|
553
|
+
printFriendlySuccess(body, { paymentRequired: false });
|
|
554
|
+
}
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
if (initialResponse.status !== 402 && initialResponse.status !== 401) {
|
|
558
|
+
const body = await initialResponse.text();
|
|
559
|
+
clearTimeout(timer);
|
|
560
|
+
if (asJson) {
|
|
561
|
+
printJsonError({
|
|
562
|
+
code: "AGENT_CALL_UNEXPECTED_STATUS",
|
|
563
|
+
message: `Unexpected status ${initialResponse.status} from agent.`,
|
|
564
|
+
recoverable: false,
|
|
565
|
+
details: { body: body.slice(0, 500) }
|
|
566
|
+
});
|
|
567
|
+
} else {
|
|
568
|
+
console.error(`Error: Unexpected status ${initialResponse.status}`);
|
|
569
|
+
console.error(body.slice(0, 500));
|
|
570
|
+
}
|
|
571
|
+
process.exit(1);
|
|
572
|
+
}
|
|
573
|
+
const challengeBody = await initialResponse.json();
|
|
574
|
+
const macaroon = challengeBody.macaroon;
|
|
575
|
+
const invoiceAddress = challengeBody.invoice;
|
|
576
|
+
if (!macaroon || !invoiceAddress) {
|
|
577
|
+
clearTimeout(timer);
|
|
578
|
+
if (asJson) {
|
|
579
|
+
printJsonError({
|
|
580
|
+
code: "AGENT_CALL_INVALID_CHALLENGE",
|
|
581
|
+
message: "Invalid L402 challenge: missing macaroon or invoice.",
|
|
582
|
+
recoverable: false
|
|
583
|
+
});
|
|
584
|
+
} else {
|
|
585
|
+
console.error("Error: Invalid L402 challenge response (missing macaroon or invoice).");
|
|
586
|
+
}
|
|
587
|
+
process.exit(1);
|
|
588
|
+
}
|
|
589
|
+
if (!asJson) {
|
|
590
|
+
console.log("Payment required. Paying invoice via Fiber...");
|
|
591
|
+
}
|
|
592
|
+
const rpcClient = await createReadyRpcClient(config);
|
|
593
|
+
const paymentResult = await rpcClient.sendPayment({
|
|
594
|
+
invoice: invoiceAddress
|
|
595
|
+
});
|
|
596
|
+
if (!asJson) {
|
|
597
|
+
console.log(
|
|
598
|
+
`Payment sent (hash: ${paymentResult.payment_hash}). Waiting for confirmation...`
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
const finalPayment = await rpcClient.waitForPayment(paymentResult.payment_hash, {
|
|
602
|
+
timeout: timeoutMs,
|
|
603
|
+
interval: 2e3
|
|
604
|
+
});
|
|
605
|
+
if (finalPayment.status !== "Success") {
|
|
606
|
+
clearTimeout(timer);
|
|
607
|
+
if (asJson) {
|
|
608
|
+
printJsonError({
|
|
609
|
+
code: "AGENT_CALL_PAYMENT_FAILED",
|
|
610
|
+
message: `Payment failed: ${finalPayment.failed_error ?? finalPayment.status}`,
|
|
611
|
+
recoverable: false
|
|
612
|
+
});
|
|
613
|
+
} else {
|
|
614
|
+
console.error(`Error: Payment failed: ${finalPayment.failed_error ?? finalPayment.status}`);
|
|
615
|
+
}
|
|
616
|
+
process.exit(1);
|
|
617
|
+
}
|
|
618
|
+
if (!asJson) {
|
|
619
|
+
console.log("Payment confirmed. Retrying request with L402 token...");
|
|
620
|
+
}
|
|
621
|
+
const retryResponse = await fetch(targetUrl, {
|
|
622
|
+
method: "POST",
|
|
623
|
+
headers: {
|
|
624
|
+
"Content-Type": "application/json",
|
|
625
|
+
Authorization: `L402 ${macaroon}`,
|
|
626
|
+
"X-L402-Payment-Hash": paymentResult.payment_hash
|
|
627
|
+
},
|
|
628
|
+
body: JSON.stringify({ prompt }),
|
|
629
|
+
signal: controller.signal
|
|
630
|
+
});
|
|
631
|
+
clearTimeout(timer);
|
|
632
|
+
if (!retryResponse.ok) {
|
|
633
|
+
const errBody = await retryResponse.text();
|
|
634
|
+
if (asJson) {
|
|
635
|
+
printJsonError({
|
|
636
|
+
code: "AGENT_CALL_RETRY_FAILED",
|
|
637
|
+
message: `Agent returned ${retryResponse.status} after payment.`,
|
|
638
|
+
recoverable: false,
|
|
639
|
+
details: { body: errBody.slice(0, 500) }
|
|
640
|
+
});
|
|
641
|
+
} else {
|
|
642
|
+
console.error(`Error: Agent returned ${retryResponse.status} after payment.`);
|
|
643
|
+
console.error(errBody.slice(0, 500));
|
|
644
|
+
}
|
|
645
|
+
process.exit(1);
|
|
646
|
+
}
|
|
647
|
+
const result = await retryResponse.json();
|
|
648
|
+
if (asJson) {
|
|
649
|
+
printJsonSuccess({
|
|
650
|
+
...result,
|
|
651
|
+
paymentHash: paymentResult.payment_hash
|
|
652
|
+
});
|
|
653
|
+
} else {
|
|
654
|
+
printFriendlySuccess(result, {
|
|
655
|
+
paymentRequired: true,
|
|
656
|
+
paymentHash: paymentResult.payment_hash
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
} catch (error) {
|
|
660
|
+
clearTimeout(timer);
|
|
661
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
662
|
+
const isTimeout = error instanceof Error && error.name === "AbortError";
|
|
663
|
+
if (asJson) {
|
|
664
|
+
printJsonError({
|
|
665
|
+
code: isTimeout ? "AGENT_CALL_TIMEOUT" : "AGENT_CALL_ERROR",
|
|
666
|
+
message: isTimeout ? "Request timed out." : message,
|
|
667
|
+
recoverable: !isTimeout
|
|
668
|
+
});
|
|
669
|
+
} else {
|
|
670
|
+
console.error(`Error: ${isTimeout ? "Request timed out." : message}`);
|
|
671
|
+
}
|
|
672
|
+
process.exit(1);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// src/lib/agent-serve.ts
|
|
677
|
+
import { execFileSync, spawn } from "child_process";
|
|
678
|
+
import { createServer } from "http";
|
|
679
|
+
import { createL402Middleware, FiberRpcClient as FiberRpcClient2 } from "@fiber-pay/sdk";
|
|
680
|
+
import express from "express";
|
|
681
|
+
function getClientIp(req) {
|
|
682
|
+
const forwarded = req.headers["x-forwarded-for"];
|
|
683
|
+
const forwardedValue = Array.isArray(forwarded) ? forwarded[0] : forwarded;
|
|
684
|
+
if (typeof forwardedValue === "string" && forwardedValue.trim().length > 0) {
|
|
685
|
+
return forwardedValue.split(",")[0]?.trim() || "unknown";
|
|
686
|
+
}
|
|
687
|
+
return req.ip || req.socket.remoteAddress || "unknown";
|
|
688
|
+
}
|
|
689
|
+
function summarizeL402Status(req) {
|
|
690
|
+
if (req.l402?.valid) {
|
|
691
|
+
if (req.l402.paymentHash) {
|
|
692
|
+
return `payment-verified:${req.l402.paymentHash.slice(0, 14)}...`;
|
|
693
|
+
}
|
|
694
|
+
if (req.l402.preimage) {
|
|
695
|
+
return "payment-verified:preimage";
|
|
696
|
+
}
|
|
697
|
+
return "payment-verified";
|
|
698
|
+
}
|
|
699
|
+
return "no-l402-token";
|
|
700
|
+
}
|
|
701
|
+
function getRequestId(req) {
|
|
702
|
+
return req._fiberPayRequestId ?? 0;
|
|
703
|
+
}
|
|
704
|
+
function runAcpx(agent, prompt, options) {
|
|
705
|
+
return new Promise((resolve2) => {
|
|
706
|
+
const args = ["--format", "quiet"];
|
|
707
|
+
if (options.approveAll) {
|
|
708
|
+
args.push("--approve-all");
|
|
709
|
+
}
|
|
710
|
+
if (options.cwd) {
|
|
711
|
+
args.push("--cwd", options.cwd);
|
|
712
|
+
}
|
|
713
|
+
if (options.timeoutSeconds > 0) {
|
|
714
|
+
args.push("--timeout", String(options.timeoutSeconds));
|
|
715
|
+
}
|
|
716
|
+
args.push(agent, "exec", prompt);
|
|
717
|
+
const child = spawn("acpx", args, {
|
|
718
|
+
cwd: options.cwd || process.cwd(),
|
|
719
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
720
|
+
env: { ...process.env }
|
|
721
|
+
});
|
|
722
|
+
let stdout = "";
|
|
723
|
+
let stderr = "";
|
|
724
|
+
child.stdout.on("data", (data) => {
|
|
725
|
+
stdout += data.toString();
|
|
726
|
+
});
|
|
727
|
+
child.stderr.on("data", (data) => {
|
|
728
|
+
stderr += data.toString();
|
|
729
|
+
});
|
|
730
|
+
const killTimer = setTimeout(
|
|
731
|
+
() => {
|
|
732
|
+
child.kill("SIGKILL");
|
|
733
|
+
},
|
|
734
|
+
(options.timeoutSeconds + 30) * 1e3
|
|
735
|
+
);
|
|
736
|
+
child.on("close", (code) => {
|
|
737
|
+
clearTimeout(killTimer);
|
|
738
|
+
resolve2({ stdout, stderr, exitCode: code ?? 1 });
|
|
739
|
+
});
|
|
740
|
+
child.on("error", (err) => {
|
|
741
|
+
clearTimeout(killTimer);
|
|
742
|
+
resolve2({ stdout: "", stderr: err.message, exitCode: 1 });
|
|
743
|
+
});
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
async function runAgentServeCommand(config, options) {
|
|
747
|
+
const asJson = Boolean(options.json);
|
|
748
|
+
const port = parseInt(options.port, 10);
|
|
749
|
+
const host = options.host;
|
|
750
|
+
const priceCkb = parseFloat(options.price);
|
|
751
|
+
const expirySeconds = parseInt(options.expiry, 10);
|
|
752
|
+
const timeoutSeconds = parseInt(options.timeout, 10);
|
|
753
|
+
const rootKey = options.rootKey || process.env.L402_ROOT_KEY;
|
|
754
|
+
if (Number.isNaN(port) || port < 1 || port > 65535) {
|
|
755
|
+
if (asJson) {
|
|
756
|
+
printJsonError({
|
|
757
|
+
code: "AGENT_SERVE_INVALID_PORT",
|
|
758
|
+
message: `Invalid port: ${options.port}`,
|
|
759
|
+
recoverable: true,
|
|
760
|
+
suggestion: "Provide a valid port number between 1 and 65535."
|
|
761
|
+
});
|
|
762
|
+
} else {
|
|
763
|
+
console.error(`Error: Invalid port: ${options.port}`);
|
|
764
|
+
}
|
|
765
|
+
process.exit(1);
|
|
766
|
+
}
|
|
767
|
+
if (Number.isNaN(priceCkb) || priceCkb <= 0) {
|
|
768
|
+
if (asJson) {
|
|
769
|
+
printJsonError({
|
|
770
|
+
code: "AGENT_SERVE_INVALID_PRICE",
|
|
771
|
+
message: `Invalid price: ${options.price}`,
|
|
772
|
+
recoverable: true,
|
|
773
|
+
suggestion: "Provide a positive CKB amount, e.g. --price 0.1"
|
|
774
|
+
});
|
|
775
|
+
} else {
|
|
776
|
+
console.error(`Error: Invalid price: ${options.price}`);
|
|
777
|
+
}
|
|
778
|
+
process.exit(1);
|
|
779
|
+
}
|
|
780
|
+
if (!rootKey) {
|
|
781
|
+
if (asJson) {
|
|
782
|
+
printJsonError({
|
|
783
|
+
code: "AGENT_SERVE_MISSING_ROOT_KEY",
|
|
784
|
+
message: "L402 root key is required.",
|
|
785
|
+
recoverable: true,
|
|
786
|
+
suggestion: "Provide --root-key <64-hex-chars> or set L402_ROOT_KEY env var. Generate with: openssl rand -hex 32"
|
|
787
|
+
});
|
|
788
|
+
} else {
|
|
789
|
+
console.error("Error: L402 root key is required.");
|
|
790
|
+
console.error(" Provide --root-key <64-hex-chars> or set L402_ROOT_KEY env var.");
|
|
791
|
+
console.error(" Generate one with: openssl rand -hex 32");
|
|
792
|
+
}
|
|
793
|
+
process.exit(1);
|
|
794
|
+
}
|
|
795
|
+
try {
|
|
796
|
+
execFileSync("acpx", ["--version"], { stdio: "ignore" });
|
|
797
|
+
} catch {
|
|
798
|
+
if (asJson) {
|
|
799
|
+
printJsonError({
|
|
800
|
+
code: "AGENT_SERVE_ACPX_NOT_FOUND",
|
|
801
|
+
message: "acpx is not installed or not in PATH.",
|
|
802
|
+
recoverable: true,
|
|
803
|
+
suggestion: "Install acpx globally: npm install -g acpx"
|
|
804
|
+
});
|
|
805
|
+
} else {
|
|
806
|
+
console.error("Error: acpx is not installed or not in PATH.");
|
|
807
|
+
console.error(" Install it with: npm install -g acpx");
|
|
808
|
+
console.error(" See: https://github.com/openclaw/acpx");
|
|
809
|
+
}
|
|
810
|
+
process.exit(1);
|
|
811
|
+
}
|
|
812
|
+
const rpcClient = new FiberRpcClient2({
|
|
813
|
+
url: config.rpcUrl,
|
|
814
|
+
biscuitToken: config.rpcBiscuitToken
|
|
815
|
+
});
|
|
816
|
+
const currency = config.network === "mainnet" ? "Fibb" : "Fibt";
|
|
817
|
+
const app = express();
|
|
818
|
+
app.use(express.json());
|
|
819
|
+
let requestCounter = 0;
|
|
820
|
+
app.use((req, res, next) => {
|
|
821
|
+
const requestId = ++requestCounter;
|
|
822
|
+
req._fiberPayRequestId = requestId;
|
|
823
|
+
const startTime = Date.now();
|
|
824
|
+
const clientIp = getClientIp(req);
|
|
825
|
+
if (!asJson) {
|
|
826
|
+
console.log(
|
|
827
|
+
`[REQ ${requestId}] ${req.method} ${req.path} from ${clientIp} (auth=${req.headers.authorization ? "present" : "none"})`
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
res.on("finish", () => {
|
|
831
|
+
if (asJson) {
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
const durationMs = Date.now() - startTime;
|
|
835
|
+
const l402State = summarizeL402Status(req);
|
|
836
|
+
if (res.statusCode === 402 || res.statusCode === 401) {
|
|
837
|
+
console.log(
|
|
838
|
+
`[REQ ${requestId}] challenge-issued status=${res.statusCode} duration=${durationMs}ms`
|
|
839
|
+
);
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
console.log(
|
|
843
|
+
`[REQ ${requestId}] completed status=${res.statusCode} duration=${durationMs}ms l402=${l402State}`
|
|
844
|
+
);
|
|
845
|
+
});
|
|
846
|
+
next();
|
|
847
|
+
});
|
|
848
|
+
app.use(
|
|
849
|
+
createL402Middleware({
|
|
850
|
+
rootKey,
|
|
851
|
+
priceCkb,
|
|
852
|
+
expirySeconds,
|
|
853
|
+
rpcClient,
|
|
854
|
+
currency
|
|
855
|
+
})
|
|
856
|
+
);
|
|
857
|
+
app.post("/", async (req, res) => {
|
|
858
|
+
const requestId = getRequestId(req);
|
|
859
|
+
const prompt = req.body?.prompt;
|
|
860
|
+
if (!prompt || typeof prompt !== "string") {
|
|
861
|
+
if (!asJson) {
|
|
862
|
+
console.log(`[REQ ${requestId}] invalid request body: missing string prompt`);
|
|
863
|
+
}
|
|
864
|
+
res.status(400).json({
|
|
865
|
+
error: 'Missing or invalid "prompt" field in request body.'
|
|
866
|
+
});
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
const startTime = Date.now();
|
|
870
|
+
try {
|
|
871
|
+
if (!asJson) {
|
|
872
|
+
console.log(
|
|
873
|
+
`[REQ ${requestId}] payment accepted, invoking agent=${options.agent} promptChars=${prompt.length}`
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
const result = await runAcpx(options.agent, prompt, {
|
|
877
|
+
cwd: options.cwd,
|
|
878
|
+
approveAll: options.approveAll,
|
|
879
|
+
timeoutSeconds
|
|
880
|
+
});
|
|
881
|
+
const durationMs = Date.now() - startTime;
|
|
882
|
+
if (result.exitCode !== 0) {
|
|
883
|
+
if (!asJson) {
|
|
884
|
+
console.log(
|
|
885
|
+
`[REQ ${requestId}] agent failed exit=${result.exitCode} duration=${durationMs}ms`
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
res.status(502).json({
|
|
889
|
+
error: "Agent execution failed.",
|
|
890
|
+
agent: options.agent,
|
|
891
|
+
stderr: result.stderr.slice(0, 1e3),
|
|
892
|
+
durationMs
|
|
893
|
+
});
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
if (!asJson) {
|
|
897
|
+
console.log(`[REQ ${requestId}] agent completed duration=${durationMs}ms`);
|
|
898
|
+
}
|
|
899
|
+
res.json({
|
|
900
|
+
response: result.stdout.trim(),
|
|
901
|
+
agent: options.agent,
|
|
902
|
+
durationMs
|
|
903
|
+
});
|
|
904
|
+
} catch (error) {
|
|
905
|
+
if (!asJson) {
|
|
906
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
907
|
+
console.log(`[REQ ${requestId}] agent execution error: ${message}`);
|
|
908
|
+
}
|
|
909
|
+
res.status(500).json({
|
|
910
|
+
error: "Internal server error.",
|
|
911
|
+
message: error instanceof Error ? error.message : String(error)
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
});
|
|
915
|
+
app.get("/health", (_req, res) => {
|
|
916
|
+
res.json({ status: "ok", agent: options.agent });
|
|
917
|
+
});
|
|
918
|
+
const server = createServer(app);
|
|
919
|
+
await new Promise((resolve2, reject) => {
|
|
920
|
+
server.on("error", (err) => {
|
|
921
|
+
if (err.code === "EADDRINUSE") {
|
|
922
|
+
if (asJson) {
|
|
923
|
+
printJsonError({
|
|
924
|
+
code: "AGENT_SERVE_PORT_IN_USE",
|
|
925
|
+
message: `Port ${port} is already in use.`,
|
|
926
|
+
recoverable: true,
|
|
927
|
+
suggestion: "Use a different port with --port <port>."
|
|
928
|
+
});
|
|
929
|
+
} else {
|
|
930
|
+
console.error(`Error: Port ${port} is already in use.`);
|
|
931
|
+
}
|
|
932
|
+
process.exit(1);
|
|
933
|
+
}
|
|
934
|
+
reject(err);
|
|
935
|
+
});
|
|
936
|
+
server.listen(port, host, () => {
|
|
937
|
+
resolve2();
|
|
938
|
+
});
|
|
939
|
+
});
|
|
940
|
+
const listenUrl = `http://${host}:${port}`;
|
|
941
|
+
if (asJson) {
|
|
942
|
+
printJsonSuccess({
|
|
943
|
+
status: "running",
|
|
944
|
+
listen: listenUrl,
|
|
945
|
+
agent: options.agent,
|
|
946
|
+
priceCkb,
|
|
947
|
+
expirySeconds,
|
|
948
|
+
currency,
|
|
949
|
+
fiberRpcUrl: config.rpcUrl
|
|
950
|
+
});
|
|
951
|
+
} else {
|
|
952
|
+
console.log("Agent service started");
|
|
953
|
+
console.log(` Listen: ${listenUrl}`);
|
|
954
|
+
console.log(` Agent: ${options.agent}`);
|
|
955
|
+
console.log(` Price: ${priceCkb} CKB per request`);
|
|
956
|
+
console.log(` Expiry: ${expirySeconds}s`);
|
|
957
|
+
console.log(` Timeout: ${timeoutSeconds}s per agent call`);
|
|
958
|
+
console.log(` Currency: ${currency}`);
|
|
959
|
+
console.log(` Fiber RPC: ${config.rpcUrl}`);
|
|
960
|
+
console.log("");
|
|
961
|
+
console.log("Endpoint:");
|
|
962
|
+
console.log(` POST ${listenUrl}/ {"prompt": "your question"}`);
|
|
963
|
+
console.log("");
|
|
964
|
+
console.log("Press Ctrl+C to stop.");
|
|
965
|
+
}
|
|
966
|
+
const shutdown = () => {
|
|
967
|
+
if (!asJson) {
|
|
968
|
+
console.log("\nStopping agent service...");
|
|
969
|
+
}
|
|
970
|
+
server.close(() => {
|
|
971
|
+
if (asJson) {
|
|
972
|
+
printJsonSuccess({ status: "stopped" });
|
|
973
|
+
} else {
|
|
974
|
+
console.log("Agent service stopped.");
|
|
975
|
+
}
|
|
976
|
+
process.exit(0);
|
|
977
|
+
});
|
|
978
|
+
setTimeout(() => process.exit(0), 5e3);
|
|
979
|
+
};
|
|
980
|
+
process.on("SIGINT", shutdown);
|
|
981
|
+
process.on("SIGTERM", shutdown);
|
|
982
|
+
await new Promise(() => {
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// src/commands/agent.ts
|
|
987
|
+
function createAgentCommand(config) {
|
|
988
|
+
const agent = new Command("agent").description("AI agent service with L402 payment");
|
|
989
|
+
agent.command("serve").description("Start an L402-gated AI agent HTTP service").requiredOption(
|
|
990
|
+
"--agent <name>",
|
|
991
|
+
"Agent to use (common values: codex, claude, opencode, gemini, pi; see acpx docs for full list)"
|
|
992
|
+
).option("--port <port>", "Listen port", "8402").option("--host <host>", "Listen host", "127.0.0.1").option("--price <ckb>", "Price per request in CKB", "0.1").option("--root-key <hex>", "Macaroon root key (32-byte hex, or set L402_ROOT_KEY env)").option("--expiry <seconds>", "Token expiry in seconds", "3600").option("--cwd <path>", "Working directory for agent execution").option("--approve-all", "Auto-approve all agent tool calls").option("--timeout <seconds>", "Max agent execution time per request", "300").option("--json").addHelpText(
|
|
993
|
+
"after",
|
|
994
|
+
[
|
|
995
|
+
"",
|
|
996
|
+
"Examples:",
|
|
997
|
+
" fiber-pay agent serve --agent codex --price 0.1 --root-key <hex>",
|
|
998
|
+
" fiber-pay agent serve --agent claude --price 0.2 --port 8402"
|
|
999
|
+
].join("\n")
|
|
1000
|
+
).action(async (options) => {
|
|
1001
|
+
await runAgentServeCommand(config, options);
|
|
1002
|
+
});
|
|
1003
|
+
agent.command("call").description("Call a remote L402-gated agent service (auto-pay via Fiber)").argument("<url>", "Agent service URL (e.g. http://host:8402)").option("--prompt <text>", "Prompt text to send").option("--file <path>", "Read prompt from file").option("--timeout <seconds>", "Request timeout in seconds", "300").option("--json").action(async (url, options) => {
|
|
1004
|
+
await runAgentCallCommand(config, url, options);
|
|
1005
|
+
});
|
|
1006
|
+
return agent;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// src/commands/binary.ts
|
|
1010
|
+
import { DEFAULT_FIBER_VERSION, downloadFiberBinary } from "@fiber-pay/node";
|
|
1011
|
+
import { Command as Command2 } from "commander";
|
|
1012
|
+
|
|
1013
|
+
// src/lib/binary-path.ts
|
|
1014
|
+
import { dirname, join as join3 } from "path";
|
|
1015
|
+
import { BinaryManager, getFiberBinaryInfo } from "@fiber-pay/node";
|
|
1016
|
+
|
|
1017
|
+
// src/lib/node-runtime-daemon.ts
|
|
1018
|
+
import { spawnSync } from "child_process";
|
|
1019
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1020
|
+
function getCustomBinaryState(binaryPath) {
|
|
1021
|
+
const exists = existsSync3(binaryPath);
|
|
1022
|
+
if (!exists) {
|
|
1023
|
+
return { path: binaryPath, ready: false, version: "unknown" };
|
|
1024
|
+
}
|
|
1025
|
+
try {
|
|
1026
|
+
const result = spawnSync(binaryPath, ["--version"], { encoding: "utf-8" });
|
|
1027
|
+
if (result.status !== 0) {
|
|
1028
|
+
return { path: binaryPath, ready: false, version: "unknown" };
|
|
1029
|
+
}
|
|
1030
|
+
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
1031
|
+
const firstLine = output.split("\n").find((line) => line.trim().length > 0) ?? "unknown";
|
|
1032
|
+
return { path: binaryPath, ready: true, version: firstLine.trim() };
|
|
1033
|
+
} catch {
|
|
1034
|
+
return { path: binaryPath, ready: false, version: "unknown" };
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
function getBinaryVersion(binaryPath) {
|
|
1038
|
+
try {
|
|
1039
|
+
const result = spawnSync(binaryPath, ["--version"], { encoding: "utf-8" });
|
|
1040
|
+
if (result.status !== 0) {
|
|
1041
|
+
return "unknown";
|
|
1042
|
+
}
|
|
1043
|
+
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
1044
|
+
if (!output) {
|
|
1045
|
+
return "unknown";
|
|
1046
|
+
}
|
|
1047
|
+
const firstLine = output.split("\n").find((line) => line.trim().length > 0);
|
|
1048
|
+
return firstLine?.trim() ?? "unknown";
|
|
1049
|
+
} catch {
|
|
1050
|
+
return "unknown";
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
function getCliEntrypoint() {
|
|
1054
|
+
const entrypoint = process.argv[1];
|
|
1055
|
+
if (!entrypoint) {
|
|
1056
|
+
throw new Error("Unable to resolve CLI entrypoint path");
|
|
1057
|
+
}
|
|
1058
|
+
return entrypoint;
|
|
1059
|
+
}
|
|
1060
|
+
function startRuntimeDaemonFromNode(params) {
|
|
1061
|
+
const cliEntrypoint = getCliEntrypoint();
|
|
1062
|
+
const result = spawnSync(
|
|
1063
|
+
process.execPath,
|
|
1064
|
+
[
|
|
1065
|
+
cliEntrypoint,
|
|
1066
|
+
"--data-dir",
|
|
1067
|
+
params.dataDir,
|
|
1068
|
+
"--rpc-url",
|
|
1069
|
+
params.rpcUrl,
|
|
1070
|
+
"runtime",
|
|
1071
|
+
"start",
|
|
1072
|
+
"--daemon",
|
|
1073
|
+
"--fiber-rpc-url",
|
|
1074
|
+
params.rpcUrl,
|
|
1075
|
+
"--proxy-listen",
|
|
1076
|
+
params.proxyListen,
|
|
1077
|
+
"--state-file",
|
|
1078
|
+
params.stateFilePath,
|
|
1079
|
+
"--alert-logs-base-dir",
|
|
1080
|
+
params.alertLogsBaseDir,
|
|
1081
|
+
"--json"
|
|
1082
|
+
],
|
|
1083
|
+
{ encoding: "utf-8" }
|
|
1084
|
+
);
|
|
1085
|
+
if (result.status === 0) {
|
|
1086
|
+
return { ok: true };
|
|
1087
|
+
}
|
|
1088
|
+
const stderr = (result.stderr ?? "").trim();
|
|
1089
|
+
const stdout = (result.stdout ?? "").trim();
|
|
1090
|
+
const details = stderr || stdout || `exit code ${result.status ?? "unknown"}`;
|
|
1091
|
+
return { ok: false, message: details };
|
|
1092
|
+
}
|
|
1093
|
+
function stopRuntimeDaemonFromNode(params) {
|
|
1094
|
+
const cliEntrypoint = getCliEntrypoint();
|
|
1095
|
+
spawnSync(
|
|
1096
|
+
process.execPath,
|
|
1097
|
+
[
|
|
1098
|
+
cliEntrypoint,
|
|
1099
|
+
"--data-dir",
|
|
1100
|
+
params.dataDir,
|
|
1101
|
+
"--rpc-url",
|
|
1102
|
+
params.rpcUrl,
|
|
1103
|
+
"runtime",
|
|
1104
|
+
"stop",
|
|
1105
|
+
"--json"
|
|
1106
|
+
],
|
|
1107
|
+
{ encoding: "utf-8" }
|
|
1108
|
+
);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
// src/lib/binary-path.ts
|
|
1112
|
+
function getProfileBinaryInstallDir(dataDir) {
|
|
1113
|
+
return join3(dataDir, "bin");
|
|
1114
|
+
}
|
|
1115
|
+
function getProfileManagedBinaryPath(dataDir) {
|
|
1116
|
+
return new BinaryManager(getProfileBinaryInstallDir(dataDir)).getBinaryPath();
|
|
1117
|
+
}
|
|
1118
|
+
function validateConfiguredBinaryPath(binaryPath) {
|
|
1119
|
+
const value = binaryPath.trim();
|
|
1120
|
+
if (!value) {
|
|
1121
|
+
throw new Error("Configured binaryPath cannot be empty");
|
|
1122
|
+
}
|
|
1123
|
+
if (value.includes("\0")) {
|
|
1124
|
+
throw new Error("Configured binaryPath contains an invalid null byte");
|
|
1125
|
+
}
|
|
1126
|
+
return value;
|
|
1127
|
+
}
|
|
1128
|
+
function resolveBinaryPath(config) {
|
|
1129
|
+
const managedPath = getProfileManagedBinaryPath(config.dataDir);
|
|
1130
|
+
if (config.binaryPath) {
|
|
1131
|
+
const binaryPath2 = validateConfiguredBinaryPath(config.binaryPath);
|
|
1132
|
+
const installDir2 = dirname(binaryPath2);
|
|
1133
|
+
const expectedPath = new BinaryManager(installDir2).getBinaryPath();
|
|
1134
|
+
const managedByBinaryManager = expectedPath === binaryPath2;
|
|
1135
|
+
const source = binaryPath2 === managedPath ? "profile-managed" : "configured-path";
|
|
1136
|
+
return {
|
|
1137
|
+
binaryPath: binaryPath2,
|
|
1138
|
+
installDir: managedByBinaryManager ? installDir2 : null,
|
|
1139
|
+
managedPath,
|
|
1140
|
+
managedByBinaryManager,
|
|
1141
|
+
source
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
const installDir = getProfileBinaryInstallDir(config.dataDir);
|
|
1145
|
+
const binaryPath = managedPath;
|
|
1146
|
+
return {
|
|
1147
|
+
binaryPath,
|
|
1148
|
+
installDir,
|
|
1149
|
+
managedPath,
|
|
1150
|
+
managedByBinaryManager: true,
|
|
1151
|
+
source: "profile-managed"
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
function getBinaryManagerInstallDirOrThrow(resolvedBinary) {
|
|
1155
|
+
if (resolvedBinary.installDir) {
|
|
1156
|
+
return resolvedBinary.installDir;
|
|
449
1157
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
console.log(
|
|
453
|
-
`Liquidity: local ${summary.totalLocalCkb} CKB | remote ${summary.totalRemoteCkb} CKB | capacity ${summary.totalCapacityCkb} CKB`
|
|
454
|
-
);
|
|
455
|
-
console.log("");
|
|
456
|
-
console.log(
|
|
457
|
-
"ID PEER STATE LOCAL REMOTE TLC"
|
|
458
|
-
);
|
|
459
|
-
console.log(
|
|
460
|
-
"---------------------------------------------------------------------------------------------------"
|
|
1158
|
+
throw new Error(
|
|
1159
|
+
`Configured binaryPath "${resolvedBinary.binaryPath}" is incompatible with BinaryManager-managed path naming. BinaryManager expects "${new BinaryManager(dirname(resolvedBinary.binaryPath)).getBinaryPath()}". Set binaryPath to a standard managed name (fnn/fnn.exe) in the target directory, or unset binaryPath to use the profile-managed binary.`
|
|
461
1160
|
);
|
|
462
|
-
for (const channel of channels) {
|
|
463
|
-
const id = truncateMiddle(channel.channel_id, 10, 8).padEnd(22, " ");
|
|
464
|
-
const peer = truncateMiddle(channel.peer_id, 10, 8).padEnd(22, " ");
|
|
465
|
-
const state = channel.state.state_name.padEnd(24, " ");
|
|
466
|
-
const local = `${shannonsToCkb(channel.local_balance)}`.padStart(8, " ");
|
|
467
|
-
const remote = `${shannonsToCkb(channel.remote_balance)}`.padStart(8, " ");
|
|
468
|
-
const tlcs = `${channel.pending_tlcs.length}`.padStart(4, " ");
|
|
469
|
-
console.log(`${id} ${peer} ${state} ${local} ${remote} ${tlcs}`);
|
|
470
|
-
}
|
|
471
1161
|
}
|
|
472
|
-
function
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
}
|
|
477
|
-
console.log(`Peers: ${peers.length}`);
|
|
478
|
-
console.log("");
|
|
479
|
-
console.log("PEER ID PUBKEY ADDRESS");
|
|
480
|
-
console.log("--------------------------------------------------------------------------");
|
|
481
|
-
for (const peer of peers) {
|
|
482
|
-
const peerId = truncateMiddle(peer.peer_id, 10, 8).padEnd(22, " ");
|
|
483
|
-
const pubkey = truncateMiddle(peer.pubkey, 10, 8).padEnd(22, " ");
|
|
484
|
-
console.log(`${peerId} ${pubkey} ${peer.address}`);
|
|
485
|
-
}
|
|
1162
|
+
async function getBinaryDetails(config) {
|
|
1163
|
+
const resolvedBinary = resolveBinaryPath(config);
|
|
1164
|
+
const info = resolvedBinary.installDir ? await getFiberBinaryInfo(resolvedBinary.installDir) : getCustomBinaryState(resolvedBinary.binaryPath);
|
|
1165
|
+
return { resolvedBinary, info };
|
|
486
1166
|
}
|
|
487
1167
|
|
|
488
1168
|
// src/commands/binary.ts
|
|
@@ -494,7 +1174,7 @@ function showProgress(progress) {
|
|
|
494
1174
|
}
|
|
495
1175
|
}
|
|
496
1176
|
function createBinaryCommand(config) {
|
|
497
|
-
const binary = new
|
|
1177
|
+
const binary = new Command2("binary").description("Fiber binary management");
|
|
498
1178
|
binary.command("download").option("--version <version>", "Fiber binary version", DEFAULT_FIBER_VERSION).option("--force", "Force re-download").option("--json").action(async (options) => {
|
|
499
1179
|
const resolvedBinary = resolveBinaryPath(config);
|
|
500
1180
|
let installDir;
|
|
@@ -553,145 +1233,13 @@ function createBinaryCommand(config) {
|
|
|
553
1233
|
// src/commands/channel.ts
|
|
554
1234
|
import { randomUUID } from "crypto";
|
|
555
1235
|
import { ckbToShannons as ckbToShannons2 } from "@fiber-pay/sdk";
|
|
556
|
-
import { Command as
|
|
1236
|
+
import { Command as Command3 } from "commander";
|
|
557
1237
|
|
|
558
1238
|
// src/lib/async.ts
|
|
559
1239
|
function sleep(ms) {
|
|
560
1240
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
561
1241
|
}
|
|
562
1242
|
|
|
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
1243
|
// src/lib/runtime-jobs.ts
|
|
696
1244
|
async function tryCreateRuntimePaymentJob(runtimeUrl, body) {
|
|
697
1245
|
try {
|
|
@@ -979,7 +1527,7 @@ function registerChannelRebalanceCommand(parent, config) {
|
|
|
979
1527
|
|
|
980
1528
|
// src/commands/channel.ts
|
|
981
1529
|
function createChannelCommand(config) {
|
|
982
|
-
const channel = new
|
|
1530
|
+
const channel = new Command3("channel").description("Channel lifecycle and status commands");
|
|
983
1531
|
channel.command("list").option("--state <state>").option("--peer <peerId>").option("--include-closed").option("--json").action(async (options) => {
|
|
984
1532
|
const rpc = await createReadyRpcClient(config);
|
|
985
1533
|
const stateFilter = parseChannelState(options.state);
|
|
@@ -1400,14 +1948,14 @@ function createChannelCommand(config) {
|
|
|
1400
1948
|
}
|
|
1401
1949
|
|
|
1402
1950
|
// src/commands/config.ts
|
|
1403
|
-
import { existsSync as existsSync5, readdirSync, readFileSync as
|
|
1951
|
+
import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
1404
1952
|
import { homedir } from "os";
|
|
1405
1953
|
import { join as join5 } from "path";
|
|
1406
|
-
import { Command as
|
|
1954
|
+
import { Command as Command4 } from "commander";
|
|
1407
1955
|
import { parseDocument, stringify as yamlStringify } from "yaml";
|
|
1408
1956
|
|
|
1409
1957
|
// src/lib/config.ts
|
|
1410
|
-
import { existsSync as existsSync4, mkdirSync, readFileSync as
|
|
1958
|
+
import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
1411
1959
|
import { join as join4 } from "path";
|
|
1412
1960
|
|
|
1413
1961
|
// src/lib/config-templates.ts
|
|
@@ -1605,7 +2153,7 @@ function loadProfileConfig(dataDir) {
|
|
|
1605
2153
|
const profilePath = getProfilePath(dataDir);
|
|
1606
2154
|
if (!existsSync4(profilePath)) return void 0;
|
|
1607
2155
|
try {
|
|
1608
|
-
const raw =
|
|
2156
|
+
const raw = readFileSync4(profilePath, "utf-8");
|
|
1609
2157
|
return JSON.parse(raw);
|
|
1610
2158
|
} catch {
|
|
1611
2159
|
return void 0;
|
|
@@ -1661,7 +2209,7 @@ function getEffectiveConfig(explicitFlags2) {
|
|
|
1661
2209
|
const dataDirSource = explicitFlags2?.has("dataDir") ? "cli" : process.env.FIBER_DATA_DIR ? "env" : "default";
|
|
1662
2210
|
const configPath = getConfigPath(dataDir);
|
|
1663
2211
|
const configExists = existsSync4(configPath);
|
|
1664
|
-
const configContent = configExists ?
|
|
2212
|
+
const configContent = configExists ? readFileSync4(configPath, "utf-8") : void 0;
|
|
1665
2213
|
const profile = loadProfileConfig(dataDir);
|
|
1666
2214
|
const cliNetwork = explicitFlags2?.has("network") ? process.env.FIBER_NETWORK : void 0;
|
|
1667
2215
|
const envNetwork = !explicitFlags2?.has("network") ? process.env.FIBER_NETWORK : void 0;
|
|
@@ -1830,7 +2378,7 @@ function normalizeHexScalarsForMutation(content) {
|
|
|
1830
2378
|
);
|
|
1831
2379
|
}
|
|
1832
2380
|
function parseConfigDocumentForMutation(configPath) {
|
|
1833
|
-
const raw =
|
|
2381
|
+
const raw = readFileSync5(configPath, "utf-8");
|
|
1834
2382
|
const normalized = normalizeHexScalarsForMutation(raw);
|
|
1835
2383
|
return parseDocument(normalized, {
|
|
1836
2384
|
keepSourceTokens: true
|
|
@@ -1866,7 +2414,7 @@ function collectConfigPaths(value, prefix = "") {
|
|
|
1866
2414
|
return prefix ? [prefix] : [];
|
|
1867
2415
|
}
|
|
1868
2416
|
function createConfigCommand(_config) {
|
|
1869
|
-
const config = new
|
|
2417
|
+
const config = new Command4("config").description("Single source configuration management");
|
|
1870
2418
|
config.command("init").option(
|
|
1871
2419
|
"--data-dir <path>",
|
|
1872
2420
|
"Target data directory (overrides FIBER_DATA_DIR for this command)"
|
|
@@ -1960,7 +2508,7 @@ function createConfigCommand(_config) {
|
|
|
1960
2508
|
}
|
|
1961
2509
|
process.exit(1);
|
|
1962
2510
|
}
|
|
1963
|
-
const content =
|
|
2511
|
+
const content = readFileSync5(effective.config.configPath, "utf-8");
|
|
1964
2512
|
const fileNetwork = parseNetworkFromConfig(content) || "unknown";
|
|
1965
2513
|
if (options.json) {
|
|
1966
2514
|
printJsonSuccess({
|
|
@@ -2101,7 +2649,7 @@ function createConfigCommand(_config) {
|
|
|
2101
2649
|
}
|
|
2102
2650
|
}
|
|
2103
2651
|
});
|
|
2104
|
-
const profile = new
|
|
2652
|
+
const profile = new Command4("profile").description(
|
|
2105
2653
|
"Manage profile.json settings (CLI-only overrides)"
|
|
2106
2654
|
);
|
|
2107
2655
|
const PROFILE_KEYS = ["binaryPath", "keyPassword", "runtimeProxyListen"];
|
|
@@ -2231,7 +2779,7 @@ function createConfigCommand(_config) {
|
|
|
2231
2779
|
|
|
2232
2780
|
// src/commands/graph.ts
|
|
2233
2781
|
import { shannonsToCkb as shannonsToCkb3, toHex as toHex2 } from "@fiber-pay/sdk";
|
|
2234
|
-
import { Command as
|
|
2782
|
+
import { Command as Command5 } from "commander";
|
|
2235
2783
|
function printGraphNodeListHuman(nodes) {
|
|
2236
2784
|
if (nodes.length === 0) {
|
|
2237
2785
|
console.log("No nodes found in the graph.");
|
|
@@ -2273,7 +2821,7 @@ function printGraphChannelListHuman(channels) {
|
|
|
2273
2821
|
}
|
|
2274
2822
|
}
|
|
2275
2823
|
function createGraphCommand(config) {
|
|
2276
|
-
const graph = new
|
|
2824
|
+
const graph = new Command5("graph").description("Network graph queries (nodes & channels)");
|
|
2277
2825
|
graph.command("nodes").option("--limit <n>", "Maximum number of nodes to return", "50").option("--after <cursor>", "Pagination cursor from a previous query").option("--json").action(async (options) => {
|
|
2278
2826
|
const rpc = await createReadyRpcClient(config);
|
|
2279
2827
|
const limit = toHex2(BigInt(parseInt(options.limit, 10)));
|
|
@@ -2313,9 +2861,9 @@ Next cursor: ${result.last_cursor}`);
|
|
|
2313
2861
|
|
|
2314
2862
|
// src/commands/invoice.ts
|
|
2315
2863
|
import { ckbToShannons as ckbToShannons3, randomBytes32, shannonsToCkb as shannonsToCkb4, toHex as toHex3 } from "@fiber-pay/sdk";
|
|
2316
|
-
import { Command as
|
|
2864
|
+
import { Command as Command6 } from "commander";
|
|
2317
2865
|
function createInvoiceCommand(config) {
|
|
2318
|
-
const invoice = new
|
|
2866
|
+
const invoice = new Command6("invoice").description("Invoice lifecycle and status commands");
|
|
2319
2867
|
invoice.command("create").argument("[amount]").option("--amount <ckb>").option("--description <text>").option("--expiry <minutes>").option("--json").action(async (amountArg, options) => {
|
|
2320
2868
|
const rpc = await createReadyRpcClient(config);
|
|
2321
2869
|
const json = Boolean(options.json);
|
|
@@ -2531,7 +3079,7 @@ function createInvoiceCommand(config) {
|
|
|
2531
3079
|
|
|
2532
3080
|
// src/commands/job.ts
|
|
2533
3081
|
import { existsSync as existsSync7 } from "fs";
|
|
2534
|
-
import { Command as
|
|
3082
|
+
import { Command as Command7 } from "commander";
|
|
2535
3083
|
|
|
2536
3084
|
// src/lib/log-files.ts
|
|
2537
3085
|
import {
|
|
@@ -2570,8 +3118,8 @@ var LogWriter = class {
|
|
|
2570
3118
|
if (!this.stream) {
|
|
2571
3119
|
throw new Error("Failed to create write stream");
|
|
2572
3120
|
}
|
|
3121
|
+
const stream = this.stream;
|
|
2573
3122
|
return new Promise((resolve2, reject) => {
|
|
2574
|
-
const stream = this.stream;
|
|
2575
3123
|
if (this.waitingForDrain) {
|
|
2576
3124
|
const onDrain = () => {
|
|
2577
3125
|
this.waitingForDrain = false;
|
|
@@ -2885,7 +3433,7 @@ async function readAppendedLines(filePath, offset, remainder = "") {
|
|
|
2885
3433
|
|
|
2886
3434
|
// src/commands/job.ts
|
|
2887
3435
|
function createJobCommand(config) {
|
|
2888
|
-
const job = new
|
|
3436
|
+
const job = new Command7("job").description("Runtime job management commands");
|
|
2889
3437
|
job.command("list").option("--state <state>", "Filter by job state").option("--type <type>", "Filter by job type (payment|invoice|channel)").option("--limit <n>", "Limit number of jobs").option("--offset <n>", "Offset for pagination").option("--json").action(async (options) => {
|
|
2890
3438
|
const json = Boolean(options.json);
|
|
2891
3439
|
const runtimeUrl = getRuntimeUrlOrExit(config, json);
|
|
@@ -3215,11 +3763,163 @@ ${title}: ${filePath}`);
|
|
|
3215
3763
|
}
|
|
3216
3764
|
}
|
|
3217
3765
|
|
|
3766
|
+
// src/commands/l402.ts
|
|
3767
|
+
import { Command as Command8 } from "commander";
|
|
3768
|
+
|
|
3769
|
+
// src/lib/l402-proxy.ts
|
|
3770
|
+
import { createServer as createServer2 } from "http";
|
|
3771
|
+
import { createL402Middleware as createL402Middleware2, FiberRpcClient as FiberRpcClient3 } from "@fiber-pay/sdk";
|
|
3772
|
+
import express2 from "express";
|
|
3773
|
+
import { createProxyMiddleware } from "http-proxy-middleware";
|
|
3774
|
+
async function runL402ProxyCommand(config, options) {
|
|
3775
|
+
const asJson = Boolean(options.json);
|
|
3776
|
+
const port = parseInt(options.port, 10);
|
|
3777
|
+
const host = options.host;
|
|
3778
|
+
const priceCkb = parseFloat(options.price);
|
|
3779
|
+
const expirySeconds = parseInt(options.expiry, 10);
|
|
3780
|
+
const rootKey = options.rootKey || process.env.L402_ROOT_KEY;
|
|
3781
|
+
if (Number.isNaN(port) || port < 1 || port > 65535) {
|
|
3782
|
+
if (asJson) {
|
|
3783
|
+
printJsonError({
|
|
3784
|
+
code: "L402_PROXY_INVALID_PORT",
|
|
3785
|
+
message: `Invalid port: ${options.port}`,
|
|
3786
|
+
recoverable: true,
|
|
3787
|
+
suggestion: "Provide a valid port number between 1 and 65535."
|
|
3788
|
+
});
|
|
3789
|
+
} else {
|
|
3790
|
+
console.error(`Error: Invalid port: ${options.port}`);
|
|
3791
|
+
}
|
|
3792
|
+
process.exit(1);
|
|
3793
|
+
}
|
|
3794
|
+
if (Number.isNaN(priceCkb) || priceCkb <= 0) {
|
|
3795
|
+
if (asJson) {
|
|
3796
|
+
printJsonError({
|
|
3797
|
+
code: "L402_PROXY_INVALID_PRICE",
|
|
3798
|
+
message: `Invalid price: ${options.price}`,
|
|
3799
|
+
recoverable: true,
|
|
3800
|
+
suggestion: "Provide a positive CKB amount, e.g. --price 0.1"
|
|
3801
|
+
});
|
|
3802
|
+
} else {
|
|
3803
|
+
console.error(`Error: Invalid price: ${options.price}`);
|
|
3804
|
+
}
|
|
3805
|
+
process.exit(1);
|
|
3806
|
+
}
|
|
3807
|
+
if (!rootKey) {
|
|
3808
|
+
if (asJson) {
|
|
3809
|
+
printJsonError({
|
|
3810
|
+
code: "L402_PROXY_MISSING_ROOT_KEY",
|
|
3811
|
+
message: "L402 root key is required.",
|
|
3812
|
+
recoverable: true,
|
|
3813
|
+
suggestion: "Provide --root-key <64-hex-chars> or set L402_ROOT_KEY environment variable. Generate one with: openssl rand -hex 32"
|
|
3814
|
+
});
|
|
3815
|
+
} else {
|
|
3816
|
+
console.error("Error: L402 root key is required.");
|
|
3817
|
+
console.error(" Provide --root-key <64-hex-chars> or set L402_ROOT_KEY env var.");
|
|
3818
|
+
console.error(" Generate one with: openssl rand -hex 32");
|
|
3819
|
+
}
|
|
3820
|
+
process.exit(1);
|
|
3821
|
+
}
|
|
3822
|
+
const rpcClient = new FiberRpcClient3({
|
|
3823
|
+
url: config.rpcUrl,
|
|
3824
|
+
biscuitToken: config.rpcBiscuitToken
|
|
3825
|
+
});
|
|
3826
|
+
const currency = config.network === "mainnet" ? "Fibb" : "Fibt";
|
|
3827
|
+
const app = express2();
|
|
3828
|
+
app.use(
|
|
3829
|
+
createL402Middleware2({
|
|
3830
|
+
rootKey,
|
|
3831
|
+
priceCkb,
|
|
3832
|
+
expirySeconds,
|
|
3833
|
+
rpcClient,
|
|
3834
|
+
currency
|
|
3835
|
+
})
|
|
3836
|
+
);
|
|
3837
|
+
app.use(
|
|
3838
|
+
createProxyMiddleware({
|
|
3839
|
+
target: options.target,
|
|
3840
|
+
changeOrigin: true
|
|
3841
|
+
})
|
|
3842
|
+
);
|
|
3843
|
+
const server = createServer2(app);
|
|
3844
|
+
await new Promise((resolve2, reject) => {
|
|
3845
|
+
server.on("error", (err) => {
|
|
3846
|
+
if (err.code === "EADDRINUSE") {
|
|
3847
|
+
if (asJson) {
|
|
3848
|
+
printJsonError({
|
|
3849
|
+
code: "L402_PROXY_PORT_IN_USE",
|
|
3850
|
+
message: `Port ${port} is already in use.`,
|
|
3851
|
+
recoverable: true,
|
|
3852
|
+
suggestion: `Use a different port with --port <port>.`
|
|
3853
|
+
});
|
|
3854
|
+
} else {
|
|
3855
|
+
console.error(`Error: Port ${port} is already in use.`);
|
|
3856
|
+
}
|
|
3857
|
+
process.exit(1);
|
|
3858
|
+
}
|
|
3859
|
+
reject(err);
|
|
3860
|
+
});
|
|
3861
|
+
server.listen(port, host, () => {
|
|
3862
|
+
resolve2();
|
|
3863
|
+
});
|
|
3864
|
+
});
|
|
3865
|
+
const listenUrl = `http://${host}:${port}`;
|
|
3866
|
+
if (asJson) {
|
|
3867
|
+
printJsonSuccess({
|
|
3868
|
+
status: "running",
|
|
3869
|
+
listen: listenUrl,
|
|
3870
|
+
target: options.target,
|
|
3871
|
+
priceCkb,
|
|
3872
|
+
expirySeconds,
|
|
3873
|
+
currency,
|
|
3874
|
+
fiberRpcUrl: config.rpcUrl
|
|
3875
|
+
});
|
|
3876
|
+
} else {
|
|
3877
|
+
console.log("L402 proxy started");
|
|
3878
|
+
console.log(` Listen: ${listenUrl}`);
|
|
3879
|
+
console.log(` Target: ${options.target}`);
|
|
3880
|
+
console.log(` Price: ${priceCkb} CKB per request`);
|
|
3881
|
+
console.log(` Expiry: ${expirySeconds}s`);
|
|
3882
|
+
console.log(` Currency: ${currency}`);
|
|
3883
|
+
console.log(` Fiber RPC: ${config.rpcUrl}`);
|
|
3884
|
+
console.log("");
|
|
3885
|
+
console.log("Press Ctrl+C to stop.");
|
|
3886
|
+
}
|
|
3887
|
+
const shutdown = () => {
|
|
3888
|
+
if (!asJson) {
|
|
3889
|
+
console.log("\nStopping L402 proxy...");
|
|
3890
|
+
}
|
|
3891
|
+
server.close(() => {
|
|
3892
|
+
if (asJson) {
|
|
3893
|
+
printJsonSuccess({ status: "stopped" });
|
|
3894
|
+
} else {
|
|
3895
|
+
console.log("L402 proxy stopped.");
|
|
3896
|
+
}
|
|
3897
|
+
process.exit(0);
|
|
3898
|
+
});
|
|
3899
|
+
setTimeout(() => {
|
|
3900
|
+
process.exit(0);
|
|
3901
|
+
}, 5e3);
|
|
3902
|
+
};
|
|
3903
|
+
process.on("SIGINT", shutdown);
|
|
3904
|
+
process.on("SIGTERM", shutdown);
|
|
3905
|
+
await new Promise(() => {
|
|
3906
|
+
});
|
|
3907
|
+
}
|
|
3908
|
+
|
|
3909
|
+
// src/commands/l402.ts
|
|
3910
|
+
function createL402Command(config) {
|
|
3911
|
+
const l402 = new Command8("l402").description("L402 payment protocol commands");
|
|
3912
|
+
l402.command("proxy").description("Start a reverse proxy with L402 payment gating").requiredOption("--target <url>", "Upstream server URL to forward paid requests to").option("--port <port>", "Listen port", "8402").option("--host <host>", "Listen host", "127.0.0.1").option("--price <ckb>", "Price per request in CKB", "0.1").option("--root-key <hex>", "Macaroon root key (32-byte hex, or set L402_ROOT_KEY env)").option("--expiry <seconds>", "Token expiry in seconds", "3600").option("--json").action(async (options) => {
|
|
3913
|
+
await runL402ProxyCommand(config, options);
|
|
3914
|
+
});
|
|
3915
|
+
return l402;
|
|
3916
|
+
}
|
|
3917
|
+
|
|
3218
3918
|
// src/commands/logs.ts
|
|
3219
3919
|
import { existsSync as existsSync8, statSync as statSync2 } from "fs";
|
|
3220
3920
|
import { join as join8 } from "path";
|
|
3221
3921
|
import { formatRuntimeAlert } from "@fiber-pay/runtime";
|
|
3222
|
-
import { Command as
|
|
3922
|
+
import { Command as Command9 } from "commander";
|
|
3223
3923
|
var ALLOWED_SOURCES = /* @__PURE__ */ new Set([
|
|
3224
3924
|
"all",
|
|
3225
3925
|
"runtime",
|
|
@@ -3252,7 +3952,7 @@ function coerceJsonLineForOutput(source, line) {
|
|
|
3252
3952
|
return parseRuntimeAlertLine(line) ?? line;
|
|
3253
3953
|
}
|
|
3254
3954
|
function createLogsCommand(config) {
|
|
3255
|
-
return new
|
|
3955
|
+
return new Command9("logs").alias("log").description("View persisted runtime/fnn logs").option("--source <source>", "Log source: all|runtime|fnn-stdout|fnn-stderr", "all").option("--tail <n>", "Number of recent lines per source", "80").option("--date <YYYY-MM-DD>", "Date of log directory to read (default: today UTC)").option("--list-dates", "List available log dates and exit").option("--follow", "Keep streaming appended log lines (human output mode only)").option("--interval-ms <ms>", "Polling interval for --follow mode", "1000").option("--json").action(async (options) => {
|
|
3256
3956
|
const json = Boolean(options.json);
|
|
3257
3957
|
const follow = Boolean(options.follow);
|
|
3258
3958
|
const listDates = Boolean(options.listDates);
|
|
@@ -3507,7 +4207,7 @@ function inferDateFromPaths(paths) {
|
|
|
3507
4207
|
}
|
|
3508
4208
|
|
|
3509
4209
|
// src/commands/node.ts
|
|
3510
|
-
import { Command as
|
|
4210
|
+
import { Command as Command10 } from "commander";
|
|
3511
4211
|
|
|
3512
4212
|
// src/lib/node-info.ts
|
|
3513
4213
|
async function runNodeInfoCommand(config, options) {
|
|
@@ -3671,7 +4371,7 @@ function printNodeNetworkHuman(data) {
|
|
|
3671
4371
|
}
|
|
3672
4372
|
|
|
3673
4373
|
// src/lib/node-start.ts
|
|
3674
|
-
import { spawn } from "child_process";
|
|
4374
|
+
import { spawn as spawn2 } from "child_process";
|
|
3675
4375
|
import { mkdirSync as mkdirSync3 } from "fs";
|
|
3676
4376
|
import { join as join9 } from "path";
|
|
3677
4377
|
import {
|
|
@@ -3682,12 +4382,12 @@ import {
|
|
|
3682
4382
|
import { startRuntimeService } from "@fiber-pay/runtime";
|
|
3683
4383
|
|
|
3684
4384
|
// src/lib/bootnode.ts
|
|
3685
|
-
import { existsSync as existsSync9, readFileSync as
|
|
4385
|
+
import { existsSync as existsSync9, readFileSync as readFileSync6 } from "fs";
|
|
3686
4386
|
import { parse as parseYaml } from "yaml";
|
|
3687
4387
|
function extractBootnodeAddrs(configFilePath) {
|
|
3688
4388
|
if (!existsSync9(configFilePath)) return [];
|
|
3689
4389
|
try {
|
|
3690
|
-
const content =
|
|
4390
|
+
const content = readFileSync6(configFilePath, "utf-8");
|
|
3691
4391
|
const doc = parseYaml(content);
|
|
3692
4392
|
const addrs = doc?.fiber?.bootnode_addrs;
|
|
3693
4393
|
if (!Array.isArray(addrs)) return [];
|
|
@@ -3942,7 +4642,7 @@ async function runNodeStartCommand(config, options) {
|
|
|
3942
4642
|
process.exit(1);
|
|
3943
4643
|
}
|
|
3944
4644
|
const childArgs = process.argv.slice(2).filter((arg) => arg !== "--daemon");
|
|
3945
|
-
const child =
|
|
4645
|
+
const child = spawn2(process.execPath, [cliEntrypoint, ...childArgs], {
|
|
3946
4646
|
detached: true,
|
|
3947
4647
|
stdio: "ignore",
|
|
3948
4648
|
cwd: process.cwd(),
|
|
@@ -4331,7 +5031,7 @@ async function runNodeStartCommand(config, options) {
|
|
|
4331
5031
|
flushTimeout = setTimeout(() => reject(new Error("Flush timeout")), 5e3);
|
|
4332
5032
|
})
|
|
4333
5033
|
]);
|
|
4334
|
-
} catch (
|
|
5034
|
+
} catch (_err) {
|
|
4335
5035
|
if (!json) {
|
|
4336
5036
|
console.log("\u26A0\uFE0F Log flush timed out, continuing shutdown...");
|
|
4337
5037
|
}
|
|
@@ -5226,7 +5926,7 @@ async function runMigrationAndReport(opts) {
|
|
|
5226
5926
|
|
|
5227
5927
|
// src/commands/node.ts
|
|
5228
5928
|
function createNodeCommand(config) {
|
|
5229
|
-
const node = new
|
|
5929
|
+
const node = new Command10("node").description("Node management");
|
|
5230
5930
|
node.command("start").option("--daemon", "Start node in detached background mode (node + runtime)").option("--runtime-proxy-listen <host:port>", "Runtime monitor proxy listen address").option("--event-stream <format>", "Event stream format for --json mode (jsonl)", "jsonl").option("--quiet-fnn", "Do not mirror fnn stdout/stderr to console; keep file persistence").option("--json").action(async (options) => {
|
|
5231
5931
|
await runNodeStartCommand(config, options);
|
|
5232
5932
|
});
|
|
@@ -5256,9 +5956,9 @@ function createNodeCommand(config) {
|
|
|
5256
5956
|
|
|
5257
5957
|
// src/commands/payment.ts
|
|
5258
5958
|
import { ckbToShannons as ckbToShannons4, shannonsToCkb as shannonsToCkb6 } from "@fiber-pay/sdk";
|
|
5259
|
-
import { Command as
|
|
5959
|
+
import { Command as Command11 } from "commander";
|
|
5260
5960
|
function createPaymentCommand(config) {
|
|
5261
|
-
const payment = new
|
|
5961
|
+
const payment = new Command11("payment").description("Payment lifecycle and status commands");
|
|
5262
5962
|
payment.command("send").argument("[invoice]").option("--invoice <invoice>").option("--to <nodeId>").option("--amount <ckb>").option("--max-fee <ckb>").option("--wait", "Wait for runtime job terminal status when runtime proxy is active").option("--timeout <seconds>", "Wait timeout for --wait mode", "120").option("--json").action(async (invoiceArg, options) => {
|
|
5263
5963
|
const rpc = await createReadyRpcClient(config);
|
|
5264
5964
|
const json = Boolean(options.json);
|
|
@@ -5592,7 +6292,7 @@ function getJobFailure(job) {
|
|
|
5592
6292
|
}
|
|
5593
6293
|
|
|
5594
6294
|
// src/commands/peer.ts
|
|
5595
|
-
import { Command as
|
|
6295
|
+
import { Command as Command12 } from "commander";
|
|
5596
6296
|
function extractPeerIdFromMultiaddr(address) {
|
|
5597
6297
|
const match = address.match(/\/p2p\/([^/]+)$/);
|
|
5598
6298
|
return match?.[1];
|
|
@@ -5609,7 +6309,7 @@ async function waitForPeerConnected(rpc, peerId, timeoutMs) {
|
|
|
5609
6309
|
return false;
|
|
5610
6310
|
}
|
|
5611
6311
|
function createPeerCommand(config) {
|
|
5612
|
-
const peer = new
|
|
6312
|
+
const peer = new Command12("peer").description("Peer management");
|
|
5613
6313
|
peer.command("list").option("--json").action(async (options) => {
|
|
5614
6314
|
const rpc = await createReadyRpcClient(config);
|
|
5615
6315
|
const peers = await rpc.listPeers();
|
|
@@ -5655,7 +6355,7 @@ function createPeerCommand(config) {
|
|
|
5655
6355
|
}
|
|
5656
6356
|
|
|
5657
6357
|
// src/commands/runtime.ts
|
|
5658
|
-
import { spawn as
|
|
6358
|
+
import { spawn as spawn3 } from "child_process";
|
|
5659
6359
|
import { join as join10, resolve } from "path";
|
|
5660
6360
|
import {
|
|
5661
6361
|
alertPriorityOrder,
|
|
@@ -5664,7 +6364,7 @@ import {
|
|
|
5664
6364
|
isAlertType,
|
|
5665
6365
|
startRuntimeService as startRuntimeService2
|
|
5666
6366
|
} from "@fiber-pay/runtime";
|
|
5667
|
-
import { Command as
|
|
6367
|
+
import { Command as Command13 } from "commander";
|
|
5668
6368
|
|
|
5669
6369
|
// src/lib/parse-options.ts
|
|
5670
6370
|
function parseIntegerOption(value, name) {
|
|
@@ -5735,7 +6435,7 @@ function resolveRuntimeRecoveryListen(config) {
|
|
|
5735
6435
|
return meta?.proxyListen ?? config.runtimeProxyListen ?? "127.0.0.1:8229";
|
|
5736
6436
|
}
|
|
5737
6437
|
function createRuntimeCommand(config) {
|
|
5738
|
-
const runtime = new
|
|
6438
|
+
const runtime = new Command13("runtime").description("Polling monitor and alert runtime service");
|
|
5739
6439
|
runtime.command("start").description("Start runtime monitor service in foreground").option("--daemon", "Start runtime monitor in detached background mode").option("--fiber-rpc-url <url>", "Target fiber rpc URL (defaults to --rpc-url/global config)").option("--proxy-listen <host:port>", "Monitor proxy listen address").option("--channel-poll-ms <ms>", "Channel polling interval in milliseconds").option("--invoice-poll-ms <ms>", "Invoice polling interval in milliseconds").option("--payment-poll-ms <ms>", "Payment polling interval in milliseconds").option("--peer-poll-ms <ms>", "Peer polling interval in milliseconds").option("--health-poll-ms <ms>", "RPC health polling interval in milliseconds").option("--include-closed <bool>", "Monitor closed channels (true|false)").option("--completed-ttl-seconds <seconds>", "TTL for completed invoices/payments in tracker").option("--state-file <path>", "State file path for snapshots and history").option("--alert-log-file <path>", "Path to runtime alert JSONL log file (legacy static path)").option("--alert-logs-base-dir <dir>", "Base logs directory for daily-rotated alert files").option("--flush-ms <ms>", "State flush interval in milliseconds").option("--webhook <url>", "Webhook URL to receive alert POST payloads").option("--websocket <host:port>", "WebSocket alert broadcast listen address").option(
|
|
5740
6440
|
"--log-min-priority <priority>",
|
|
5741
6441
|
"Minimum runtime log priority (critical|high|medium|low)"
|
|
@@ -5793,7 +6493,7 @@ function createRuntimeCommand(config) {
|
|
|
5793
6493
|
}
|
|
5794
6494
|
if (daemon && !isRuntimeChild) {
|
|
5795
6495
|
const childArgv = process.argv.filter((arg) => arg !== "--daemon");
|
|
5796
|
-
const child =
|
|
6496
|
+
const child = spawn3(process.execPath, childArgv.slice(1), {
|
|
5797
6497
|
detached: true,
|
|
5798
6498
|
stdio: "ignore",
|
|
5799
6499
|
cwd: process.cwd(),
|
|
@@ -6144,15 +6844,15 @@ function createRuntimeCommand(config) {
|
|
|
6144
6844
|
}
|
|
6145
6845
|
|
|
6146
6846
|
// src/commands/version.ts
|
|
6147
|
-
import { Command as
|
|
6847
|
+
import { Command as Command14 } from "commander";
|
|
6148
6848
|
|
|
6149
6849
|
// src/lib/build-info.ts
|
|
6150
|
-
var CLI_VERSION = "0.1.
|
|
6151
|
-
var CLI_COMMIT = "
|
|
6850
|
+
var CLI_VERSION = "0.1.1";
|
|
6851
|
+
var CLI_COMMIT = "4b099d434842d019d12ba4df4031314860de25be";
|
|
6152
6852
|
|
|
6153
6853
|
// src/commands/version.ts
|
|
6154
6854
|
function createVersionCommand() {
|
|
6155
|
-
return new
|
|
6855
|
+
return new Command14("version").description("Show CLI version and commit id").option("--json", "Output JSON").action((options) => {
|
|
6156
6856
|
const payload = {
|
|
6157
6857
|
version: CLI_VERSION,
|
|
6158
6858
|
commit: CLI_COMMIT
|
|
@@ -6167,7 +6867,7 @@ function createVersionCommand() {
|
|
|
6167
6867
|
}
|
|
6168
6868
|
|
|
6169
6869
|
// src/commands/wallet.ts
|
|
6170
|
-
import { Command as
|
|
6870
|
+
import { Command as Command15, Option } from "commander";
|
|
6171
6871
|
|
|
6172
6872
|
// src/lib/wallet-address.ts
|
|
6173
6873
|
import { scriptToAddress as scriptToAddress2 } from "@fiber-pay/sdk";
|
|
@@ -6222,7 +6922,7 @@ async function runWalletBalanceCommand(config, options) {
|
|
|
6222
6922
|
|
|
6223
6923
|
// src/commands/wallet.ts
|
|
6224
6924
|
function createWalletCommand(config) {
|
|
6225
|
-
const wallet = new
|
|
6925
|
+
const wallet = new Command15("wallet").description("Wallet management");
|
|
6226
6926
|
wallet.command("address").description("Display the funding address").addOption(new Option("--json", "Output as JSON").conflicts("qrcode")).option("--qrcode", "Display address as QR code in terminal").action(async (options) => {
|
|
6227
6927
|
await runWalletAddressCommand(config, options);
|
|
6228
6928
|
});
|
|
@@ -6397,7 +7097,7 @@ async function main() {
|
|
|
6397
7097
|
}
|
|
6398
7098
|
applyGlobalOverrides(argv);
|
|
6399
7099
|
const config = getEffectiveConfig(explicitFlags).config;
|
|
6400
|
-
const program = new
|
|
7100
|
+
const program = new Command16();
|
|
6401
7101
|
program.name("fiber-pay").description("AI Agent Payment SDK for CKB Lightning Network").option("--profile <name>", "Use profile at ~/.fiber-pay/profiles/<name>").option("--data-dir <path>", "Override data directory for all commands").option("--rpc-url <url>", "Override RPC URL for all commands").option("--rpc-biscuit-token <token>", "Set RPC Authorization Bearer token for all commands").option("--network <network>", "Override network for all commands (testnet|mainnet)").option("--key-password <password>", "Override key password for all commands").option("--binary-path <path>", "Override fiber binary path for all commands").showHelpAfterError().showSuggestionAfterError();
|
|
6402
7102
|
program.exitOverride();
|
|
6403
7103
|
program.configureOutput({
|
|
@@ -6409,6 +7109,7 @@ async function main() {
|
|
|
6409
7109
|
}
|
|
6410
7110
|
});
|
|
6411
7111
|
program.addCommand(createNodeCommand(config));
|
|
7112
|
+
program.addCommand(createAgentCommand(config));
|
|
6412
7113
|
program.addCommand(createChannelCommand(config));
|
|
6413
7114
|
program.addCommand(createInvoiceCommand(config));
|
|
6414
7115
|
program.addCommand(createPaymentCommand(config));
|
|
@@ -6418,6 +7119,7 @@ async function main() {
|
|
|
6418
7119
|
program.addCommand(createGraphCommand(config));
|
|
6419
7120
|
program.addCommand(createBinaryCommand(config));
|
|
6420
7121
|
program.addCommand(createConfigCommand(config));
|
|
7122
|
+
program.addCommand(createL402Command(config));
|
|
6421
7123
|
program.addCommand(createRuntimeCommand(config));
|
|
6422
7124
|
program.addCommand(createVersionCommand());
|
|
6423
7125
|
program.addCommand(createWalletCommand(config));
|