@arp-protocol/cli 0.0.3 → 0.0.5

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,219 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ decideShare,
4
+ installedFromReceipts,
5
+ installedFromSkillLock,
6
+ isSharingOn,
7
+ parseLocalFailures,
8
+ parseLocalOutcomes,
9
+ sharedLineKey,
10
+ sharingChoiceOf,
11
+ sharingFileText
12
+ } from "./cli-g3q6fxm0.js";
13
+ import"./cli-zehr6kdx.js";
14
+ import"./cli-0gsbnq5w.js";
15
+ import"./cli-fbwpcfvp.js";
16
+ import"./cli-mhzh9z8k.js";
17
+ import"./cli-d3dy7cxe.js";
18
+ import"./cli-3kbbwfbp.js";
19
+ import"./cli-g5zvr10v.js";
20
+
21
+ // src/share.ts
22
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
23
+ import { join } from "node:path";
24
+ var read = (path) => existsSync(path) ? readFileSync(path, "utf8") : null;
25
+ function shared(env) {
26
+ const keys = new Set;
27
+ for (const row of (read(join(env.arpHome, "shared-failures.jsonl")) ?? "").split(`
28
+ `)) {
29
+ if (row.trim() === "")
30
+ continue;
31
+ try {
32
+ keys.add(sharedLineKey(JSON.parse(row)));
33
+ } catch {
34
+ continue;
35
+ }
36
+ }
37
+ return keys;
38
+ }
39
+ function onceGrants(env) {
40
+ const grants = [];
41
+ for (const row of (read(join(env.arpHome, "share-once.jsonl")) ?? "").split(`
42
+ `)) {
43
+ try {
44
+ const { session, skill } = JSON.parse(row);
45
+ if (typeof session === "string" && typeof skill === "string")
46
+ grants.push({ session, skill });
47
+ } catch {
48
+ continue;
49
+ }
50
+ }
51
+ return grants;
52
+ }
53
+ var nameOf = (skill) => skill.split(":").pop() ?? skill;
54
+ function granted(text, grants, since = null) {
55
+ return text.split(`
56
+ `).filter((row) => {
57
+ try {
58
+ const { session, skill, at } = JSON.parse(row);
59
+ if (since !== null && typeof at === "string" && at >= since)
60
+ return true;
61
+ return grants.some((grant) => grant.session === session && typeof skill === "string" && nameOf(skill) === nameOf(grant.skill));
62
+ } catch {
63
+ return false;
64
+ }
65
+ }).join(`
66
+ `);
67
+ }
68
+ var rowCount = (text) => text.split(`
69
+ `).filter((row) => row.trim() !== "").length;
70
+ function decide(env) {
71
+ const choice = sharingChoiceOf(read(join(env.arpHome, "sharing.json")));
72
+ const on = isSharingOn(read(join(env.arpHome, "sharing.json")));
73
+ const since = on && choice?.how === "asked-always" && choice.at !== null ? choice.at : null;
74
+ const grants = on && since === null ? [] : onceGrants(env);
75
+ const failuresAll = read(join(env.arpHome, "failures.jsonl")) ?? "";
76
+ const outcomesAll = read(join(env.arpHome, "outcomes.jsonl")) ?? "";
77
+ const failuresText = since === null ? failuresAll : granted(failuresAll, grants, since);
78
+ const outcomesText = since === null ? outcomesAll : granted(outcomesAll, grants, since);
79
+ const earlier = rowCount(failuresAll) + rowCount(outcomesAll) - rowCount(failuresText) - rowCount(outcomesText);
80
+ const installed = [
81
+ ...installedFromSkillLock(read(join(env.home, ".agents", ".skill-lock.json"))),
82
+ ...installedFromSkillLock(read(join(env.cwd, "skills-lock.json"))),
83
+ ...installedFromReceipts(read(join(env.arpHome, "receipts.jsonl"))),
84
+ ...installedFromReceipts(read(join(env.cwd, ".arp", "receipts.jsonl")))
85
+ ];
86
+ const alreadyShared = shared(env);
87
+ const all = decideShare({
88
+ on,
89
+ failures: parseLocalFailures(failuresText),
90
+ outcomes: parseLocalOutcomes(outcomesText),
91
+ installed,
92
+ alreadyShared
93
+ });
94
+ if (on || grants.length === 0)
95
+ return { ...all, on, choice, once: false, earlier };
96
+ const once = decideShare({
97
+ on: true,
98
+ failures: parseLocalFailures(granted(failuresText, grants)),
99
+ outcomes: parseLocalOutcomes(granted(outcomesText, grants)),
100
+ installed,
101
+ alreadyShared
102
+ });
103
+ const onceSkills = new Set(grants.map((grant) => nameOf(grant.skill)));
104
+ const stillOff = all.keptLocal.filter((row) => !onceSkills.has(row.skill));
105
+ return { lines: once.lines, keptLocal: [...once.keptLocal, ...stillOff], on, choice, once: once.lines.length > 0, earlier: 0 };
106
+ }
107
+ function setSharing(env, on, how = "command", now = new Date) {
108
+ mkdirSync(env.arpHome, { recursive: true });
109
+ writeFileSync(join(env.arpHome, "sharing.json"), sharingFileText(on, how, now));
110
+ }
111
+ var LOCK = "share-send.lock";
112
+ var STALE_MS = 10 * 60 * 1000;
113
+ async function withSendLock(env, send, now = Date.now) {
114
+ mkdirSync(env.arpHome, { recursive: true });
115
+ const path = join(env.arpHome, LOCK);
116
+ const take = () => {
117
+ try {
118
+ writeFileSync(path, `${JSON.stringify({ pid: process.pid, at: new Date(now()).toISOString() })}
119
+ `, { flag: "wx" });
120
+ return true;
121
+ } catch {
122
+ return false;
123
+ }
124
+ };
125
+ if (!take()) {
126
+ let at = 0;
127
+ try {
128
+ at = Date.parse(JSON.parse(readFileSync(path, "utf8")).at ?? "");
129
+ } catch {
130
+ at = 0;
131
+ }
132
+ if (Number.isFinite(at) && now() - at < STALE_MS)
133
+ return null;
134
+ rmSync(path, { force: true });
135
+ if (!take())
136
+ return null;
137
+ }
138
+ try {
139
+ return await send();
140
+ } finally {
141
+ rmSync(path, { force: true });
142
+ }
143
+ }
144
+ function logSend(env, result, now = new Date) {
145
+ mkdirSync(env.arpHome, { recursive: true });
146
+ const row = { at: now.toISOString(), sent: result.sent, failed: result.failed, ...result.firstError === null ? {} : { error: result.firstError } };
147
+ appendFileSync(join(env.arpHome, "share-log.jsonl"), `${JSON.stringify(row)}
148
+ `);
149
+ }
150
+ async function sendShared(env, write) {
151
+ const { on, once, lines } = decide(env);
152
+ if (!on && !once)
153
+ return { sent: 0, failed: 0, firstError: null };
154
+ let sent = 0;
155
+ let failed = 0;
156
+ let firstError = null;
157
+ for (const line of lines) {
158
+ try {
159
+ await write(line);
160
+ appendFileSync(join(env.arpHome, "shared-failures.jsonl"), `${JSON.stringify(line)}
161
+ `);
162
+ sent += 1;
163
+ } catch (error) {
164
+ failed += 1;
165
+ firstError ??= error instanceof Error ? error.message : String(error);
166
+ }
167
+ }
168
+ return { sent, failed, firstError };
169
+ }
170
+ var WHY = {
171
+ sharing_off: "sharing is off",
172
+ no_public_source: "no known public source",
173
+ several_sources: "several sources with this name"
174
+ };
175
+ var HOW = {
176
+ init: "at install",
177
+ command: "by command",
178
+ desktop: "in Claude Desktop's settings",
179
+ "asked-once": 'answering "yes, this once"',
180
+ "asked-always": 'answering "always"',
181
+ "asked-no": 'answering "no"'
182
+ };
183
+ function status(decision) {
184
+ const state = decision.on ? "on" : "off";
185
+ if (decision.choice === null)
186
+ return `sharing is ${state}, not decided yet`;
187
+ const when = decision.choice.at === null ? "" : ` ${decision.choice.at.slice(0, 10)}`;
188
+ const how = decision.choice.how === null ? "" : ` ${HOW[decision.choice.how]}`;
189
+ return when === "" && how === "" ? `sharing is ${state}` : `sharing is ${state}, chosen${when}${how}`;
190
+ }
191
+ function describe(decision) {
192
+ const out = [status(decision)];
193
+ if (decision.lines.length > 0) {
194
+ out.push("", decision.on ? "would be public on testnet 13579:" : "would be public on testnet 13579, agreed once:");
195
+ for (const line of decision.lines) {
196
+ const tail = line.outcome === "failed" ? `failed ${line.reason}${line.need === undefined ? "" : ` needs ${line.need}`}` : `worked${line.need === undefined ? "" : ` with ${line.need}`}`;
197
+ out.push(` ${line.toolUrl} ${line.domain ?? "(no domain)"} ${tail}`);
198
+ }
199
+ }
200
+ if (decision.keptLocal.length > 0) {
201
+ out.push("", "stays on this machine:");
202
+ for (const row of decision.keptLocal)
203
+ out.push(` ${row.skill}: ${WHY[row.why]}`);
204
+ }
205
+ if (decision.earlier > 0) {
206
+ out.push("", `kept before "always" was chosen, and staying here: ${decision.earlier} record(s). \`arp share on\` would show and include them`);
207
+ }
208
+ return `${out.join(`
209
+ `)}
210
+ `;
211
+ }
212
+ export {
213
+ withSendLock,
214
+ setSharing,
215
+ sendShared,
216
+ logSend,
217
+ describe,
218
+ decide
219
+ };
package/dist/cli.js CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  import { dirname } from "node:path";
15
15
  import { fileURLToPath } from "node:url";
