@arp-protocol/cli 0.0.4 → 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.
package/CHANGELOG.md CHANGED
@@ -3,7 +3,18 @@
3
3
  `@arp-protocol/cli`. What a person or an agent running `arp` sees change, with the task that changed it
4
4
  (`docs/tasks/<n>-*.md` in the repository). Everything writes on Intuition testnet 13579 only.
5
5
 
6
- ## 0.0.4 — unreleased
6
+ ## 0.0.5 — unreleased
7
+
8
+ - **Sharing is asked once** (task 126, ADR `0191`). After the first skill result (worked or failed) on a
9
+ machine where sharing was never chosen, the agent asks: "yes, this once", "always" or "no". Nothing
10
+ leaves without a yes. "Always" turns sharing on and sends lines in the background at the end of each
11
+ session (`arp share send --auto`, a lock, failures in `~/.arp/share-log.jsonl`), counting only results
12
+ kept after the choice. "No" is kept and the question does not come back. `ARP_SHARE_AUTO=off` stops
13
+ both the question and the background send.
14
+ - Sharing turned on any other way (`arp init`, `/arp:share-on`) now also sends at the end of a session.
15
+ - `arp share` prints when and how sharing was chosen.
16
+
17
+ ## 0.0.4 — published 2026-11-11
7
18
 
8
19
  - **The sponsoring service is hosted** (task 117): `ARP_SPONSOR` defaults to
9
20
  `https://sponsor.arp.intuition.box` instead of `http://127.0.0.1:3200`, so `arp line`, `arp share send`
package/README.md CHANGED
@@ -41,9 +41,9 @@ With no `arp` on `PATH`, its commands stand in for `arp share` and `arp line`. A
41
41
 
42
42
  ```
43
43
  /arp:share what would be public, and what stays here. Sends nothing
44
- /arp:share-on turn sharing on (off by default). Sends nothing
44
+ /arp:share-on turn sharing on (off by default); the hook then sends at the end of each session
45
45
  /arp:share-off turn it off
46
- /arp:share-send write the shared failures as public lines on testnet. Needs a sponsoring service
46
+ /arp:share-send write the shared lines now as public lines on testnet. Needs a sponsoring service
47
47
  /arp:line write one public line on testnet: --tool <url> --outcome worked|failed
48
48
  ```
49
49
 
@@ -5,7 +5,7 @@ import {
5
5
  } from "./cli-00qp1sj2.js";
6
6
  import {
7
7
  readRequires
8
- } from "./cli-wer26exb.js";
8
+ } from "./cli-g3q6fxm0.js";
9
9
  import"./cli-zehr6kdx.js";
10
10
  import"./cli-0gsbnq5w.js";
11
11
  import"./cli-fbwpcfvp.js";
@@ -9857,6 +9857,30 @@ function domainInText(text, words) {
9857
9857
  return alias === undefined ? null : words.aliases[alias];
9858
9858
  }
9859
9859
 
9860
+ // ../../packages/protocol/dist/sharing-state.js
9861
+ var SHARING_HOWS = ["init", "command", "desktop", "asked-once", "asked-always", "asked-no"];
9862
+ function sharingChoiceOf(text) {
9863
+ if (text === null)
9864
+ return null;
9865
+ try {
9866
+ const parsed = JSON.parse(text);
9867
+ if (typeof parsed !== "object" || parsed === null)
9868
+ return { on: false, at: null, how: null };
9869
+ const { failures, at, how } = parsed;
9870
+ return {
9871
+ on: failures === true,
9872
+ at: typeof at === "string" ? at : null,
9873
+ how: typeof how === "string" && SHARING_HOWS.includes(how) ? how : null
9874
+ };
9875
+ } catch {
9876
+ return { on: false, at: null, how: null };
9877
+ }
9878
+ }
9879
+ function sharingFileText(on, how, at) {
9880
+ return `${JSON.stringify({ failures: on, at: at.toISOString(), how })}
9881
+ `;
9882
+ }
9883
+
9860
9884
  // ../../packages/protocol/dist/declare/line-need.js
9861
9885
  var NEEDS_LABEL2 = "needs";
9862
9886
  var WITH_LABEL2 = "with";
@@ -13255,15 +13279,6 @@ ${path}`;
13255
13279
  quota: () => ({ ...quota })
13256
13280
  };
13257
13281
  }
13258
- // ../../packages/protocol/dist/share-offer.js
13259
- var SHARE_OFFER = {
13260
- question: "Share this machine's skill failures as public lines?",
13261
- sends: `When a skill fails here, one public line on Intuition testnet: the skill's public address, its category when known, "failed" and a closed reason (missing-key, missing-program, missing-file).`,
13262
- signer: "Each line is signed by this machine's own ARP address, created on the first send: it is public and the same on every line, so this machine's lines can be told apart from others' and linked together.",
13263
- never: "Never your prompts, your files, your paths, your code or your keys.",
13264
- service: "Sending goes through ARP's sponsoring service, which is not online yet: until it is, nothing is sent and `arp share send` says so.",
13265
- later: "Off by default. Change it any time with `arp share on` or `arp share off`."
13266
- };
13267
13282
  // ../../packages/protocol/dist/pulse-score.js
13268
13283
  var SCORE_VERSION = "pulse-score-3";
13269
13284
  var PRIOR_TOP = 0.8;
@@ -15202,4 +15217,4 @@ var RUNG_NAMES = {
15202
15217
  };
15203
15218
  var API_NAMES = new Set(["api", "rest", "openai", "jsonrpc", "grpc"]);
15204
15219
  var ANSWERED = new Set(["live", "serves-declared-protocol", "declared-protocol-not-served"]);
