@arp-protocol/cli 0.0.0-stage → 0.0.3

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,57 @@
1
+ #!/usr/bin/env node
2
+ import"./cli-g5zvr10v.js";
3
+
4
+ // src/install/env.ts
5
+ import { spawnSync } from "node:child_process";
6
+ import { existsSync } from "node:fs";
7
+ import { homedir } from "node:os";
8
+ import { delimiter, join } from "node:path";
9
+ import { createInterface } from "node:readline/promises";
10
+ function which(command) {
11
+ for (const dir of (process.env.PATH ?? "").split(delimiter)) {
12
+ if (dir.length === 0)
13
+ continue;
14
+ const candidate = join(dir, command);
15
+ if (existsSync(candidate))
16
+ return candidate;
17
+ }
18
+ return null;
19
+ }
20
+ function processEnv(dist, version) {
21
+ return {
22
+ home: homedir(),
23
+ arpHome: process.env["ARP_HOME"]?.trim() || join(homedir(), ".arp"),
24
+ cwd: process.cwd(),
25
+ which,
26
+ run: (argv, stdin, timeoutMs = 15000) => {
27
+ const [command, ...args] = argv;
28
+ const result = spawnSync(command ?? "", args, {
29
+ input: stdin,
30
+ encoding: "utf8",
31
+ timeout: timeoutMs
32
+ });
33
+ return { code: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
34
+ },
35
+ interactive: process.stdin.isTTY === true && process.stdout.isTTY === true,
36
+ ask: async (question) => {
37
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
38
+ try {
39
+ const answer = await rl.question(`${question} [y/N] `);
40
+ return /^y(es)?$/i.test(answer.trim());
41
+ } finally {
42
+ rl.close();
43
+ }
44
+ },
45
+ out: (line) => process.stdout.write(`${line}
46
+ `),
47
+ err: (line) => process.stderr.write(`${line}
48
+ `),
49
+ now: () => new Date,
50
+ node: process.execPath,
51
+ dist,
52
+ version
53
+ };
54
+ }
55
+ export {
56
+ processEnv
57
+ };
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ DEFAULT_REQUEST_TIMEOUT_MS,
4
+ DEFAULT_SERVE_MAX_BODY_BYTES,
5
+ KeyFileError,
6
+ MovedStateError,
7
+ appendJsonLine,
8
+ arpHome,
9
+ bigintSafe,
10
+ bufferedCopy,
11
+ jsonResponse,
12
+ linesOf,
13
+ loadOrCreateKeyFile,
14
+ lockFile,
15
+ loopbackHosts,
16
+ readJsonLines,
17
+ readKeyFile,
18
+ redactUrls,
19
+ refuseForeignRequest,
20
+ refuseMovedState,
21
+ serveFetch,
22
+ stateFile
23
+ } from "./cli-n4qagybd.js";
24
+ import {
25
+ ERC8004_REGISTRY
26
+ } from "./cli-fbwpcfvp.js";
27
+ import"./cli-mhzh9z8k.js";
28
+ import"./cli-d3dy7cxe.js";
29
+ import"./cli-3kbbwfbp.js";
30
+ import"./cli-g5zvr10v.js";
31
+ export {
32
+ stateFile,
33
+ serveFetch,
34
+ refuseMovedState,
35
+ refuseForeignRequest,
36
+ redactUrls,
37
+ readKeyFile,
38
+ readJsonLines,
39
+ loopbackHosts,
40
+ lockFile,
41
+ loadOrCreateKeyFile,
42
+ linesOf,
43
+ jsonResponse,
44
+ bufferedCopy,
45
+ bigintSafe,
46
+ arpHome,
47
+ appendJsonLine,
48
+ MovedStateError,
49
+ KeyFileError,
50
+ ERC8004_REGISTRY as ERC8004_REGISTRY_REF,
51
+ DEFAULT_SERVE_MAX_BODY_BYTES,
52
+ DEFAULT_REQUEST_TIMEOUT_MS
53
+ };
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ agentFor,
4
+ chainId,
5
+ domainWalk,
6
+ domainWords,
7
+ loopConfig,
8
+ sponsorApproval,
9
+ writeLine
10
+ } from "./cli-10wacn76.js";
11
+ import"./cli-00qp1sj2.js";
12
+ import"./cli-n4qagybd.js";
13
+ import {
14
+ USAGE_REASONS,
15
+ X402_DEFAULT_NETWORKS,
16
+ chainIdOfNetwork,
17
+ isCategoryIn,
18
+ isExactEndpointUrl,
19
+ isTransactionAbsent,
20
+ isUsageOutcome,
21
+ isUsageReason,
22
+ needShapeProblem,
23
+ normaliseCategoryLabel,
24
+ readDomainTree,
25
+ resolveDomain,
26
+ transfersIn,
27
+ x402NetworksFrom,
28
+ x402Reading
29
+ } from "./cli-wev0jgf7.js";
30
+ import"./cli-0gsbnq5w.js";
31
+ import"./cli-fbwpcfvp.js";
32
+ import"./cli-mhzh9z8k.js";
33
+ import"./cli-d3dy7cxe.js";
34
+ import"./cli-3kbbwfbp.js";
35
+ import"./cli-g5zvr10v.js";
36
+
37
+ // ../mcp/src/line.ts
38
+ function lineNetworks(env = process.env) {
39
+ return x402NetworksFrom(X402_DEFAULT_NETWORKS, env);
40
+ }
41
+ function lineFromArgs(args, env = process.env) {
42
+ if (args.parent === undefined !== (args.why === undefined))
43
+ return new Error("--parent and --why go together");
44
+ if (args.paymentTx === undefined !== (args.network === undefined))
45
+ return new Error("--payment-tx and --network go together");
46
+ if (args.network !== undefined && chainIdOfNetwork(args.network) === null) {
47
+ return new Error("--network is a CAIP-2 EVM network, such as eip155:8453 for Base");
48
+ }
49
+ if (args.network !== undefined) {
50
+ let networks;
51
+ try {
52
+ networks = lineNetworks(env);
53
+ } catch (error) {
54
+ return error instanceof Error ? error : new Error(String(error));
55
+ }
56
+ if (!networks.has(args.network))
57
+ return new Error(`--network must be one of ${[...networks.keys()].join(", ")}`);
58
+ }
59
+ if (args.paymentTx !== undefined && !/^0x[0-9a-fA-F]{64}$/.test(args.paymentTx))
60
+ return new Error("--payment-tx is a 32-byte transaction hash");
61
+ if (args.paymentTx !== undefined && !isExactEndpointUrl(args.toolUrl)) {
62
+ return new Error("with --payment-tx, --tool is the exact URL you paid, scheme included, as you called it");
63
+ }
64
+ if (args.parent !== undefined && args.domain === undefined)
65
+ return new Error("--parent and --why add a --domain");
66
+ if (args.why !== undefined && args.why.trim() === "")
67
+ return new Error("--why says why no category fits");
68
+ if (!isUsageOutcome(args.outcome))
69
+ return new Error("--outcome must be worked or failed");
70
+ if (args.reason !== undefined && !isUsageReason(args.reason)) {
71
+ return new Error(`--reason must be one of ${USAGE_REASONS.join(", ")}`);
72
+ }
73
+ if (args.reason !== undefined && args.outcome !== "failed")
74
+ return new Error("a reason only follows --outcome failed");
75
+ if (args.needs !== undefined && args.outcome !== "failed")
76
+ return new Error("--needs follows --outcome failed; a worked line takes --with");
77
+ if (args.with !== undefined && args.outcome !== "worked")
78
+ return new Error("--with follows --outcome worked; a failure takes --needs");
79
+ const need = args.needs ?? args.with;
80
+ if (need !== undefined) {
81
+ const problem = needShapeProblem({ need, outcome: args.outcome, reason: args.reason ?? "" });
82
+ if (problem !== null)
83
+ return new Error(problem);
84
+ if (args.paymentTx !== undefined)
85
+ return new Error("--needs and --with name a skill's word; a paid endpoint has no SKILL.md");
86
+ }
87
+ return {
88
+ toolUrl: args.toolUrl,
89
+ outcome: args.outcome,
90
+ ...args.domain === undefined ? {} : { domain: args.domain },
91
+ ...args.reason === undefined ? {} : { reason: args.reason },
92
+ ...args.parent === undefined || args.why === undefined ? {} : { newDomain: { parent: args.parent, why: args.why.trim() } },
93
+ ...args.paymentTx === undefined || args.network === undefined ? {} : { payment: { network: args.network, txHash: args.paymentTx.toLowerCase() } },
94
+ ...need === undefined ? {} : { need }
95
+ };
96
+ }
97
+ async function readLinePayment(input, reading) {
98
+ if (input.payment === undefined)
99
+ return null;
100
+ const { network, txHash } = input.payment;
101
+ const assets = reading.policy.get(network)?.assets ?? [];
102
+ const reads = reading.readsFor(network);
103
+ let receipt;
104
+ try {
105
+ receipt = await reads.getTransactionReceipt({ hash: txHash });
106
+ } catch (error) {
107
+ if (isTransactionAbsent(error))
108
+ throw new Error(`transaction ${txHash} not found on ${network}`);
109
+ throw new Error(`${network} could not be read: ${error instanceof Error ? error.message.split(`
110
+ `)[0] : String(error)}`);
111
+ }
112
+ if (receipt.status !== "success")
113
+ throw new Error(`the payment ${txHash} reverted on ${network}`);
114
+ const transfers = transfersIn(receipt.logs, assets);
115
+ if (transfers.length === 0)
116
+ throw new Error(`${txHash} on ${network} moves no accepted asset (${assets.join(", ")})`);
117
+ return { network, txHash, transfers: transfers.map((transfer) => ({ ...transfer, amount: transfer.amount.toString() })) };
118
+ }
119
+ async function writeLineChecked(steps, input) {
120
+ let line = input;
121
+ let payment = null;
122
+ if (input.payment !== undefined) {
123
+ if (!isExactEndpointUrl(input.toolUrl))
124
+ throw new Error(`${input.toolUrl} is not the exact http(s) URL that was paid`);
125
+ if (steps.payments === undefined)
126
+ throw new Error("this build reads no payment network");
127
+ payment = await readLinePayment(input, steps.payments);
128
+ }
129
+ if (input.domain !== undefined) {
130
+ const domain = normaliseCategoryLabel(input.domain);
131
+ const resolved = await steps.isCategory(domain) ? null : await steps.resolve?.(domain);
132
+ if (resolved === null || resolved?.status === "node") {
133
+ const { newDomain: _unused, ...rest } = input;
134
+ line = { ...rest, domain: resolved?.status === "node" ? resolved.label : domain };
135
+ } else if (input.newDomain === undefined) {
136
+ const closest = resolved?.status === "unknown" && resolved.suggestions.length > 0 ? ` Closest: ${resolved.suggestions.join(", ")}.` : "";
137
+ throw new Error(`"${input.domain}" is not a category under tool.${closest} Pick one, or add it with --parent <category> --why <reason>`);
138
+ } else {
139
+ const parent = normaliseCategoryLabel(input.newDomain.parent);
140
+ if (!await steps.isCategory(parent))
141
+ throw new Error(`--parent "${input.newDomain.parent}" is not a category under tool`);
142
+ line = { ...input, domain, newDomain: { parent, why: input.newDomain.why } };
143
+ }
144
+ }
145
+ await sponsorApproval(steps.config);
146
+ const written = await (steps.write ?? writeLine)(steps.config, steps.agent(), line);
147
+ return payment === null ? written : { ...written, payment };
148
+ }
149
+ async function writeLineHere(input) {
150
+ if (chainId !== 13579)
151
+ throw new Error("a line is written on testnet 13579 only for now");
152
+ return writeLineChecked({
153
+ config: loopConfig(),
154
+ isCategory: async (label) => isCategoryIn(await readDomainTree(domainWalk()))(label),
155
+ resolve: async (word) => resolveDomain(word, domainWords(await readDomainTree(domainWalk()))),
156
+ agent: agentFor,
157
+ payments: x402Reading(lineNetworks(), process.env)
158
+ }, input);
159
+ }
160
+ export {
161
+ writeLineHere,
162
+ writeLineChecked,
163
+ readLinePayment,
164
+ lineNetworks,
165
+ lineFromArgs
166
+ };
@@ -0,0 +1,255 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ resolveNodeKey
4
+ } from "./cli-64fx73kx.js";
5
+ import"./cli-n4qagybd.js";
6
+ import {
7
+ asEndpointBinding,
8
+ endpointBindingDocument,
9
+ normaliseEndpoint,
10
+ parseAgentRegistry,
11
+ validateHandshakeRequest
12
+ } from "./cli-wev0jgf7.js";
13
+ import"./cli-0gsbnq5w.js";
14
+ import"./cli-fbwpcfvp.js";
15
+ import {
16
+ isHex
17
+ } from "./cli-mhzh9z8k.js";
18
+ import"./cli-d3dy7cxe.js";
19
+ import"./cli-3kbbwfbp.js";
20
+ import"./cli-g5zvr10v.js";
21
+
22
+ // src/node-init.ts
23
+ import { mkdirSync, writeFileSync } from "node:fs";
24
+ import { dirname, join } from "node:path";
25
+
26
+ // ../node/src/register.ts
27
+ async function post(fetchLike, url, body) {
28
+ const response = await fetchLike(url, {
29
+ method: "POST",
30
+ headers: { "content-type": "application/json" },
31
+ body: JSON.stringify(body)
32
+ });
33
+ let parsed = null;
34
+ try {
35
+ parsed = await response.json();
36
+ } catch {
37
+ parsed = null;
38
+ }
39
+ return { status: response.status, ok: response.ok, body: parsed };
40
+ }
41
+ function refusal(status, body) {
42
+ if (typeof body === "object" && body !== null) {
43
+ const { error, reason } = body;
44
+ if (typeof error === "string")
45
+ return typeof reason === "string" ? `${error}: ${reason}` : error;
46
+ }
47
+ return `HTTP ${status}`;
48
+ }
49
+ function isChallenge(value) {
50
+ if (typeof value !== "object" || value === null)
51
+ return false;
52
+ const record = value;
53
+ return typeof record["nonce"] === "string" && typeof record["message"] === "string" && typeof record["owner"] === "string" && typeof record["nodeAddress"] === "string";
54
+ }
55
+ async function register(options) {
56
+ const fetchLike = options.fetch ?? fetch;
57
+ const base = options.directoryUrl.replace(/\/$/, "");
58
+ const request = {
59
+ ...options.agent,
60
+ nodePublicKey: options.node.publicKey,
61
+ endpoint: options.endpoint
62
+ };
63
+ const valid = validateHandshakeRequest(request);
64
+ if (!valid.ok) {
65
+ return {
66
+ ok: false,
67
+ step: "request",
68
+ reason: valid.error.code === "malformed" ? valid.error.reason : valid.error.code
69
+ };
70
+ }
71
+ let answer;
72
+ try {
73
+ answer = await post(fetchLike, `${base}/challenge`, request);
74
+ } catch (error) {
75
+ return {
76
+ ok: false,
77
+ step: "challenge",
78
+ reason: error instanceof Error ? error.message : String(error)
79
+ };
80
+ }
81
+ if (!answer.ok)
82
+ return { ok: false, step: "challenge", reason: refusal(answer.status, answer.body) };
83
+ if (!isChallenge(answer.body))
84
+ return { ok: false, step: "challenge", reason: "not a challenge" };
85
+ const challenge = answer.body;
86
+ if (challenge.nodeAddress.toLowerCase() !== options.node.address.toLowerCase()) {
87
+ return { ok: false, step: "challenge", reason: "the challenge names another node key" };
88
+ }
89
+ let ownerSignature;
90
+ try {
91
+ ownerSignature = await options.signAsOwner(challenge);
92
+ } catch (error) {
93
+ return {
94
+ ok: false,
95
+ step: "owner",
96
+ reason: error instanceof Error ? error.message : String(error)
97
+ };
98
+ }
99
+ if (!isHex(ownerSignature))
100
+ return { ok: false, step: "owner", reason: "the signature is not hex" };
101
+ const nodeSignature = await options.node.signMessage({ message: challenge.message });
102
+ let done;
103
+ try {
104
+ done = await post(fetchLike, `${base}/complete`, {
105
+ nonce: challenge.nonce,
106
+ ownerSignature,
107
+ nodeSignature
108
+ });
109
+ } catch (error) {
110
+ return {
111
+ ok: false,
112
+ step: "complete",
113
+ reason: error instanceof Error ? error.message : String(error)
114
+ };
115
+ }
116
+ if (!done.ok)
117
+ return { ok: false, step: "complete", reason: refusal(done.status, done.body) };
118
+ const binding = asEndpointBinding(done.body);
119
+ if (binding === null || binding.message !== challenge.message) {
120
+ return { ok: false, step: "complete", reason: "the directory did not return this binding" };
121
+ }
122
+ return { ok: true, binding, challenge };
123
+ }
124
+
125
+ // src/node-init.ts
126
+ var NODE_INIT_EXIT = { ok: 0, usage: 2, refused: 3, failed: 6 };
127
+ var VALUE_FLAGS = {
128
+ "--agent-registry": "agentRegistry",
129
+ "--agent-id": "agentId",
130
+ "--endpoint": "endpoint",
131
+ "--directory": "directory"
132
+ };
133
+ function parseNodeInitFlags(args) {
134
+ const flags = {
135
+ agentRegistry: null,
136
+ agentId: null,
137
+ endpoint: null,
138
+ directory: null,
139
+ noInput: false
140
+ };
141
+ for (let i = 0;i < args.length; i += 1) {
142
+ const arg = args[i] ?? "";
143
+ if (arg === "--node")
144
+ continue;
145
+ if (arg === "--no-input") {
146
+ flags.noInput = true;
147
+ continue;
148
+ }
149
+ const [name, inline] = arg.includes("=") ? [arg.slice(0, arg.indexOf("=")), arg.slice(arg.indexOf("=") + 1)] : [arg, null];
150
+ if (name in VALUE_FLAGS) {
151
+ const value = inline ?? args[i + 1];
152
+ if (inline === null)
153
+ i += 1;
154
+ if (value === undefined || value.length === 0 || value.startsWith("--")) {
155
+ return new Error(`${name} needs a value`);
156
+ }
157
+ flags[VALUE_FLAGS[name]] = value;
158
+ continue;
159
+ }
160
+ return new Error(`unknown option '${arg}' for init --node`);
161
+ }
162
+ return flags;
163
+ }
164
+ function writePrivate(path, text) {
165
+ mkdirSync(dirname(path), { recursive: true, mode: 448 });
166
+ writeFileSync(path, text, { mode: 384 });
167
+ }
168
+ async function nodeInit(flags, io) {
169
+ const agentRegistry = flags.agentRegistry ?? io.env["AGENT_REGISTRY"] ?? null;
170
+ const agentId = flags.agentId ?? io.env["AGENT_ID"] ?? null;
171
+ const endpointRaw = flags.endpoint ?? io.env["ARP_NODE_PUBLIC_URL"] ?? null;
172
+ const directory = flags.directory ?? io.env["ARP_DIRECTORY_URL"] ?? null;
173
+ const missing = [];
174
+ if (agentRegistry === null)
175
+ missing.push("--agent-registry (or AGENT_REGISTRY)");
176
+ if (agentId === null)
177
+ missing.push("--agent-id (or AGENT_ID)");
178
+ if (endpointRaw === null)
179
+ missing.push("--endpoint (or ARP_NODE_PUBLIC_URL)");
180
+ if (directory === null)
181
+ missing.push("--directory (or ARP_DIRECTORY_URL)");
182
+ if (missing.length > 0 || agentRegistry === null || agentId === null || endpointRaw === null || directory === null) {
183
+ io.err(`arp init --node: missing ${missing.join(", ")}`);
184
+ return NODE_INIT_EXIT.usage;
185
+ }
186
+ if (parseAgentRegistry(agentRegistry) === null || !/^(0|[1-9][0-9]*)$/.test(agentId)) {
187
+ io.err("arp init --node: the agent is eip155:<chainId>:<registry> plus a decimal token id");
188
+ return NODE_INIT_EXIT.usage;
189
+ }
190
+ const endpoint = normaliseEndpoint(endpointRaw);
191
+ if (endpoint === null) {
192
+ io.err(`arp init --node: the endpoint must be the tunnel's https URL, got ${endpointRaw}`);
193
+ return NODE_INIT_EXIT.usage;
194
+ }
195
+ if (flags.noInput || !io.interactive) {
196
+ io.err("arp init --node: the owner signs the handshake in their wallet, which needs a terminal");
197
+ return NODE_INIT_EXIT.refused;
198
+ }
199
+ let key;
200
+ try {
201
+ key = resolveNodeKey(io.env, { home: io.home });
202
+ } catch (error) {
203
+ io.err(`arp init --node: ${error instanceof Error ? error.message : String(error)}`);
204
+ return NODE_INIT_EXIT.failed;
205
+ }
206
+ io.err(`node key ${key.account.address} (${key.source === "env" ? "AGENT_PRIVATE_KEY" : key.path})`);
207
+ const messagePath = join(io.home, ".arp", "handshake-message.txt");
208
+ const result = await register({
209
+ directoryUrl: directory,
210
+ agent: { agentRegistry, agentId },
211
+ endpoint,
212
+ node: key.account,
213
+ ...io.fetch === undefined ? {} : { fetch: io.fetch },
214
+ async signAsOwner(challenge) {
215
+ writePrivate(messagePath, challenge.message);
216
+ io.err("");
217
+ io.err(`Sign this message as ${challenge.owner}, the token's owner, before ${challenge.expiresAt}:`);
218
+ io.err("");
219
+ for (const line of challenge.message.split(`
220
+ `))
221
+ io.err(` ${line}`);
222
+ io.err("");
223
+ io.err(`It is also in ${messagePath}. With Foundry, for example:`);
224
+ io.err(` cast wallet sign --interactive "$(cat ${messagePath})"`);
225
+ io.err("");
226
+ const answer = (await io.readLine("owner signature (0x…): ")).trim();
227
+ if (!isHex(answer) || answer.length < 132)
228
+ throw new Error("that is not a signature");
229
+ return answer;
230
+ }
231
+ });
232
+ if (!result.ok) {
233
+ io.err(`arp init --node: ${result.step} refused: ${result.reason}`);
234
+ return NODE_INIT_EXIT.failed;
235
+ }
236
+ const bindingPath = join(io.home, ".arp", "node-binding.json");
237
+ const documentPath = join(io.home, ".arp", "node-binding.document.json");
238
+ writePrivate(bindingPath, `${JSON.stringify(result.binding, null, 2)}
239
+ `);
240
+ writePrivate(documentPath, endpointBindingDocument(result.binding));
241
+ io.out(`bound node ${result.binding.nodeAddress} to agent ${agentId} of ${agentRegistry}`);
242
+ io.out(`endpoint ${result.binding.endpoint}`);
243
+ io.out(`saved ${bindingPath}`);
244
+ io.out(`document ${documentPath}`);
245
+ io.out("");
246
+ io.out("Next: pin the document to IPFS, then write the binding on Intuition testnet with");
247
+ io.out("`prepareEndpointBinding`. This command signs no transaction.");
248
+ io.out("A transfer of the token voids the binding until you run this again.");
249
+ return NODE_INIT_EXIT.ok;
250
+ }
251
+ export {
252
+ parseNodeInitFlags,
253
+ nodeInit,
254
+ NODE_INIT_EXIT
255
+ };