16
16
  // package.json
17
- var version = "0.0.3";
17
+ var version = "0.0.5";
18
18
 
19
19
  // src/cli.ts
20
20
  var USAGE = `arp ${version} — ARP's agent-side programs
@@ -23,18 +23,26 @@ usage
23
23
  arp mcp [--http] the MCP server. stdio by default, which is how an MCP client launches it
24
24
  arp hook the runtime hook: one payload on stdin. Records what a session used in
25
25
  .arp/session.jsonl, and a skill's failures in ~/.arp/failures.jsonl. On
26
- Stop, asks once per skill whether it did its job: ~/.arp/outcomes.jsonl
26
+ Stop, asks once per skill whether it did its job: ~/.arp/outcomes.jsonl.
27
+ While sharing is undecided, the first result also asks once whether to share
28
+ it; with sharing on, SessionEnd starts arp share send --auto in the background
27
29
  arp backfill read old Claude Code transcripts into ~/.arp/failures.jsonl. Sends nothing
28
30
  arp share say what would be public from ~/.arp/failures.jsonl and outcomes.jsonl,
29
31
  and what stays here.
30
- arp share on, off (off by default). arp share send writes them on testnet
32
+ arp share on, off (off by default). arp share send writes them on testnet;
33
+ --auto prints nothing and keeps the result in ~/.arp/share-log.jsonl
34
+ arp check <skill> before you use a skill: what its files contain, what agents wrote about
35
+ it, and whether what it needed when it failed is on this machine.
36
+ <owner/repo/skill> or a github.com URL with ?skill=. Writes nothing
31
37
  arp node the hire endpoint. Opt-in: it listens on 127.0.0.1 and is reached through
32
38
  a tunnel you rent (cloudflared or ngrok, or ARP_NODE_PUBLIC_URL)
33
39
 
34
40
  arp line write one public line on testnet 13579 through ARP's sponsor: --tool <url>
35
41
  --outcome worked|failed [--domain <category>] [--reason <reason>]
36
42
  [--payment-tx <hash> --network eip155:8453] an x402 payment, read on Base;
37
- with it, --tool is the exact URL you paid
43
+ with it, --tool is the exact URL you paid, written
44
+ with its values removed, and the 402's terms you paid
45
+ against: --pay-to --asset --price --terms-at
38
46
  [--needs <name>] or [--with <name>] the program or variable name that was
39
47
  missing, or that you added; written only if the skill's SKILL.md names it
40
48
  arp init add the MCP server and the hook to every runtime found here
@@ -70,7 +78,7 @@ var asksHelp = (rest) => rest.some((arg) => arg === "--help" || arg === "-h");
70
78
  var WANTS_HELP = asksHelp(ARGV.slice(1));
71
79
  var STDIO_INPUT = ARGV[0] === "mcp" && !ARGV.includes("--http") && !WANTS_HELP ? bufferedCopy(process.stdin) : undefined;
72
80
  async function runNodeInit(rest) {
73
- const { nodeInit, parseNodeInitFlags } = await import("./chunks/node-init-rcepfyeh.js");
81
+ const { nodeInit, parseNodeInitFlags } = await import("./chunks/node-init-rmrmmg59.js");
74
82
  const flags = parseNodeInitFlags(rest);
75
83
  if (flags instanceof Error) {
76
84
  process.stderr.write(`arp init --node: ${flags.message}