15205
- export { decodeEventLog, createPublicClient, createWalletClient, http, verifyMessage3 as verifyMessage, parseEther2 as parseEther, resolveRef, toCaip19, parseCaip19, isTransactionAbsent, HAS_TAG, IS_A_SUB_DOMAIN_OF, USES, intuitionSource, createErc8004Client, IPFS_GATEWAYS, privateProbeTarget, guardedFetchText, probeEndpoint, resolveAgent, listAgents, readDeclaredEndpoints, partitionByCheckability, getToolUsage, multiVaultDeclareAbi, multiVaultReads, prepareMarketPosition, prepareMarketExit, toolAtomUri, redactEndpointUrl, intuitionToolAtomLookup, prepareDeclareUsed, prepareStakeOnUsed, resolveDeclarationSubject, normaliseCategoryLabel, isCategoryIn, intuitionTestnet, ARP_DOMAIN_ATTESTORS, chainIdOfNetwork, paymentAtomLabel, isExactEndpointUrl, transfersIn, x402NetworksFrom, x402Reading, X402_DEFAULT_NETWORKS, listTools, listRegisteredTools, verdictOf, readReachabilityState2 as readReachabilityState, planReachability2 as planReachability, readDomainTree, toolsInDomain, findAgents, parseSessionLog, resolveInvocations, skillRefFromUrl, resolveContentPath, readRequires, contentsFileOf, readSkillContents, parseSkillsFind, fuseSearches, readPulse, groupByContent, githubPulseSource, resolveDomain, domainInText, SHARE_OFFER, NEEDS_LABEL2 as NEEDS_LABEL, WITH_LABEL2 as WITH_LABEL, needKindOfReason2 as needKindOfReason, publishableNeed2 as publishableNeed, needShapeProblem2 as needShapeProblem, HAS_OUTCOME_LABEL2 as HAS_OUTCOME_LABEL, USAGE_REASONS2 as USAGE_REASONS, BECAUSE_LABEL2 as BECAUSE_LABEL, isUsageOutcome2 as isUsageOutcome, isUsageReason2 as isUsageReason, prepareUsageContext2 as prepareUsageContext, budgetRefusal2 as budgetRefusal, parseLocalFailures, yourFailures, parseLocalOutcomes, isSharingOn, installedFromSkillLock, installedFromReceipts, sharedLineKey, decideShare, SCORE_VERSION, WALLETS_TO_MOVE, signalOf, entersList, unconfirmedNote, scoreSkill, findToolAtoms, findDeclaredTools, readThingUrl, workedInDomain, readEdgeWriters, getPulseUsage, HIRE_RECEIPT_VERSION, hireDigest, signHireReceipt, atomIsCanonicalFor, resolveAgentAtom, walletControlsSubject, findAgentsInDomains, findAgentsWithEndpoints, wordsMatchingOffer, readLineReceipts, receiptHolds, contentRequestTypedData, recoverContentRequester, verifyContentReceipt, corroborationOf, receiptMatchesContent, serveContent, contentIndexOf, bundleMatchesReceipt, serveSkillBundle, reviewSkill, renderSkillPrompt, contentsLines, repoFacts, endpointFacts, parseAgentRegistry, normaliseEndpoint, validateHandshakeRequest, asEndpointBinding, endpointBindingDocument, getAgentEndpoints, encodeApproveDeposit, sponsorProofDigest, signSponsorRequest, signMintGasRequest, signListToolRequest, signLineRequest, CATEGORY_REQUEST_TYPES, categoryRequestTypedData, signCategoryRequest, verifyCategoryRequest, searchSkillsIndex, RUNG_NAMES };
15220
+ export { decodeEventLog, createPublicClient, createWalletClient, http, verifyMessage3 as verifyMessage, parseEther2 as parseEther, resolveRef, toCaip19, parseCaip19, isTransactionAbsent, HAS_TAG, IS_A_SUB_DOMAIN_OF, USES, intuitionSource, createErc8004Client, IPFS_GATEWAYS, privateProbeTarget, guardedFetchText, probeEndpoint, resolveAgent, listAgents, readDeclaredEndpoints, partitionByCheckability, getToolUsage, multiVaultDeclareAbi, multiVaultReads, prepareMarketPosition, prepareMarketExit, toolAtomUri, redactEndpointUrl, intuitionToolAtomLookup, prepareDeclareUsed, prepareStakeOnUsed, resolveDeclarationSubject, normaliseCategoryLabel, isCategoryIn, intuitionTestnet, ARP_DOMAIN_ATTESTORS, chainIdOfNetwork, paymentAtomLabel, isExactEndpointUrl, transfersIn, x402NetworksFrom, x402Reading, X402_DEFAULT_NETWORKS, listTools, listRegisteredTools, verdictOf, readReachabilityState2 as readReachabilityState, planReachability2 as planReachability, readDomainTree, toolsInDomain, findAgents, parseSessionLog, resolveInvocations, skillRefFromUrl, resolveContentPath, readRequires, contentsFileOf, readSkillContents, parseSkillsFind, fuseSearches, readPulse, groupByContent, githubPulseSource, resolveDomain, domainInText, sharingChoiceOf, sharingFileText, NEEDS_LABEL2 as NEEDS_LABEL, WITH_LABEL2 as WITH_LABEL, needKindOfReason2 as needKindOfReason, publishableNeed2 as publishableNeed, needShapeProblem2 as needShapeProblem, HAS_OUTCOME_LABEL2 as HAS_OUTCOME_LABEL, USAGE_REASONS2 as USAGE_REASONS, BECAUSE_LABEL2 as BECAUSE_LABEL, isUsageOutcome2 as isUsageOutcome, isUsageReason2 as isUsageReason, prepareUsageContext2 as prepareUsageContext, budgetRefusal2 as budgetRefusal, parseLocalFailures, yourFailures, parseLocalOutcomes, isSharingOn, installedFromSkillLock, installedFromReceipts, sharedLineKey, decideShare, SCORE_VERSION, WALLETS_TO_MOVE, signalOf, entersList, unconfirmedNote, scoreSkill, findToolAtoms, findDeclaredTools, readThingUrl, workedInDomain, readEdgeWriters, getPulseUsage, HIRE_RECEIPT_VERSION, hireDigest, signHireReceipt, atomIsCanonicalFor, resolveAgentAtom, walletControlsSubject, findAgentsInDomains, findAgentsWithEndpoints, wordsMatchingOffer, readLineReceipts, receiptHolds, contentRequestTypedData, recoverContentRequester, verifyContentReceipt, corroborationOf, receiptMatchesContent, serveContent, contentIndexOf, bundleMatchesReceipt, serveSkillBundle, reviewSkill, renderSkillPrompt, contentsLines, repoFacts, endpointFacts, parseAgentRegistry, normaliseEndpoint, validateHandshakeRequest, asEndpointBinding, endpointBindingDocument, getAgentEndpoints, encodeApproveDeposit, sponsorProofDigest, signSponsorRequest, signMintGasRequest, signListToolRequest, signLineRequest, CATEGORY_REQUEST_TYPES, categoryRequestTypedData, signCategoryRequest, verifyCategoryRequest, searchSkillsIndex, RUNG_NAMES };
@@ -50,7 +50,7 @@ import {
50
50
  skillRefFromUrl,
51
51
  sponsorProofDigest,
52
52
  verifyContentReceipt
53
- } from "./cli-wer26exb.js";
53
+ } from "./cli-g3q6fxm0.js";
54
54
  import {
55
55
  encodeFunctionData
56
56
  } from "./cli-0gsbnq5w.js";
@@ -13,7 +13,7 @@ import {
13
13
  statSync,
14
14
  writeFileSync
15
15
  } from "node:fs";
16
- import { dirname, join as join2 } from "node:path";
16
+ import { dirname as dirname2, join as join2 } from "node:path";
17
17
 
18
18
  // ../../packages/protocol/src/share-offer.ts
