@arp-protocol/cli 0.0.0-stage → 0.0.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.
@@ -0,0 +1,593 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ resolveNodeKey
4
+ } from "./cli-zpft011j.js";
5
+ import {
6
+ appendJsonLine,
7
+ bigintSafe,
8
+ jsonResponse,
9
+ readJsonLines,
10
+ serveFetch,
11
+ stateFile
12
+ } from "./cli-tyey74p3.js";
13
+ import {
14
+ HIRE_RECEIPT_VERSION,
15
+ createPublicClient,
16
+ hireDigest,
17
+ http,
18
+ normaliseEndpoint,
19
+ parseEther,
20
+ signHireReceipt
21
+ } from "./cli-74hv4etk.js";
22
+ import"./cli-c8568g4c.js";
23
+ import {
24
+ ERC8004_REGISTRY
25
+ } from "./cli-v446mw5v.js";
26
+ import {
27
+ formatEther,
28
+ isAddress
29
+ } from "./cli-0hqxver6.js";
30
+ import"./cli-d3dy7cxe.js";
31
+ import"./cli-3kbbwfbp.js";
32
+ import"./cli-g5zvr10v.js";
33
+
34
+ // ../node/src/env.ts
35
+ function portFrom(raw, fallback) {
36
+ if (raw === undefined || raw.trim() === "")
37
+ return fallback;
38
+ const port = Number(raw);
39
+ if (!/^[0-9]+$/.test(raw.trim()) || port < 1 || port > 65535) {
40
+ throw new Error(`AGENT_NODE_PORT must be a port between 1 and 65535, got ${raw}`);
41
+ }
42
+ return port;
43
+ }
44
+ function minBudgetFrom(raw, fallback) {
45
+ const value = raw === undefined || raw.trim() === "" ? fallback : raw.trim();
46
+ if (!/^(0|[1-9][0-9]*)(\.[0-9]{1,18})?$/.test(value)) {
47
+ throw new Error(`AGENT_NODE_MIN_BUDGET must be a decimal amount such as 0.001, got ${value}`);
48
+ }
49
+ return parseEther(value);
50
+ }
51
+
52
+ // ../node/src/task.ts
53
+ var MAX_OFFER_FIELD = 500;
54
+ function readOffer(value) {
55
+ if (typeof value !== "object" || value === null)
56
+ return null;
57
+ const { description, input, output } = value;
58
+ const fields = [description, input, output];
59
+ if (!fields.every((field) => typeof field === "string" && field.length > 0 && field.length <= MAX_OFFER_FIELD)) {
60
+ return null;
61
+ }
62
+ return { description, input, output };
63
+ }
64
+ var echo = async (payload, context) => ({
65
+ echoed: payload,
66
+ note: "No handler configured, so this node proves the protocol and nothing about the agent. Set " + "ARP_NODE_HANDLER to a module exporting `handle(payload, context)`.",
67
+ paidWei: context.paid.toString()
68
+ });
69
+
70
+ class PaymentLedger {
71
+ path;
72
+ spent = new Set;
73
+ pending = new Set;
74
+ constructor(path) {
75
+ this.path = path;
76
+ if (path === null)
77
+ return;
78
+ for (const row of readJsonLines(path)) {
79
+ const hash = row?.paymentTxHash;
80
+ if (typeof hash === "string")
81
+ this.spent.add(hash.toLowerCase());
82
+ }
83
+ }
84
+ isSpent(hash) {
85
+ const key = hash.toLowerCase();
86
+ return this.spent.has(key) || this.pending.has(key);
87
+ }
88
+ claim(hash) {
89
+ if (this.isSpent(hash))
90
+ return false;
91
+ this.pending.add(hash.toLowerCase());
92
+ return true;
93
+ }
94
+ release(hash) {
95
+ this.pending.delete(hash.toLowerCase());
96
+ }
97
+ commit(hash, record) {
98
+ const key = hash.toLowerCase();
99
+ if (this.path !== null)
100
+ appendJsonLine(this.path, { paymentTxHash: key, ...record });
101
+ this.pending.delete(key);
102
+ this.spent.add(key);
103
+ }
104
+ }
105
+ var DEFAULT_LIMITS = {
106
+ maxBodyBytes: 64 * 1024,
107
+ handlerTimeoutMs: 60000,
108
+ requestsPerMinute: 30,
109
+ receiptTtlSeconds: 86400
110
+ };
111
+ var TX_HASH = /^0x[0-9a-fA-F]{64}$/;
112
+ var RATE_WINDOW_MS = 60000;
113
+ var RATE_SWEEP_AT = 1e4;
114
+ var json = jsonResponse;
115
+
116
+ class Timeout extends Error {
117
+ }
118
+ function withTimeout(work, ms) {
119
+ let timer;
120
+ const expiry = new Promise((_resolve, reject) => {
121
+ timer = setTimeout(() => reject(new Timeout), ms);
122
+ });
123
+ return Promise.race([work, expiry]).finally(() => clearTimeout(timer));
124
+ }
125
+ function createNode(options) {
126
+ const { account, publicClient, chainId, identity, minBudget, handle, offer = null, ledger } = options;
127
+ const limits = { ...DEFAULT_LIMITS, ...options.limits };
128
+ const now = options.now ?? Date.now;
129
+ const windows = new Map;
130
+ function overLimit(client) {
131
+ const at = now();
132
+ if (windows.size > RATE_SWEEP_AT) {
133
+ for (const [key, window2] of windows) {
134
+ if (at - window2.start >= RATE_WINDOW_MS)
135
+ windows.delete(key);
136
+ }
137
+ }
138
+ const window = windows.get(client);
139
+ if (window === undefined || at - window.start >= RATE_WINDOW_MS) {
140
+ windows.set(client, { start: at, count: 1 });
141
+ return false;
142
+ }
143
+ window.count += 1;
144
+ return window.count > limits.requestsPerMinute;
145
+ }
146
+ async function verifyPayment(hash, requester) {
147
+ const tx = await publicClient.getTransaction({ hash }).catch(() => null);
148
+ if (tx === null)
149
+ return { error: `payment tx ${hash} not found` };
150
+ const receipt = await publicClient.getTransactionReceipt({ hash }).catch(() => null);
151
+ if (receipt === null)
152
+ return { error: `payment tx ${hash} is not mined yet` };
153
+ if (receipt.status !== "success")
154
+ return { error: `payment tx ${hash} reverted` };
155
+ if (!tx.to || tx.to.toLowerCase() !== account.address.toLowerCase()) {
156
+ return { error: `payment recipient ${tx.to ?? "none"} is not this agent ${account.address}` };
157
+ }
158
+ if (tx.value < minBudget) {
159
+ return {
160
+ error: `payment ${formatEther(tx.value)} is below this agent's minimum ${formatEther(minBudget)}`
161
+ };
162
+ }
163
+ if (tx.from.toLowerCase() !== requester.toLowerCase()) {
164
+ return { error: `payment came from ${tx.from}, not from the declared requester` };
165
+ }
166
+ return { requester, paid: tx.value, paymentTxHash: hash, agent: account.address };
167
+ }
168
+ async function readBody(request) {
169
+ const declared = Number(request.headers.get("content-length") ?? "0");
170
+ if (declared > limits.maxBodyBytes) {
171
+ return json({ error: `body over ${limits.maxBodyBytes} bytes` }, 413);
172
+ }
173
+ const text = await request.text();
174
+ if (new TextEncoder().encode(text).byteLength > limits.maxBodyBytes) {
175
+ return json({ error: `body over ${limits.maxBodyBytes} bytes` }, 413);
176
+ }
177
+ try {
178
+ const parsed = JSON.parse(text);
179
+ if (typeof parsed !== "object" || parsed === null) {
180
+ return json({ error: "body is not a JSON object" }, 400);
181
+ }
182
+ return parsed;
183
+ } catch {
184
+ return json({ error: "body is not JSON" }, 400);
185
+ }
186
+ }
187
+ return {
188
+ async fetch(request, client = "unknown") {
189
+ const url = new URL(request.url);
190
+ if (request.method === "GET" && url.pathname === "/healthz") {
191
+ return json({ ok: true, agent: account.address, minBudgetWei: minBudget.toString() });
192
+ }
193
+ if (request.method === "GET" && url.pathname === "/") {
194
+ return json({
195
+ agent: account.address,
196
+ endpoints: [{ path: "/task", method: "POST", description: "hire this agent" }],
197
+ minBudgetWei: minBudget.toString(),
198
+ offer
199
+ });
200
+ }
201
+ if (request.method !== "POST" || url.pathname !== "/task") {
202
+ return json({ error: "POST /task, or GET /healthz" }, 404);
203
+ }
204
+ if (overLimit(client)) {
205
+ return json({ error: `more than ${limits.requestsPerMinute} requests a minute` }, 429);
206
+ }
207
+ const body = await readBody(request);
208
+ if (body instanceof Response)
209
+ return body;
210
+ const { paymentTxHash: hash, requesterAddress: requester } = body;
211
+ if (typeof hash !== "string" || !TX_HASH.test(hash)) {
212
+ return json({ error: "paymentTxHash must be a 32-byte 0x-prefixed hex hash" }, 400);
213
+ }
214
+ if (typeof requester !== "string" || !isAddress(requester)) {
215
+ return json({ error: "requesterAddress must be an address" }, 400);
216
+ }
217
+ const { verifier, termId } = body;
218
+ if (typeof verifier !== "string" || !isAddress(verifier)) {
219
+ return json({ error: "verifier must be the address the receipt is for (an enforcer or a service)" }, 400);
220
+ }
221
+ if (typeof termId !== "string" || !TX_HASH.test(termId)) {
222
+ return json({ error: "termId must be the 32-byte term the requester will deposit on" }, 400);
223
+ }
224
+ const payment = hash;
225
+ const spentError = () => json({ error: `payment tx ${payment} was already used for a job` }, 409);
226
+ if (ledger.isSpent(payment))
227
+ return spentError();
228
+ const verified = await verifyPayment(payment, requester);
229
+ if ("error" in verified)
230
+ return json(verified, 402);
231
+ if (!ledger.claim(payment))
232
+ return spentError();
233
+ let result;
234
+ let requestHash;
235
+ let resultHash;
236
+ const abort = new AbortController;
237
+ const work = Promise.resolve().then(() => handle(body.payload, { ...verified, signal: abort.signal }));
238
+ try {
239
+ const answer = await withTimeout(work, limits.handlerTimeoutMs);
240
+ result = JSON.parse(JSON.stringify(answer ?? null, bigintSafe));
241
+ requestHash = hireDigest(body.payload ?? null);
242
+ resultHash = hireDigest(result);
243
+ } catch (error) {
244
+ if (error instanceof Timeout) {
245
+ abort.abort();
246
+ work.then(() => ledger.release(payment), () => ledger.release(payment));
247
+ return json({ error: `handler did not answer within ${limits.handlerTimeoutMs} ms` }, 504);
248
+ }
249
+ ledger.release(payment);
250
+ return json({ error: `handler failed: ${String(error).slice(0, 300)}` }, 500);
251
+ }
252
+ const issuedAt = Math.floor(now() / 1000);
253
+ const receipt = {
254
+ version: HIRE_RECEIPT_VERSION,
255
+ agentRegistry: identity?.agentRegistry ?? "",
256
+ agentId: (identity?.agentId ?? 0n).toString(),
257
+ requester: verified.requester,
258
+ payment,
259
+ paid: verified.paid.toString(),
260
+ requestHash,
261
+ resultHash,
262
+ issuedAt,
263
+ termId,
264
+ expiresAt: issuedAt + limits.receiptTtlSeconds
265
+ };
266
+ try {
267
+ ledger.commit(payment, {
268
+ requester: verified.requester,
269
+ requestHash,
270
+ at: new Date(now()).toISOString()
271
+ });
272
+ } catch (error) {
273
+ ledger.release(payment);
274
+ return json({ error: `could not record the payment: ${String(error).slice(0, 300)}` }, 500);
275
+ }
276
+ const signed = await signHireReceipt(receipt, { chainId, verifyingContract: verifier }, {
277
+ address: account.address,
278
+ signTypedData: (data) => account.signTypedData(data)
279
+ });
280
+ return json({ result, receipt: signed });
281
+ }
282
+ };
283
+ }
284
+
285
+ // ../node/src/tunnel.ts
286
+ import { spawn as spawnChild } from "node:child_process";
287
+ import { accessSync, constants, statSync } from "node:fs";
288
+ import { delimiter, join } from "node:path";
289
+ import { createInterface } from "node:readline";
290
+ var DEFAULT_TUNNEL_TIMEOUT_MS = 30000;
291
+ function isExecutable(path) {
292
+ try {
293
+ if (!statSync(path).isFile())
294
+ return false;
295
+ accessSync(path, constants.X_OK);
296
+ return true;
297
+ } catch {
298
+ return false;
299
+ }
300
+ }
301
+ function findOnPath(name, path, executable) {
302
+ for (const dir of (path ?? "").split(delimiter)) {
303
+ if (dir.length === 0)
304
+ continue;
305
+ const candidate = join(dir, name);
306
+ if (executable(candidate))
307
+ return candidate;
308
+ }
309
+ return null;
310
+ }
311
+ function chooseTunnel(env, executable = isExecutable) {
312
+ const mode = (env["ARP_NODE_TUNNEL"] ?? "auto").trim().toLowerCase();
313
+ const configured = () => {
314
+ const raw = env["ARP_NODE_PUBLIC_URL"];
315
+ if (raw === undefined || raw.trim() === "") {
316
+ return new Error("ARP_NODE_TUNNEL=configured needs ARP_NODE_PUBLIC_URL");
317
+ }
318
+ const publicUrl = normaliseEndpoint(raw);
319
+ if (publicUrl === null) {
320
+ return new Error(`ARP_NODE_PUBLIC_URL must be an https URL without credentials, got ${raw}`);
321
+ }
322
+ return { kind: "configured", publicUrl };
323
+ };
324
+ const binary = (kind) => {
325
+ const found = findOnPath(kind, env["PATH"], executable);
326
+ return found === null ? new Error(`${kind} is not on PATH`) : { kind, binary: found };
327
+ };
328
+ switch (mode) {
329
+ case "none":
330
+ return { kind: "none" };
331
+ case "configured":
332
+ return configured();
333
+ case "cloudflared":
334
+ case "ngrok":
335
+ return binary(mode);
336
+ case "auto": {
337
+ if (env["ARP_NODE_PUBLIC_URL"] !== undefined && env["ARP_NODE_PUBLIC_URL"].trim() !== "") {
338
+ return configured();
339
+ }
340
+ const cloudflared = binary("cloudflared");
341
+ if (!(cloudflared instanceof Error))
342
+ return cloudflared;
343
+ const ngrok = binary("ngrok");
344
+ if (!(ngrok instanceof Error))
345
+ return ngrok;
346
+ return new Error("no tunnel: install cloudflared or ngrok, or set ARP_NODE_PUBLIC_URL to a tunnel you run. " + "ARP_NODE_TUNNEL=none serves localhost only.");
347
+ }
348
+ default:
349
+ return new Error(`ARP_NODE_TUNNEL must be auto, cloudflared, ngrok, configured or none, got ${mode}`);
350
+ }
351
+ }
352
+ function tunnelArgs(kind, localUrl) {
353
+ return kind === "cloudflared" ? ["tunnel", "--no-autoupdate", "--url", localUrl] : ["http", localUrl, "--log", "stdout", "--log-format", "json"];
354
+ }
355
+ var QUICK_TUNNEL = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/;
356
+ function publicUrlFrom(kind, line) {
357
+ if (kind === "cloudflared")
358
+ return QUICK_TUNNEL.exec(line)?.[0] ?? null;
359
+ let parsed;
360
+ try {
361
+ parsed = JSON.parse(line);
362
+ } catch {
363
+ return null;
364
+ }
365
+ if (typeof parsed !== "object" || parsed === null)
366
+ return null;
367
+ const url = parsed["url"];
368
+ return typeof url === "string" ? normaliseEndpoint(url) : null;
369
+ }
370
+ var LOOPBACK = new Set(["127.0.0.1", "::1", "::ffff:127.0.0.1"]);
371
+ function clientAddress(request, socketIp, tunnel) {
372
+ if (!LOOPBACK.has(socketIp))
373
+ return socketIp;
374
+ if (tunnel === "cloudflared") {
375
+ const cloudflare = request.headers.get("cf-connecting-ip")?.trim();
376
+ if (cloudflare !== undefined && cloudflare !== "")
377
+ return cloudflare;
378
+ }
379
+ if (tunnel === "ngrok") {
380
+ const forwarded = request.headers.get("x-forwarded-for")?.split(",").at(-1)?.trim();
381
+ if (forwarded !== undefined && forwarded !== "")
382
+ return forwarded;
383
+ }
384
+ return socketIp;
385
+ }
386
+ var spawnProcess = (command, args) => {
387
+ const child = spawnChild(command, [...args], { stdio: ["ignore", "pipe", "pipe"] });
388
+ const lineListeners = [];
389
+ for (const stream of [child.stdout, child.stderr]) {
390
+ createInterface({ input: stream }).on("line", (line) => {
391
+ for (const listener of lineListeners)
392
+ listener(line);
393
+ });
394
+ }
395
+ return {
396
+ onLine: (listener) => lineListeners.push(listener),
397
+ onExit: (listener) => child.on("exit", (code) => listener(code)),
398
+ kill: () => child.kill("SIGTERM")
399
+ };
400
+ };
401
+ function startTunnel(spec, localUrl, options = {}) {
402
+ if (spec.kind === "none") {
403
+ return Promise.resolve({ spec, publicUrl: null, stable: false, stop: () => {} });
404
+ }
405
+ if (spec.kind === "configured") {
406
+ return Promise.resolve({ spec, publicUrl: spec.publicUrl, stable: true, stop: () => {} });
407
+ }
408
+ const { kind } = spec;
409
+ const child = (options.spawn ?? spawnProcess)(spec.binary, tunnelArgs(kind, localUrl));
410
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TUNNEL_TIMEOUT_MS;
411
+ return new Promise((resolve, reject) => {
412
+ let state = "pending";
413
+ const fail = (message) => {
414
+ if (state !== "pending")
415
+ return;
416
+ state = "failed";
417
+ clearTimeout(timer);
418
+ child.kill();
419
+ reject(new Error(message));
420
+ };
421
+ const timer = setTimeout(() => fail(`${kind} printed no public URL within ${timeoutMs} ms`), timeoutMs);
422
+ child.onLine((line) => {
423
+ if (state !== "pending")
424
+ return;
425
+ const publicUrl = publicUrlFrom(kind, line);
426
+ if (publicUrl === null)
427
+ return;
428
+ state = "up";
429
+ clearTimeout(timer);
430
+ const stop = () => {
431
+ state = "stopped";
432
+ child.kill();
433
+ };
434
+ resolve({ spec, publicUrl, stable: false, stop });
435
+ });
436
+ child.onExit((code) => {
437
+ if (state === "up")
438
+ options.onDown?.(code);
439
+ else
440
+ fail(`${kind} exited (${code ?? "signal"}) before printing a public URL`);
441
+ });
442
+ });
443
+ }
444
+
445
+ // ../node/src/node.ts
446
+ var PORT;
447
+ var MIN_BUDGET;
448
+ try {
449
+ PORT = portFrom(process.env.AGENT_NODE_PORT, 3100);
450
+ MIN_BUDGET = minBudgetFrom(process.env.AGENT_NODE_MIN_BUDGET, "0.001");
451
+ } catch (error) {
452
+ console.error(error instanceof Error ? error.message : String(error));
453
+ process.exit(1);
454
+ }
455
+ var LEDGER;
456
+ try {
457
+ LEDGER = stateFile(process.env.AGENT_NODE_LEDGER, "node-payments.jsonl");
458
+ } catch (error) {
459
+ console.error(error instanceof Error ? error.message : String(error));
460
+ process.exit(1);
461
+ }
462
+ function positive(name, fallback) {
463
+ const raw = process.env[name];
464
+ if (raw === undefined)
465
+ return fallback;
466
+ const value = Number(raw);
467
+ if (!Number.isInteger(value) || value <= 0) {
468
+ console.error(`${name} must be a positive integer, got ${raw}`);
469
+ process.exit(1);
470
+ }
471
+ return value;
472
+ }
473
+ var LIMITS = {
474
+ maxBodyBytes: positive("AGENT_NODE_MAX_BODY_BYTES", DEFAULT_LIMITS.maxBodyBytes),
475
+ handlerTimeoutMs: positive("AGENT_NODE_HANDLER_TIMEOUT_MS", DEFAULT_LIMITS.handlerTimeoutMs),
476
+ requestsPerMinute: positive("AGENT_NODE_RATE_LIMIT", DEFAULT_LIMITS.requestsPerMinute),
477
+ receiptTtlSeconds: positive("AGENT_NODE_RECEIPT_TTL", DEFAULT_LIMITS.receiptTtlSeconds)
478
+ };
479
+ var rpcUrl = process.env.INTUITION_RPC_URL ?? process.env.INTUITION_TESTNET_RPC_URL;
480
+ if (!rpcUrl) {
481
+ console.error("no RPC: set INTUITION_RPC_URL or INTUITION_TESTNET_RPC_URL");
482
+ process.exit(1);
483
+ }
484
+ var nodeKey;
485
+ try {
486
+ nodeKey = resolveNodeKey(process.env);
487
+ } catch (error) {
488
+ console.error(error instanceof Error ? error.message : String(error));
489
+ process.exit(1);
490
+ }
491
+ var account = nodeKey.account;
492
+ function identityFromEnv() {
493
+ const registry = process.env.AGENT_REGISTRY;
494
+ const id = process.env.AGENT_ID;
495
+ if (registry === undefined && id === undefined)
496
+ return null;
497
+ if (registry === undefined || id === undefined) {
498
+ console.error("set AGENT_REGISTRY and AGENT_ID together, or neither");
499
+ process.exit(1);
500
+ }
501
+ if (!ERC8004_REGISTRY.test(registry) || !/^(0|[1-9][0-9]*)$/.test(id)) {
502
+ console.error("AGENT_REGISTRY must be eip155:<chainId>:<address> and AGENT_ID a decimal token id");
503
+ process.exit(1);
504
+ }
505
+ return { agentRegistry: registry, agentId: BigInt(id) };
506
+ }
507
+ var identity = identityFromEnv();
508
+ var handlerPath = process.env.ARP_NODE_HANDLER;
509
+ var handle = echo;
510
+ var offer = null;
511
+ if (handlerPath !== undefined) {
512
+ const module = await import(handlerPath);
513
+ if (typeof module.handle !== "function") {
514
+ console.error(`${handlerPath} does not export a \`handle\` function`);
515
+ process.exit(1);
516
+ }
517
+ handle = module.handle;
518
+ offer = readOffer(module.offer);
519
+ if (module.offer !== undefined && offer === null) {
520
+ console.error(`${handlerPath} exports an \`offer\` that is not three short strings, so none is served`);
521
+ }
522
+ }
523
+ var publicClient = createPublicClient({ transport: http(rpcUrl) });
524
+ var chainId = await publicClient.getChainId();
525
+ var node = createNode({
526
+ account,
527
+ publicClient,
528
+ chainId,
529
+ identity,
530
+ minBudget: MIN_BUDGET,
531
+ handle,
532
+ offer,
533
+ ledger: new PaymentLedger(LEDGER),
534
+ limits: LIMITS
535
+ });
536
+ async function main() {
537
+ const tunnel = chooseTunnel(process.env);
538
+ if (tunnel instanceof Error) {
539
+ process.stderr.write(`${tunnel.message}
540
+ `);
541
+ process.exit(1);
542
+ }
543
+ await serveFetch({ port: PORT, hostname: "127.0.0.1", maxBodyBytes: LIMITS.maxBodyBytes }, (request, ip) => node.fetch(request, clientAddress(request, ip, tunnel.kind)));
544
+ const local = `http://127.0.0.1:${PORT}`;
545
+ let running;
546
+ try {
547
+ running = await startTunnel(tunnel, local, {
548
+ onDown: (code) => process.stderr.write(`tunnel exited (${code ?? "signal"}); the node is unreachable
549
+ `)
550
+ });
551
+ } catch (error) {
552
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}
553
+ `);
554
+ process.exit(1);
555
+ }
556
+ for (const signal of ["SIGINT", "SIGTERM"]) {
557
+ process.on(signal, () => {
558
+ running.stop();
559
+ process.exit(0);
560
+ });
561
+ }
562
+ process.on("exit", () => running.stop());
563
+ process.stderr.write(`agent node on ${local}
564
+ `);
565
+ process.stderr.write(` public ${running.publicUrl ?? "none (ARP_NODE_TUNNEL=none): localhost only"}${running.publicUrl !== null && !running.stable ? " — a new address on every start: run arp init --node again after a restart" : ""}
566
+ `);
567
+ process.stderr.write(` tunnel ${tunnel.kind}
568
+ `);
569
+ process.stderr.write(` agent ${account.address}
570
+ `);
571
+ process.stderr.write(` key ${nodeKey.source === "env" ? "AGENT_PRIVATE_KEY" : `${nodeKey.path}${nodeKey.created ? " (created)" : ""}`}
572
+ `);
573
+ process.stderr.write(` min budget ${formatEther(MIN_BUDGET)}
574
+ `);
575
+ process.stderr.write(` handler ${handlerPath ?? "echo (no handler configured)"}
576
+ `);
577
+ process.stderr.write(` chain ${chainId}
578
+ `);
579
+ process.stderr.write(` identity ${identity === null ? "none (AGENT_REGISTRY / AGENT_ID unset)" : `${identity.agentRegistry} #${identity.agentId}`}
580
+ `);
581
+ process.stderr.write(` ledger ${LEDGER}
582
+ `);
583
+ process.stderr.write(` limits ${LIMITS.maxBodyBytes} B body, ${LIMITS.handlerTimeoutMs} ms handler, ${LIMITS.requestsPerMinute}/min per client
584
+ `);
585
+ process.stderr.write(`
586
+ It signs receipts and never transactions.
587
+ `);
588
+ }
589
+ if (false)
590
+ ;
591
+ export {
592
+ main
593
+ };