@@ -100,18 +108,42 @@ async function runNodeInit(rest) {
100
108
  async function shareCommand(rest) {
101
109
  const { arpHome } = await import("./chunks/index-jrs2shcc.js");
102
110
  const { homedir } = await import("node:os");
103
- const share = await import("./chunks/share-vtbkzs5f.js");
104
- const env = { arpHome: arpHome(process.env, homedir()), home: homedir(), cwd: process.cwd() };
111
+ const share = await import("./chunks/share-k1dnzsna.js");
112
+ const cwd = process.env["ARP_PROJECT_DIR"]?.trim() || process.cwd();
113
+ const env = { arpHome: arpHome(process.env, homedir()), home: homedir(), cwd };
105
114
  if (rest[0] === "send") {
106
- if (!share.decide(env).on) {
107
- process.stderr.write("sharing is off: nothing was sent. `arp share on` turns it on\n");
115
+ const auto = rest.includes("--auto");
116
+ const decision = share.decide(env);
117
+ if (!decision.on && !decision.once) {
118
+ if (!auto)
119
+ process.stderr.write("sharing is off: nothing was sent. `arp share on` turns it on\n");
108
120
  return EXIT.ok;
109
121
  }
122
+ if (auto && decision.lines.length === 0)
123
+ return EXIT.ok;
110
124
  process.env["ARP_TARGET"] ??= "testnet";
111
- const { writeLineHere } = await import("./chunks/line-691gbaha.js");
112
- const done = await share.sendShared(env, async (line) => {
113
- await writeLineHere(line);
114
- });
125
+ let done;
126
+ try {
127
+ const { writeLineHere } = await import("./chunks/line-wra91qkr.js");
128
+ done = await share.withSendLock(env, () => share.sendShared(env, async (line) => {
129
+ await writeLineHere(line);
130
+ }));
131
+ } catch (error) {
132
+ if (!auto)
133
+ throw error;
134
+ share.logSend(env, { sent: 0, failed: 0, firstError: error instanceof Error ? error.message : String(error) });
135
+ return EXIT.ok;
136
+ }
137
+ if (done === null) {
138
+ if (!auto)
139
+ process.stderr.write(`another send is running: nothing was sent by this one
140
+ `);
141
+ return EXIT.ok;
142
+ }
143
+ if (auto) {
144
+ share.logSend(env, done);
145
+ return EXIT.ok;
146
+ }
115
147
  process.stdout.write(`sent ${done.sent}, failed ${done.failed}
116
148
  `);
117
149
  if (done.firstError !== null)
@@ -139,7 +171,7 @@ async function run(argv) {
139
171
  return runNodeInit(rest);
140
172
  switch (command) {
141
173
  case "mcp": {
142
- const { main } = await import("./chunks/server-59zry2jp.js");
174
+ const { main } = await import("./chunks/server-bcsxfd7q.js");
143
175
  await main(rest.includes("--http") ? rest : ["--stdio", ...rest], STDIO_INPUT);
144
176
  return EXIT.ok;
145
177
  }
@@ -154,6 +186,17 @@ async function run(argv) {
154
186
  `);
155
187
  return EXIT.writeFailed;
156
188
  }
189
+ case "check": {
190
+ const { checkSkill, skillRefOf, thisMachine } = await import("./chunks/check-k4qnc9ee.js");
191
+ const ref = rest.length === 1 ? skillRefOf(rest[0] ?? "") : null;
192
+ if (ref === null) {
193
+ process.stderr.write(`usage: arp check <owner/repo/skill | https://github.com/owner/repo?skill=name>
194
+ `);
195
+ return EXIT.usage;
196
+ }
197
+ process.stdout.write(await checkSkill(ref, { env: process.env, machine: thisMachine() }));
198
+ return EXIT.ok;
199
+ }
157
200
  case "line": {
158
201
  const flag = (name) => {
159
202
  const at = rest.indexOf(`--${name}`);
@@ -165,14 +208,17 @@ async function run(argv) {
165
208
  process.stderr.write(`usage: arp line --tool <url> --outcome worked|failed [--domain <category>] [--reason <reason>]
166
209
  [--parent <category> --why <text>] adds a --domain the tree lacks, under --parent
167
210
  [--payment-tx <hash> --network eip155:8453] the x402 payment this outcome refers to;
168
- --tool is then the exact URL you paid
211
+ --tool is then the exact URL you paid;
212
+ its values are removed before anything is signed
213
+ ` + ` [--pay-to <0x…> --asset <0x…> --price <units> --terms-at <unix|ISO>] with --payment-tx: the 402's
214
+ ` + ` payTo, asset and amount you paid against, and when it came
169
215
  [--needs <name>] with missing-program or missing-key: the program or variable name (never a value)
170
216
  [--with <name>] with --outcome worked: what you had to add; either is written only if the SKILL.md names it
171
217
  `);
172
218
  return EXIT.usage;
173
219
  }
174
220
  process.env["ARP_TARGET"] ??= "testnet";
175
- const { lineFromArgs, writeLineHere } = await import("./chunks/line-691gbaha.js");
221
+ const { lineFromArgs, writeLineHere } = await import("./chunks/line-wra91qkr.js");
176
222
  const input = lineFromArgs({
177
223
  toolUrl,
178
224
  outcome,
@@ -182,6 +228,10 @@ async function run(argv) {
182
228
  why: flag("why"),
183
229
  paymentTx: flag("payment-tx"),
184
230
  network: flag("network"),
231
+ payTo: flag("pay-to"),
232
+ asset: flag("asset"),
233
+ price: flag("price"),
234
+ termsAt: flag("terms-at"),
185
235
  needs: flag("needs"),
186
236
  with: flag("with")
187
237
  });
@@ -202,21 +252,21 @@ async function run(argv) {
202
252
  }
203
253
  }
204
254
  case "backfill": {
205
- const { backfill } = await import("./chunks/backfill-awengrsd.js");
255
+ const { backfill } = await import("./chunks/backfill-kzgeaffa.js");
206
256
  const found = backfill();
207
257
  process.stdout.write(`read ${found.transcripts} transcripts: ${found.uses} skill uses, ${found.added} new failures in ${found.file}
208
258
  `);
209
259
  return EXIT.ok;
210
260
  }
211
261
  case "node": {
212
- const { main } = await import("./chunks/node-m0mx4y5k.js");
262
+ const { main } = await import("./chunks/node-2rw44jrf.js");
213
263
  await main();
214
264
  return EXIT.ok;
215
265
  }
216
266
  case "init":
217
267
  case "uninstall":
218
268
  case "doctor": {
219
- const install = await import("./chunks/commands-157bms70.js");
269
+ const install = await import("./chunks/commands-vvrdahbr.js");
220
270
  const { processEnv } = await import("./chunks/env-n5ac8d1z.js");
221
271
  const flags = install.parseFlags(rest);
222
272
  if (flags instanceof Error) {