19
19
  var SHARE_OFFER = {
@@ -28,9 +28,15 @@ function shareOfferLines() {
28
28
  return [SHARE_OFFER.sends, SHARE_OFFER.signer, SHARE_OFFER.never, SHARE_OFFER.service, SHARE_OFFER.later];
29
29
  }
30
30
 
31
+ // ../../packages/protocol/src/sharing-state.ts
32
+ function sharingFileText(on, how, at) {
33
+ return `${JSON.stringify({ failures: on, at: at.toISOString(), how })}
34
+ `;
35
+ }
36
+
31
37
  // src/install/targets.ts
32
38
  import { existsSync, readFileSync } from "node:fs";
33
- import { join } from "node:path";
39
+ import { dirname, join } from "node:path";
34
40
 
35
41
  // src/install/edits.ts
36
42
  function isRecord(value) {
@@ -68,7 +74,8 @@ var CLAUDE_HOOKS = {
68
74
  { name: "PostToolUse", matcher: "Skill", sync: true, timeout: 5 },
69
75
  { name: "PostToolUseFailure", matcher: "*", sync: true },
70
76
  { name: "PreToolUse", matcher: "Bash", sync: true, timeout: 15, if: "Bash(*skills*)" },
71
- { name: "Stop", sync: true, timeout: 10 }
77
+ { name: "Stop", sync: true, timeout: 10 },
78
+ { name: "SessionEnd", sync: true, timeout: 5 }
72
79
  ],
73
80
  entry: (command, event) => ({
74
81
  type: "command",
@@ -244,10 +251,10 @@ function read(path) {
244
251
  return null;
245
252
  }
246
253
  }
247
- function npxLaunch(pkg) {
254
+ function npxLaunch(pkg, command = "mcp") {
248
255
  return {
249
256
  command: "sh",
250
- args: ["-c", `export ARP_PROJECT_DIR="$PWD"; cd / && exec npx --prefix / -y --fetch-retries=0 --fetch-timeout=5000 ${pkg} mcp`]
257
+ args: ["-c", `export ARP_PROJECT_DIR="$PWD"; cd / && exec npx --prefix / -y --fetch-retries=0 --fetch-timeout=5000 ${pkg} ${command}`]
251
258
  };
252
259
  }
253
260
  var SERVER_ENV = { ARP_TARGET: "testnet" };
@@ -266,7 +273,8 @@ function commandsFor(env, cliPath, capture = false, sponsored = false, force = f
266
273
  env: sponsored ? SPONSORED_ENV : SERVER_ENV
267
274
  };
268
275
  const hook = `"${env.node}" "${hookFile}"${capture ? " --capture" : ""}`;
269
- return { mcp, hook, hookFile, cliPath, capture, force };
276
+ const send = fromNpx ? npxLaunch(`@arp-protocol/cli@${env.version}`, "share send --auto") : { command: env.node, args: [cliPath, "share", "send", "--auto"] };
277
+ return { mcp, hook, hookFile, sendFile: join(dirname(hookFile), "send.json"), send, cliPath, capture, force };
270
278
  }
271
279
  function diffLines(before, after) {
272
280
  const lines = (value, sign) => value === undefined || typeof value === "object" && value !== null && Object.keys(value).length === 0 ? [] : JSON.stringify(value, null, 2).split(`
@@ -669,7 +677,7 @@ function apply(env, step) {
669
677
  rmSync(step.path, { force: true });
670
678
  return null;
671
679
  }
672
- mkdirSync(dirname(step.path), { recursive: true });
680
+ mkdirSync(dirname2(step.path), { recursive: true });
673
681
  const mode = step.before === null ? undefined : statSync(step.path).mode & 511;
674
682
  const temp = `${step.path}.arp-tmp`;
675
683
  writeFileSync(temp, step.after, mode === undefined ? undefined : { mode });
@@ -700,6 +708,19 @@ function hookFileStep(env, commands, remove) {
700
708
  diff: []
701
709
  };
702
710
  }
711
+ function sendFileStep(commands, remove) {
712
+ const before = read2(commands.sendFile);
713
+ const after = remove ? null : `${JSON.stringify(commands.send, null, 2)}
714
+ `;
715
+ return {
716
+ kind: "file",
717
+ path: commands.sendFile,
718
+ before,
719
+ after,
720
+ summary: remove ? "removed" : "how the hook starts `arp share send --auto` when sharing is on",
721
+ diff: []
722
+ };
723
+ }
703
724
  async function consent(env, flags, question) {
704
725
  if (flags.yes)
705
726
  return true;
@@ -775,7 +796,7 @@ async function init(env, flags, cliPath) {
775
796
  steps: runtime.install(env, commands, flags.project)
776
797
  }));
777
798
  if (installsHook(runtimes, flags.capture)) {
778
- plan.push({ runtime: "hook", steps: [hookFileStep(env, commands, false)] });
799
+ plan.push({ runtime: "hook", steps: [hookFileStep(env, commands, false), sendFileStep(commands, false)] });
779
800
  }
780
801
  const code = await change(env, flags, plan, "Install");
781
802
  if ((code === EXIT.ok || code === EXIT.partly) && !flags.dryRun)
@@ -787,8 +808,7 @@ async function offerSharing(env, flags) {
787
808
  const file = join2(home, "sharing.json");
788
809
  const save = (on) => {
789
810
  mkdirSync(home, { recursive: true });
790
- writeFileSync(file, `${JSON.stringify({ failures: on })}
791
- `);
811
+ writeFileSync(file, sharingFileText(on, "init", env.now()));
792
812
  };
793
813
  if (flags.share !== null) {
794
814
  save(flags.share);
@@ -813,7 +833,7 @@ async function uninstall(env, flags, cliPath) {
813
833
  runtime: runtime.name,
814
834
  steps: runtime.remove(env, commands, flags.project)
815
835
  }));
816
- plan.push({ runtime: "hook", steps: [hookFileStep(env, commands, true)] });
836
+ plan.push({ runtime: "hook", steps: [hookFileStep(env, commands, true), sendFileStep(commands, true)] });
817
837
  const code = await change(env, flags, plan, "Remove");
818
838
  const captures = join2(env.home, ".arp", "captures");
819
839
  if (existsSync2(captures))
@@ -7,7 +7,7 @@ import {
7
7
  loopConfig,
8
8
  sponsorApproval,
9
9
  writeLine
10
- } from "./cli-8m56jsfx.js";
10
+ } from "./cli-jew4syvf.js";
11
11
  import"./cli-7c393v47.js";
12
12
  import"./cli-00qp1sj2.js";
13
13
  import"./cli-n4qagybd.js";
@@ -28,7 +28,7 @@ import {
28
28
  transfersIn,
29
29
  x402NetworksFrom,
30
30
  x402Reading
31
- } from "./cli-wer26exb.js";
31
+ } from "./cli-g3q6fxm0.js";
32
32
  import"./cli-zehr6kdx.js";
33
33
  import"./cli-0gsbnq5w.js";
34
34
  import"./cli-fbwpcfvp.js";
@@ -18,7 +18,7 @@ import {
18
18
  normaliseEndpoint,
19
19
  parseEther,
20
20
  signHireReceipt
21
- } from "./cli-wer26exb.js";
21
+ } from "./cli-g3q6fxm0.js";
22
22
  import"./cli-zehr6kdx.js";
23
23
  import"./cli-0gsbnq5w.js";
24
24
  import {
@@ -9,7 +9,7 @@ import {
9
9
  normaliseEndpoint,
10
10
  parseAgentRegistry,
11
11
  validateHandshakeRequest
12
- } from "./cli-wer26exb.js";
12
+ } from "./cli-g3q6fxm0.js";
13
13
  import"./cli-zehr6kdx.js";
14
14
  import"./cli-0gsbnq5w.js";
15
15
  import"./cli-fbwpcfvp.js";
@@ -48,7 +48,7 @@ import {
48
48
  targetName,
49
49
  text,
50
50
  toolAtoms
51
- } from "./cli-8m56jsfx.js";
51
+ } from "./cli-jew4syvf.js";
52
52
  import"./cli-7c393v47.js";
53
53
  import"./cli-00qp1sj2.js";
54
54
  import {
@@ -69,7 +69,6 @@ import {
69
69
  NEEDS_LABEL,
70
70
  RUNG_NAMES,
71
71
  SCORE_VERSION,
72
- SHARE_OFFER,
73
72
  USAGE_REASONS,
74
73
  WALLETS_TO_MOVE,
75
74
  WITH_LABEL,
@@ -135,6 +134,7 @@ import {
135
134
  resolveRef,
136
135
  scoreSkill,
137
136
  searchSkillsIndex,
137
+ sharingFileText,
138
138
  signalOf,
139
139
  skillRefFromUrl,
140
140
  toCaip19,
@@ -149,7 +149,7 @@ import {
149
149
  wordsMatchingOffer,
150
150
  workedInDomain,
151
151
  yourFailures
152
- } from "./cli-wer26exb.js";
152
+ } from "./cli-g3q6fxm0.js";
153
153
  import"./cli-zehr6kdx.js";
154
154
  import {
155
155
  parseAbi
@@ -166,26 +166,13 @@ import"./cli-3kbbwfbp.js";
166
166
  import"./cli-g5zvr10v.js";
167
167
 
168
168
  // ../mcp/src/share-ask.ts
169
- import { existsSync, mkdirSync, writeFileSync } from "node:fs";
169
+ import { mkdirSync, writeFileSync } from "node:fs";
170
170
  import { join } from "node:path";
171
- function shareAskInstructions(arpHome2) {
172
- if (existsSync(join(arpHome2, "sharing.json")))
173
- return null;
174
- return [
175
- "SHARING IS NOT SET YET. Once in this session, at a natural pause (never in the middle of a task), ask the",
176
- `person: "${SHARE_OFFER.question}" and give them, in your own short words, exactly this: ${SHARE_OFFER.sends}`,
177
- `${SHARE_OFFER.signer} ${SHARE_OFFER.never} ${SHARE_OFFER.service}`,
178
- "It is off unless they agree. If they agree, they type /arp:share-on; if not, /arp:share-off, so the",
179
- "question is not asked again. Do not run either yourself, and do not ask twice."
180
- ].join(`
181
- `);
182
- }
183
171
  function applyDesktopShare(value, arpHome2) {
184
172
  if (value !== "true" && value !== "false")
185
173
  return null;
186
174
  mkdirSync(arpHome2, { recursive: true });
187
- writeFileSync(join(arpHome2, "sharing.json"), `${JSON.stringify({ failures: value === "true" })}
188
- `);
175
+ writeFileSync(join(arpHome2, "sharing.json"), sharingFileText(value === "true", "desktop", new Date));
189
176
  return value === "true";
190
177
  }
191
178
 
@@ -5882,7 +5869,7 @@ async function findTools(id, args) {
5882
5869
  }
5883
5870
 
5884
5871
  // ../mcp/src/handlers/pulse.ts
5885
- import { chmodSync as chmodSync2, existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync3, rmSync } from "node:fs";
5872
+ import { chmodSync as chmodSync2, existsSync, mkdirSync as mkdirSync3, readFileSync as readFileSync3, rmSync } from "node:fs";
5886
5873
  import { homedir } from "node:os";
5887
5874
  import { join as join3, delimiter } from "node:path";
5888
5875
 
@@ -6207,7 +6194,7 @@ function searchHome() {
6207
6194
  return join3(arpHome(process.env, homedir()), "skills-cli");
6208
6195
  }
6209
6196
  function searchIsCached() {
6210
- return existsSync2(join3(searchHome(), ".npm", "_npx"));
6197
+ return existsSync(join3(searchHome(), ".npm", "_npx"));
6211
6198
  }
6212
6199
  async function search(query, timeoutMs, stop, firstDownload = false) {
6213
6200
  if (!canSearch({ holdsAgentKey: DESKTOP, setting: process.env["ARP_PULSE_SEARCH"] })) {
@@ -6316,7 +6303,7 @@ function usageLine(usage, domain) {
6316
6303
  function readYourFailures() {
6317
6304
  try {
6318
6305
  const path = join3(arpHome(process.env, homedir()), "failures.jsonl");
6319
- return existsSync2(path) ? parseLocalFailures(readFileSync3(path, "utf8")) : [];
6306
+ return existsSync(path) ? parseLocalFailures(readFileSync3(path, "utf8")) : [];
6320
6307
  } catch {
6321
6308
  return [];
6322
6309
  }
@@ -6811,9 +6798,7 @@ var handler = {
6811
6798
  protocolVersion: PROTOCOL_VERSION,
6812
6799
  capabilities: { tools: {} },
6813
6800
  serverInfo: { name: "ARP", version: "0.2.0" },
6814
- instructions: DESKTOP ? DESKTOP_INSTRUCTIONS : HOSTED ? READ_INSTRUCTIONS : READ_ONLY ? [READ_INSTRUCTIONS, shareAskInstructions(arpHome())].filter((part) => part !== null).join(`
6815
-
6816
- `) : SERVER_INSTRUCTIONS
6801
+ instructions: DESKTOP ? DESKTOP_INSTRUCTIONS : READ_ONLY || HOSTED ? READ_INSTRUCTIONS : SERVER_INSTRUCTIONS
6817
6802
  });
6818
6803
  case "notifications/initialized":
6819
6804
  return new Response(null, { status: 202 });
@@ -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.4";
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,11 +23,14 @@ 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
31
34
  arp check <skill> before you use a skill: what its files contain, what agents wrote about
32
35
  it, and whether what it needed when it failed is on this machine.
33
36
  <owner/repo/skill> or a github.com URL with ?skill=. Writes nothing
@@ -75,7 +78,7 @@ var asksHelp = (rest) => rest.some((arg) => arg === "--help" || arg === "-h");
75
78
  var WANTS_HELP = asksHelp(ARGV.slice(1));
76
79
  var STDIO_INPUT = ARGV[0] === "mcp" && !ARGV.includes("--http") && !WANTS_HELP ? bufferedCopy(process.stdin) : undefined;
77
80
  async function runNodeInit(rest) {
78
- const { nodeInit, parseNodeInitFlags } = await import("./chunks/node-init-amedveja.js");
81
+ const { nodeInit, parseNodeInitFlags } = await import("./chunks/node-init-rmrmmg59.js");
79
82
  const flags = parseNodeInitFlags(rest);
80
83
  if (flags instanceof Error) {
81
84
  process.stderr.write(`arp init --node: ${flags.message}
@@ -105,18 +108,42 @@ async function runNodeInit(rest) {
105
108
  async function shareCommand(rest) {
106
109
  const { arpHome } = await import("./chunks/index-jrs2shcc.js");
107
110
  const { homedir } = await import("node:os");
108
- const share = await import("./chunks/share-ax2k8805.js");
109
- 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 };
110
114
  if (rest[0] === "send") {
111
- if (!share.decide(env).on) {
112
- 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");
113
120
  return EXIT.ok;
114
121
  }
122
+ if (auto && decision.lines.length === 0)
123
+ return EXIT.ok;
115
124
  process.env["ARP_TARGET"] ??= "testnet";
116
- const { writeLineHere } = await import("./chunks/line-kvm1ew55.js");
117
- const done = await share.sendShared(env, async (line) => {
118
- await writeLineHere(line);
119
- });
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
+ }
120
147
  process.stdout.write(`sent ${done.sent}, failed ${done.failed}
121
148
  `);
122
149
  if (done.firstError !== null)
@@ -144,7 +171,7 @@ async function run(argv) {
144
171
  return runNodeInit(rest);
145
172
  switch (command) {
146
173
  case "mcp": {
147
- const { main } = await import("./chunks/server-ek7fet5m.js");
174
+ const { main } = await import("./chunks/server-bcsxfd7q.js");
148
175
  await main(rest.includes("--http") ? rest : ["--stdio", ...rest], STDIO_INPUT);
149
176
  return EXIT.ok;
150
177
  }
@@ -191,7 +218,7 @@ async function run(argv) {
191
218
  return EXIT.usage;
192
219
  }
193
220
  process.env["ARP_TARGET"] ??= "testnet";
194
- const { lineFromArgs, writeLineHere } = await import("./chunks/line-kvm1ew55.js");
221
+ const { lineFromArgs, writeLineHere } = await import("./chunks/line-wra91qkr.js");
195
222
  const input = lineFromArgs({
196
223
  toolUrl,
197
224
  outcome,
@@ -225,21 +252,21 @@ async function run(argv) {
225
252
  }
226
253
  }
227
254
  case "backfill": {
228
- const { backfill } = await import("./chunks/backfill-5zmh0t43.js");
255
+ const { backfill } = await import("./chunks/backfill-kzgeaffa.js");
229
256
  const found = backfill();
230
257
  process.stdout.write(`read ${found.transcripts} transcripts: ${found.uses} skill uses, ${found.added} new failures in ${found.file}
231
258
  `);
232
259
  return EXIT.ok;
233
260
  }
234
261
  case "node": {
235
- const { main } = await import("./chunks/node-x35ncwhq.js");
262
+ const { main } = await import("./chunks/node-2rw44jrf.js");
236
263
  await main();
237
264
  return EXIT.ok;
238
265
  }
239
266
  case "init":
240
267
  case "uninstall":
241
268
  case "doctor": {
242
- const install = await import("./chunks/commands-t5a0w785.js");
269
+ const install = await import("./chunks/commands-vvrdahbr.js");
243
270
  const { processEnv } = await import("./chunks/env-n5ac8d1z.js");
244
271
  const flags = install.parseFlags(rest);
245
272
  if (flags instanceof Error) {
package/dist/hook.js CHANGED
@@ -14,9 +14,9 @@ function appendOwn(path, text) {
14
14
  }
15
15
 
16
16
  // ../hook/src/hook.ts
17
- import { existsSync as existsSync3, lstatSync as lstatSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
17
+ import { existsSync as existsSync4, lstatSync as lstatSync2, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
18
18
  import { homedir as homedir3 } from "node:os";
19
- import { dirname as dirname3, join as join5 } from "node:path";
19
+ import { dirname as dirname4, join as join6 } from "node:path";
20
20
  // ../../packages/protocol/dist/declare/tool-thing.js
21
21
  class InvalidToolUrlError extends Error {
22
22
  input;
@@ -4622,19 +4622,200 @@ async function postToolUseFailure(payload) {
4622
4622
  return context === null ? null : failureOutput(context);
4623
4623
  }
4624
4624
 
4625
+ // ../hook/src/share-ask.ts
4626
+ import { spawn } from "node:child_process";
4627
+ import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
4628
+ import { dirname as dirname3, join as join4 } from "node:path";
4629
+ import { fileURLToPath } from "node:url";
4630
+
4631
+ // ../../packages/protocol/src/sharing-state.ts
4632
+ var SHARING_HOWS2 = ["init", "command", "desktop", "asked-once", "asked-always", "asked-no"];
4633
+ function sharingChoiceOf2(text) {
4634
+ if (text === null)
4635
+ return null;
4636
+ try {
4637
+ const parsed2 = JSON.parse(text);
4638
+ if (typeof parsed2 !== "object" || parsed2 === null)
4639
+ return { on: false, at: null, how: null };
4640
+ const { failures, at, how } = parsed2;
4641
+ return {
4642
+ on: failures === true,
4643
+ at: typeof at === "string" ? at : null,
4644
+ how: typeof how === "string" && SHARING_HOWS2.includes(how) ? how : null
4645
+ };
4646
+ } catch {
4647
+ return { on: false, at: null, how: null };
4648
+ }
4649
+ }
4650
+ function sharingFileText2(on, how, at) {
4651
+ return `${JSON.stringify({ failures: on, at: at.toISOString(), how })}
4652
+ `;
4653
+ }
4654
+ function choiceForAnswer2(answer) {
4655
+ return { on: answer === "always", how: `asked-${answer}` };
4656
+ }
4657
+ var ANSWER = /^[\s>*`-]*arp-share:\s*(once|always|no)[\s.`*]*$/gim;
4658
+ function shareAnswerIn2(text) {
4659
+ const found = new Set;
4660
+ for (const match of text.matchAll(ANSWER)) {
4661
+ const word = match[1]?.toLowerCase();
4662
+ if (word === "once" || word === "always" || word === "no")
4663
+ found.add(word);
4664
+ }
4665
+ return found.size === 1 ? [...found][0] ?? null : null;
4666
+ }
4667
+
4668
+ // ../hook/src/share-ask.ts
4669
+ var isOff = () => process.env["ARP_SHARE_AUTO"]?.trim().toLowerCase() === "off";
4670
+ var GITHUB_REPO = /^[\w.-]+\/[\w.-]+$/;
4671
+ var readIfThere2 = (path) => existsSync2(path) ? readFileSync3(path, "utf8") : null;
4672
+ function shareQuestionFor() {
4673
+ return " Sharing is not set on this machine yet. If that line says worked or failed, ask the person once, right after it, " + "in their language: share this result as a public line on Intuition testnet? Three choices: yes, this once; " + "always (this result and every one from now on, sent at the end of each session; earlier ones stay here); no (not asked again). Say what would leave: " + "the skill's public address, its category, worked or failed with a closed reason, signed by this machine's own " + "ARP address. Never their prompts, files, paths, code or keys. Do not choose for them. At the end of your reply to " + "their answer, add one line: `arp-share: once`, `arp-share: always` or `arp-share: no`, the one they chose; if " + "they did not answer it, add none.";
4674
+ }
4675
+ function stateOf(home) {
4676
+ const text = readIfThere2(join4(home, "share-question.json"));
4677
+ if (text === null)
4678
+ return null;
4679
+ try {
4680
+ const row = JSON.parse(text);
4681
+ if (typeof row.session !== "string" || typeof row.skill !== "string")
4682
+ return null;
4683
+ if (row.state !== "offered" && row.state !== "put" && row.state !== "answered")
4684
+ return null;
4685
+ return { at: typeof row.at === "string" ? row.at : "", session: row.session, skill: row.skill, state: row.state };
4686
+ } catch {
4687
+ return { at: "", session: "", skill: "", state: "put" };
4688
+ }
4689
+ }
4690
+ var writeState = (home, state) => writeFileSync2(join4(home, "share-question.json"), `${JSON.stringify(state)}
4691
+ `);
4692
+ function offerShare(session, skill, source, cwd, now = new Date) {
4693
+ if (isOff())
4694
+ return false;
4695
+ try {
4696
+ const home = arpHome();
4697
+ if (existsSync2(join4(home, "sharing.json")))
4698
+ return false;
4699
+ const state = stateOf(home);
4700
+ if (state !== null && (state.state !== "offered" || state.session === session))
4701
+ return false;
4702
+ const name = skill.split(":").pop() ?? skill;
4703
+ const known = source ?? installedSource(name, cwd);
4704
+ if (known === null || !GITHUB_REPO.test(known))
4705
+ return false;
4706
+ mkdirSync3(home, { recursive: true });
4707
+ writeState(home, { at: now.toISOString(), session, skill, state: "offered" });
4708
+ return true;
4709
+ } catch {
4710
+ return false;
4711
+ }
4712
+ }
4713
+ function onStopShare(session, last, recorded, now = new Date) {
4714
+ if (isOff())
4715
+ return;
4716
+ try {
4717
+ const home = arpHome();
4718
+ const state = stateOf(home);
4719
+ if (state === null || state.session !== session)
4720
+ return;
4721
+ if (state.state === "put") {
4722
+ if (existsSync2(join4(home, "sharing.json")))
4723
+ return;
4724
+ const answer = shareAnswerIn2(last);
4725
+ if (answer === null)
4726
+ return;
4727
+ const choice = choiceForAnswer2(answer);
4728
+ writeFileSync2(join4(home, "sharing.json"), sharingFileText2(choice.on, choice.how, now));
4729
+ if (answer !== "no") {
4730
+ appendOwn(join4(home, "share-once.jsonl"), `${JSON.stringify({ at: now.toISOString(), session, skill: state.skill })}
4731
+ `);
4732
+ }
4733
+ writeState(home, { ...state, state: "answered", answer, at: now.toISOString() });
4734
+ return;
4735
+ }
4736
+ if (state.state !== "offered")
4737
+ return;
4738
+ const outcome = recorded.get(state.skill);
4739
+ if (outcome === "worked" || outcome === "failed")
4740
+ writeState(home, { ...state, state: "put", at: now.toISOString() });
4741
+ } catch {}
4742
+ }
4743
+ function senderOf(here = dirname3(fileURLToPath(import.meta.url))) {
4744
+ const send = ["share", "send", "--auto"];
4745
+ const given = process.env["ARP_CLI"]?.trim();
4746
+ if (given !== undefined && given !== "")
4747
+ return { command: process.execPath, args: [given, ...send] };
4748
+ for (const name of ["arp.js", "cli.js"]) {
4749
+ if (existsSync2(join4(here, name)))
4750
+ return { command: process.execPath, args: [join4(here, name), ...send] };
4751
+ }
4752
+ const written = readIfThere2(join4(here, "send.json"));
4753
+ if (written === null)
4754
+ return null;
4755
+ try {
4756
+ const parsed2 = JSON.parse(written);
4757
+ if (typeof parsed2.command !== "string" || !Array.isArray(parsed2.args))
4758
+ return null;
4759
+ const args = parsed2.args.filter((arg) => typeof arg === "string");
4760
+ return { command: parsed2.command, args };
4761
+ } catch {
4762
+ return null;
4763
+ }
4764
+ }
4765
+ function onceFor(home, session) {
4766
+ for (const line of (readIfThere2(join4(home, "share-once.jsonl")) ?? "").split(`
4767
+ `)) {
4768
+ try {
4769
+ if (JSON.parse(line).session === session)
4770
+ return true;
4771
+ } catch {
4772
+ continue;
4773
+ }
4774
+ }
4775
+ return false;
4776
+ }
4777
+ async function sessionEnd(payload) {
4778
+ if (isOff() || typeof payload !== "object" || payload === null)
4779
+ return null;
4780
+ const raw = payload;
4781
+ const session = raw["session_id"] ?? raw["sessionId"];
4782
+ if (typeof session !== "string")
4783
+ return null;
4784
+ const cwd = typeof raw["cwd"] === "string" ? raw["cwd"] : process.cwd();
4785
+ try {
4786
+ const home = arpHome();
4787
+ const choice = sharingChoiceOf2(readIfThere2(join4(home, "sharing.json")));
4788
+ if (choice?.on !== true && !onceFor(home, session))
4789
+ return null;
4790
+ const sender = senderOf();
4791
+ if (sender === null) {
4792
+ mkdirSync3(home, { recursive: true });
4793
+ appendOwn(join4(home, "share-log.jsonl"), `${JSON.stringify({ at: new Date().toISOString(), sent: 0, failed: 0, error: "no arp program beside the hook; run arp share send" })}
4794
+ `);
4795
+ return null;
4796
+ }
4797
+ const child = spawn(sender.command, sender.args, { cwd, detached: true, stdio: "ignore", env: process.env });
4798
+ child.on("error", () => {
4799
+ return;
4800
+ });
4801
+ child.unref();
4802
+ } catch {}
4803
+ return null;
4804
+ }
4805
+
4625
4806
  // ../hook/src/worked.ts
4626
- import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync3 } from "node:fs";
4627
- import { join as join4 } from "node:path";
4807
+ import { existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync4 } from "node:fs";
4808
+ import { join as join5 } from "node:path";
4628
4809
 
4629
4810
  // ../../packages/protocol/src/local-outcomes.ts
4630
4811
  var OUTCOME_ANSWERS2 = ["worked", "failed", "unsure"];
4631
4812
  function isOutcomeAnswer2(word) {
4632
4813
  return OUTCOME_ANSWERS2.includes(word);
4633
4814
  }
4634
- var ANSWER = /^[\s>*`-]*arp:\s*([A-Za-z0-9._:@/-]+)\s+(worked|failed|unsure)[\s.`*]*$/gim;
4815
+ var ANSWER2 = /^[\s>*`-]*arp:\s*([A-Za-z0-9._:@/-]+)\s+(worked|failed|unsure)[\s.`*]*$/gim;
4635
4816
  function answersIn2(text, asked) {
4636
4817
  const found = new Map;
4637
- for (const match of text.matchAll(ANSWER)) {
4818
+ for (const match of text.matchAll(ANSWER2)) {
4638
4819
  const [, skill, word] = match;
4639
4820
  if (skill === undefined || word === undefined)
4640
4821
  continue;
@@ -4665,18 +4846,18 @@ function stopInputOf(raw) {
4665
4846
  transcript: text(field(raw, "transcript_path", "transcriptPath"))
4666
4847
  };
4667
4848
  }
4668
- function requestFor(skill) {
4669
- return `ARP: at the end of your final reply, add one line saying whether the skill ${skill} did its job: ` + `\`arp: ${skill} worked\`, \`arp: ${skill} failed\` or \`arp: ${skill} unsure\`. ` + "Kept on this machine unless `arp share` is on.";
4849
+ function requestFor(skill, share = false) {
4850
+ return `ARP: at the end of your final reply, add one line saying whether the skill ${skill} did its job: ` + `\`arp: ${skill} worked\`, \`arp: ${skill} failed\` or \`arp: ${skill} unsure\`. ` + "Kept on this machine unless `arp share` is on." + (share ? shareQuestionFor() : "");
4670
4851
  }
4671
4852
  function requestOutput(context) {
4672
4853
  return JSON.stringify({ hookSpecificOutput: { hookEventName: "PostToolUse", additionalContext: context } });
4673
4854
  }
4674
4855
  var isArpsOwn = (name) => name === "arp" || name.endsWith(":arp");
4675
4856
  function rows(path) {
4676
- if (!existsSync2(path))
4857
+ if (!existsSync3(path))
4677
4858
  return [];
4678
4859
  const found = [];
4679
- for (const line of readFileSync3(path, "utf8").split(`
4860
+ for (const line of readFileSync4(path, "utf8").split(`
4680
4861
  `)) {
4681
4862
  if (line.trim() === "")
4682
4863
  continue;
@@ -4692,7 +4873,7 @@ function withOf(skill, source, cwd, before) {
4692
4873
  const name = skill.split(":").pop() ?? skill;
4693
4874
  let need = null;
4694
4875
  let latest = "";
4695
- for (const row of rows(join4(arpHome(), "failures.jsonl"))) {
4876
+ for (const row of rows(join5(arpHome(), "failures.jsonl"))) {
4696
4877
  const { skill: rowSkill, source: rowSource, need: rowNeed, at } = row;
4697
4878
  if (typeof rowSkill !== "string" || typeof rowNeed !== "string" || typeof at !== "string")
4698
4879
  continue;
@@ -4710,11 +4891,11 @@ function withOf(skill, source, cwd, before) {
4710
4891
  const directory = skillDirectory(name, cwd);
4711
4892
  if (directory === null)
4712
4893
  return null;
4713
- return publishableNeed3(need, readFileSync3(join4(directory, "SKILL.md"), "utf8"));
4894
+ return publishableNeed3(need, readFileSync4(join5(directory, "SKILL.md"), "utf8"));
4714
4895
  }
4715
4896
  function record(input, asked, outcome, at) {
4716
4897
  const search = foundBySearch(input.cwd, input.session, asked.skill);
4717
- const words = input.transcript !== null && existsSync2(input.transcript) ? lastUserWords(readFileSync3(input.transcript, "utf8")) : "";
4898
+ const words = input.transcript !== null && existsSync3(input.transcript) ? lastUserWords(readFileSync4(input.transcript, "utf8")) : "";
4718
4899
  const domain = search?.domain ?? contextOf(words, asked.skill);
4719
4900
  const source = asked.source ?? search?.source ?? installedSource(asked.skill.split(":").pop() ?? asked.skill, input.cwd);
4720
4901
  const named = outcome === "worked" ? withOf(asked.skill, source, input.cwd, at) : null;
@@ -4727,22 +4908,22 @@ function record(input, asked, outcome, at) {
4727
4908
  outcome,
4728
4909
  ...named === null ? {} : { with: named }
4729
4910
  };
4730
- appendOwn(join4(arpHome(), "outcomes.jsonl"), `${JSON.stringify(line)}
4911
+ appendOwn(join5(arpHome(), "outcomes.jsonl"), `${JSON.stringify(line)}
4731
4912
  `);
4732
4913
  }
4733
- function askOnSkill(session, skill, source, now = new Date) {
4914
+ function askOnSkill(session, skill, source, cwd = process.cwd(), now = new Date) {
4734
4915
  if (isArpsOwn(skill))
4735
4916
  return null;
4736
4917
  try {
4737
4918
  const home = arpHome();
4738
- mkdirSync3(home, { recursive: true });
4739
- const askedPath = join4(home, "asked.jsonl");
4919
+ mkdirSync4(home, { recursive: true });
4920
+ const askedPath = join5(home, "asked.jsonl");
4740
4921
  if (rows(askedPath).some((row2) => row2["session"] === session && row2["skill"] === skill))
4741
4922
  return null;
4742
4923
  const row = { at: now.toISOString(), session, skill, source };
4743
4924
  appendOwn(askedPath, `${JSON.stringify(row)}
4744
4925
  `);
4745
- return requestFor(skill);
4926
+ return requestFor(skill, offerShare(session, skill, source, cwd, now));
4746
4927
  } catch {
4747
4928
  return null;
4748
4929
  }
@@ -4756,20 +4937,22 @@ async function stop(payload) {
4756
4937
  function onStop(input, now = new Date) {
4757
4938
  try {
4758
4939
  const home = arpHome();
4759
- const asked = rows(join4(home, "asked.jsonl")).flatMap((row) => row["session"] === input.session && typeof row["skill"] === "string" ? [{
4940
+ const asked = rows(join5(home, "asked.jsonl")).flatMap((row) => row["session"] === input.session && typeof row["skill"] === "string" ? [{
4760
4941
  at: typeof row["at"] === "string" ? row["at"] : "",
4761
4942
  session: input.session,
4762
4943
  skill: row["skill"],
4763
4944
  source: typeof row["source"] === "string" ? row["source"] : null
4764
4945
  }] : []);
4765
- const answered = new Set(rows(join4(home, "outcomes.jsonl")).flatMap((row) => row["session"] === input.session && typeof row["skill"] === "string" ? [row["skill"]] : []));
4946
+ const answered = new Set(rows(join5(home, "outcomes.jsonl")).flatMap((row) => row["session"] === input.session && typeof row["skill"] === "string" ? [row["skill"]] : []));
4766
4947
  const pending = asked.filter((row) => !answered.has(row.skill));
4767
4948
  const at = now.toISOString();
4768
- for (const [skill, answer] of answersIn2(input.last, pending.map((row) => row.skill))) {
4949
+ const answers = answersIn2(input.last, pending.map((row) => row.skill));
4950
+ for (const [skill, answer] of answers) {
4769
4951
  const row = pending.find((entry) => entry.skill === skill);
4770
4952
  if (row !== undefined)
4771
4953
  record(input, row, answer, at);
4772
4954
  }
4955
+ onStopShare(input.session, input.last, answers, now);
4773
4956
  } catch {}
4774
4957
  }
4775
4958
 
@@ -4777,7 +4960,8 @@ function onStop(input, now = new Date) {
4777
4960
  var ROUTES = {
4778
4961
  PreToolUse: (payload) => preToolUse(payload),
4779
4962
  PostToolUseFailure: (payload) => postToolUseFailure(payload),
4780
- Stop: (payload) => stop(payload)
4963
+ Stop: (payload) => stop(payload),
4964
+ SessionEnd: (payload) => sessionEnd(payload)
4781
4965
  };
4782
4966
  async function route(handler, payload) {
4783
4967
  let out = null;
@@ -4797,12 +4981,12 @@ async function route(handler, payload) {
4797
4981
  });
4798
4982
  }
4799
4983
  function fetchedRefs(cwd) {
4800
- const path = process.env["ARP_RECEIPTS"] ?? join5(cwd, ".arp", "receipts.jsonl");
4801
- if (!existsSync3(path))
4984
+ const path = process.env["ARP_RECEIPTS"] ?? join6(cwd, ".arp", "receipts.jsonl");
4985
+ if (!existsSync4(path))
4802
4986
  return [];
4803
4987
  let text2;
4804
4988
  try {
4805
- text2 = readFileSync4(path, "utf8");
4989
+ text2 = readFileSync5(path, "utf8");
4806
4990
  } catch {
4807
4991
  return [];
4808
4992
  }
@@ -4882,7 +5066,7 @@ try {
4882
5066
  process.exit(0);
4883
5067
  }
4884
5068
  if (process.argv.includes("--capture"))
4885
- capture(parsed2, join5(homedir3(), ".arp", "captures"));
5069
+ capture(parsed2, join6(homedir3(), ".arp", "captures"));
4886
5070
  var event = eventOf(parsed2);
4887
5071
  var handler = event === null ? undefined : ROUTES[event];
4888
5072
  if (handler !== undefined)
@@ -4903,8 +5087,8 @@ if (what === null)
4903
5087
  var classified = classifyCall(call);
4904
5088
  if (classified === null)
4905
5089
  process.exit(0);
4906
- var own = join5(cwd, ".arp");
4907
- var path = process.env["ARP_SESSION_LOG"] ?? join5(own, "session.jsonl");
5090
+ var own = join6(cwd, ".arp");
5091
+ var path = process.env["ARP_SESSION_LOG"] ?? join6(own, "session.jsonl");
4908
5092
  var line = {
4909
5093
  at: new Date().toISOString(),
4910
5094
  session: call.session,
@@ -4917,18 +5101,18 @@ var line = {
4917
5101
  response: responseOf(call, classified.outcome)
4918
5102
  };
4919
5103
  try {
4920
- mkdirSync4(dirname3(path), { recursive: true });
4921
- if (dirname3(path) === own && lstatSync2(own).isSymbolicLink())
5104
+ mkdirSync5(dirname4(path), { recursive: true });
5105
+ if (dirname4(path) === own && lstatSync2(own).isSymbolicLink())
4922
5106
  throw new Error(`${own} is a symbolic link`);
4923
- const ignore = join5(own, ".gitignore");
4924
- if (dirname3(path) === own && !existsSync3(ignore))
4925
- writeFileSync2(ignore, `*
5107
+ const ignore = join6(own, ".gitignore");
5108
+ if (dirname4(path) === own && !existsSync4(ignore))
5109
+ writeFileSync3(ignore, `*
4926
5110
  `, { flag: "wx" });
4927
5111
  appendOwn(path, `${JSON.stringify(line)}
4928
5112
  `);
4929
5113
  } catch {}
4930
5114
  if (what.kind === "skill" && call.event === "PostToolUse" && classified.outcome === "ok" && call.session !== null) {
4931
- const request = askOnSkill(call.session, what.name, what.source);
5115
+ const request = askOnSkill(call.session, what.name, what.source, cwd);
4932
5116
  if (request !== null)
4933
5117
  process.stdout.write(`${requestOutput(request)}
4934
5118
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arp-protocol/cli",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "mcpName": "io.github.Wieedze/arp",
5
5
  "description": "ARP's agent-side programs in one binary: the MCP server, the runtime hook that records what an agent used, and the opt-in hire node. Runs on Node 22 or later.",
6
6
  "homepage": "https://github.com/intuition-box/ARP",
@@ -1,106 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- decideShare,
4
- installedFromReceipts,
5
- installedFromSkillLock,
6
- isSharingOn,
7
- parseLocalFailures,
8
- parseLocalOutcomes,
9
- sharedLineKey
10
- } from "./cli-wer26exb.js";
11
- import"./cli-zehr6kdx.js";
12
- import"./cli-0gsbnq5w.js";
13
- import"./cli-fbwpcfvp.js";
14
- import"./cli-mhzh9z8k.js";
15
- import"./cli-d3dy7cxe.js";
16
- import"./cli-3kbbwfbp.js";
17
- import"./cli-g5zvr10v.js";
18
-
19
- // src/share.ts
20
- import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
21
- import { join } from "node:path";
22
- var read = (path) => existsSync(path) ? readFileSync(path, "utf8") : null;
23
- function shared(env) {
24
- const keys = new Set;
25
- for (const row of (read(join(env.arpHome, "shared-failures.jsonl")) ?? "").split(`
26
- `)) {
27
- if (row.trim() === "")
28
- continue;
29
- try {
30
- keys.add(sharedLineKey(JSON.parse(row)));
31
- } catch {
32
- continue;
33
- }
34
- }
35
- return keys;
36
- }
37
- function decide(env) {
38
- const on = isSharingOn(read(join(env.arpHome, "sharing.json")));
39
- const decision = decideShare({
40
- on,
41
- failures: parseLocalFailures(read(join(env.arpHome, "failures.jsonl")) ?? ""),
42
- outcomes: parseLocalOutcomes(read(join(env.arpHome, "outcomes.jsonl")) ?? ""),
43
- installed: [
44
- ...installedFromSkillLock(read(join(env.home, ".agents", ".skill-lock.json"))),
45
- ...installedFromSkillLock(read(join(env.cwd, "skills-lock.json"))),
46
- ...installedFromReceipts(read(join(env.arpHome, "receipts.jsonl"))),
47
- ...installedFromReceipts(read(join(env.cwd, ".arp", "receipts.jsonl")))
48
- ],
49
- alreadyShared: shared(env)
50
- });
51
- return { ...decision, on };
52
- }
53
- function setSharing(env, on) {
54
- mkdirSync(env.arpHome, { recursive: true });
55
- writeFileSync(join(env.arpHome, "sharing.json"), `${JSON.stringify({ failures: on })}
56
- `);
57
- }
58
- async function sendShared(env, write) {
59
- const { on, lines } = decide(env);
60
- if (!on)
61
- return { sent: 0, failed: 0, firstError: null };
62
- let sent = 0;
63
- let failed = 0;
64
- let firstError = null;
65
- for (const line of lines) {
66
- try {
67
- await write(line);
68
- appendFileSync(join(env.arpHome, "shared-failures.jsonl"), `${JSON.stringify(line)}
69
- `);
70
- sent += 1;
71
- } catch (error) {
72
- failed += 1;
73
- firstError ??= error instanceof Error ? error.message : String(error);
74
- }
75
- }
76
- return { sent, failed, firstError };
77
- }
78
- var WHY = {
79
- sharing_off: "sharing is off",
80
- no_public_source: "no known public source",
81
- several_sources: "several sources with this name"
82
- };
83
- function describe(decision) {
84
- const out = [`sharing is ${decision.on ? "on" : "off"}`];
85
- if (decision.lines.length > 0) {
86
- out.push("", "would be public on testnet 13579:");
87
- for (const line of decision.lines) {
88
- const tail = line.outcome === "failed" ? `failed ${line.reason}${line.need === undefined ? "" : ` needs ${line.need}`}` : `worked${line.need === undefined ? "" : ` with ${line.need}`}`;
89
- out.push(` ${line.toolUrl} ${line.domain ?? "(no domain)"} ${tail}`);
90
- }
91
- }
92
- if (decision.keptLocal.length > 0) {
93
- out.push("", "stays on this machine:");
94
- for (const row of decision.keptLocal)
95
- out.push(` ${row.skill}: ${WHY[row.why]}`);
96
- }
97
- return `${out.join(`
98
- `)}
99
- `;
100
- }
101
- export {
102
- setSharing,
103
- sendShared,
104
- describe,
105
- decide
106
- };