@heyanon-arp/cli 0.0.21 → 0.0.23

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/dist/cli.js CHANGED
@@ -729,12 +729,11 @@ var init_api = __esm({
729
729
  // src/cli.ts
730
730
  var import_shield3 = require("@heyanon-arp/shield");
731
731
  var import_commander = require("commander");
732
- var import_simple_update_notifier = __toESM(require("simple-update-notifier"));
733
732
 
734
733
  // package.json
735
734
  var package_default = {
736
735
  name: "@heyanon-arp/cli",
737
- version: "0.0.21",
736
+ version: "0.0.23",
738
737
  description: "Command-line client for the Agent Relationship Protocol \u2014 register agents, sign envelopes, run escrowed work cycles on Solana.",
739
738
  license: "MIT",
740
739
  keywords: ["arp", "agent-relationship-protocol", "did", "solana", "escrow", "ed25519", "agents", "a2a", "cli"],
@@ -763,8 +762,7 @@ var package_default = {
763
762
  "@solana/web3.js": "^1.98.4",
764
763
  chalk: "^4.1.2",
765
764
  commander: "^12.1.0",
766
- prompts: "^2.4.2",
767
- "simple-update-notifier": "^2.0.0"
765
+ prompts: "^2.4.2"
768
766
  },
769
767
  devDependencies: {
770
768
  "@types/jest": "^29.5.2",
@@ -1790,6 +1788,10 @@ var UNTIL_PHASES = [
1790
1788
  ];
1791
1789
  var WAIT_DEFAULT_INTERVAL_SEC = 3;
1792
1790
  var WAIT_DEFAULT_TIMEOUT_SEC = 300;
1791
+ var WAIT_ABS_MAX_SEC = 7 * 86400;
1792
+ var WAIT_CONTRACT_LAG_MARGIN_SEC = 300;
1793
+ var WAIT_CONTRACT_CAP_FALLBACK_SEC = 3600;
1794
+ var WAIT_LOCK_FINALIZATION_DEFAULT_SEC = 1200;
1793
1795
  function registerStatusCommand(root) {
1794
1796
  root.command("status").description("Where am I in the work cycle? FSM state + next-action hint for ONE relationship (signed reads)").argument("<relationship-id>", "Relationship UUID").option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Signer DID \u2014 required only if multiple agents are registered against this server").option("--from <name>", "Signer agent NAME (handle) \u2014 alternative to --from-did, resolved against your local agents").option("--json", "Machine-readable: single JSON object with the composed summary. Pipe-safe.", false).option(
1795
1797
  "--wait",
@@ -1797,8 +1799,10 @@ function registerStatusCommand(root) {
1797
1799
  false
1798
1800
  ).option(
1799
1801
  "--wait-timeout <seconds>",
1800
- `Max wall-clock seconds to wait when --wait is set (default ${WAIT_DEFAULT_TIMEOUT_SEC}). Process exits code 124 on timeout, matching the unix \`timeout\` convention.`,
1801
- String(WAIT_DEFAULT_TIMEOUT_SEC)
1802
+ `Max wall-clock seconds to wait when --wait is set. Default ${WAIT_DEFAULT_TIMEOUT_SEC}s, with two phase-aware exceptions when --until is set: (1) delegation.locked uses a generous lock-finalization default (on-chain confirmation + indexer projection latency, not the lifecycle windows); (2) a lifecycle phase (work/review/dispute-governed, e.g. work.responded) auto-sizes to the live on-chain budget (work+review+dispute windows + margin, read from the contract) so it tracks the windows. An explicit value is always honored as-is (it may legitimately exceed the budget \u2014 a wait can span pre-lock acceptance/funding too); only a generous 7-day wait-loop safety cap is enforced. Exit code 124 on timeout (unix \`timeout\` convention).`
1803
+ // NB: no Commander default — an omitted value must reach resolveWaitTimeoutSec
1804
+ // as `undefined` so it can phase-size the default (a literal '300' here would
1805
+ // mask the contract-budget auto-sizing for --until waits).
1802
1806
  ).option(
1803
1807
  "--wait-interval <seconds>",
1804
1808
  `Seconds between polls when --wait is set (default ${WAIT_DEFAULT_INTERVAL_SEC}). Bound to [1, 60].`,
@@ -1833,9 +1837,9 @@ async function runStatus(relationshipId, opts) {
1833
1837
  console.log(formatStatusReport(summary));
1834
1838
  return;
1835
1839
  }
1836
- const waitTimeout = parseWaitTimeout(opts.waitTimeout);
1837
- const waitInterval = parseWaitInterval(opts.waitInterval);
1838
1840
  const until = parseUntilPhase(opts.until);
1841
+ const waitInterval = parseWaitInterval(opts.waitInterval);
1842
+ const waitTimeout = await resolveWaitTimeoutSec(api, opts.waitTimeout, { untilPhase: until });
1839
1843
  const outcome = await runWaitLoop({
1840
1844
  fetchSummary: () => composeStatus(api, sender.did, relationshipId, signer),
1841
1845
  waitIntervalSec: waitInterval,
@@ -1996,11 +2000,38 @@ function parseWaitTimeout(raw) {
1996
2000
  if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {
1997
2001
  throw new Error(`status: --wait-timeout must be a positive integer number of seconds (got '${raw}')`);
1998
2002
  }
1999
- if (n > 3600) {
2000
- throw new Error(`status: --wait-timeout must be <= 3600 seconds (1h). Got ${n}; if you really need to wait longer, script a loop around \`heyarp status\`.`);
2003
+ if (n > WAIT_ABS_MAX_SEC) {
2004
+ throw new Error(`status: --wait-timeout must be <= ${WAIT_ABS_MAX_SEC} seconds (the 7-day wait-loop safety cap). Got ${n}.`);
2001
2005
  }
2002
2006
  return n;
2003
2007
  }
2008
+ function contractWaitCapSec(cfg) {
2009
+ const w = Number(cfg?.workWindowSecs);
2010
+ const r = Number(cfg?.reviewWindowSecs);
2011
+ const d = Number(cfg?.disputeWindowSecs);
2012
+ if (![w, r, d].every((n) => Number.isFinite(n) && n > 0)) return WAIT_CONTRACT_CAP_FALLBACK_SEC;
2013
+ return Math.min(WAIT_ABS_MAX_SEC, w + r + d + WAIT_CONTRACT_LAG_MARGIN_SEC);
2014
+ }
2015
+ var LOCK_FINALIZATION_PHASES = /* @__PURE__ */ new Set(["delegation.locked"]);
2016
+ var CONTRACT_BUDGETED_PHASES = /* @__PURE__ */ new Set([
2017
+ "delegation.disputing",
2018
+ "work.requested",
2019
+ "work.responded",
2020
+ "receipt.proposed",
2021
+ "cycle.released",
2022
+ "cycle.complete"
2023
+ ]);
2024
+ async function resolveWaitTimeoutSec(api, raw, opts) {
2025
+ if (raw !== void 0) return parseWaitTimeout(raw);
2026
+ if (opts.untilPhase !== void 0 && LOCK_FINALIZATION_PHASES.has(opts.untilPhase)) return WAIT_LOCK_FINALIZATION_DEFAULT_SEC;
2027
+ const contractBudgeted = opts.untilPhase !== void 0 && CONTRACT_BUDGETED_PHASES.has(opts.untilPhase);
2028
+ if (!contractBudgeted) return WAIT_DEFAULT_TIMEOUT_SEC;
2029
+ try {
2030
+ return contractWaitCapSec(await api.getEscrowConfig());
2031
+ } catch {
2032
+ return WAIT_CONTRACT_CAP_FALLBACK_SEC;
2033
+ }
2034
+ }
2004
2035
  function parseUntilPhase(raw) {
2005
2036
  if (raw === void 0 || raw === "") return void 0;
2006
2037
  if (!UNTIL_PHASES.includes(raw)) {
@@ -2318,9 +2349,8 @@ function nextAction(input) {
2318
2349
  }
2319
2350
  const iAmCaller = latestReceipt.callerDid === signerDid;
2320
2351
  return {
2321
- // There is no cosign step buyer consent is the on-chain
2322
- // claim_work_payment tx, worker recourse is the
2323
- // review-window self-claim.
2352
+ // Consent is the on-chain claim_work_payment tx (the buyer's), and
2353
+ // worker recourse is the review-window self-claim.
2324
2354
  hint: iAmCaller ? `Receipt PROPOSED \u2014 you (caller): review the deliverable and approve payment ON-CHAIN with \`heyarp escrow claim ${latestReceipt.delegationId}\` (irreversible release; \`heyarp escrow dispute open ${latestReceipt.delegationId}\` within the review window if the work is unacceptable)` : `Receipt PROPOSED \u2014 counterparty (the buyer) owes the on-chain approval (\`heyarp escrow claim\`); once the review window lapses you may self-claim with \`heyarp escrow claim ${latestReceipt.delegationId}\``,
2325
2355
  owner: iAmCaller ? "me" : "counterparty",
2326
2356
  complete: false
@@ -2959,7 +2989,10 @@ function registerOffer(parent) {
2959
2989
  ).option(
2960
2990
  "--wait-until <phase>",
2961
2991
  'Block after delivery until the named FSM phase is reached (e.g. delegation.accepted). One of the UNTIL_PHASES from `heyarp status --help`. Exit code 124 on --wait-timeout. Recovers the "sub-agent exits before counterparty accepts" antipattern.'
2962
- ).option("--wait-timeout <seconds>", "When --wait-until is set: max wall-clock wait (default 300). Exit code 124 on timeout.").option("--wait-interval <seconds>", "When --wait-until is set: poll cadence (default 3, bound [1, 60]).").option(
2992
+ ).option(
2993
+ "--wait-timeout <seconds>",
2994
+ "When --wait-until is set: max wall-clock wait. Omitted \u2192 delegation.locked uses a generous lock-finalization default (on-chain confirmation + indexer projection); later lifecycle phases (work/review/dispute-governed) auto-size to the live on-chain budget (read from the contract); other phases default to 300s. An explicit value is always honored as-is (a wait may span pre-lock acceptance/funding, so it can exceed the budget), bounded only by a generous 7-day wait-loop safety cap. Exit code 124 on timeout."
2995
+ ).option("--wait-interval <seconds>", "When --wait-until is set: poll cadence (default 3, bound [1, 60]).").option(
2963
2996
  "--wait-verbose",
2964
2997
  'When --wait-until is set: emit one dim line per poll tick showing the current FSM state. Useful for "is it alive or stuck?" diagnosis on long blocks.',
2965
2998
  false
@@ -3184,7 +3217,8 @@ After the worker accepts, fund the escrow lock:`));
3184
3217
  relationshipId: result.relationshipId,
3185
3218
  untilPhase,
3186
3219
  waitIntervalSec: parseWaitInterval(opts.waitInterval),
3187
- waitTimeoutSec: parseWaitTimeout(opts.waitTimeout),
3220
+ // Cap/default the wait against the live on-chain windows (work+review+dispute).
3221
+ waitTimeoutSec: await resolveWaitTimeoutSec(api, opts.waitTimeout, { untilPhase }),
3188
3222
  waitVerbose: !!opts.waitVerbose,
3189
3223
  json: false
3190
3224
  // delegation offer is a human-text command (printIngestResult is human-text); JSON mode would be a follow-up.
@@ -3208,7 +3242,10 @@ function registerFund(parent) {
3208
3242
  ).option(
3209
3243
  "--wait-until <phase>",
3210
3244
  "Block after delivery until the named FSM phase is reached (typically delegation.locked \u2014 resolves once the escrow lock confirms on chain). One of the UNTIL_PHASES from `heyarp status --help`. Exit code 124 on --wait-timeout."
3211
- ).option("--wait-timeout <seconds>", "When --wait-until is set: max wall-clock wait (default 300). Exit code 124 on timeout.").option("--wait-interval <seconds>", "When --wait-until is set: poll cadence (default 3, bound [1, 60]).").option("--wait-verbose", "When --wait-until is set: emit one dim line per poll tick showing the current FSM state.", false).action(async (delegationId, opts) => {
3245
+ ).option(
3246
+ "--wait-timeout <seconds>",
3247
+ "When --wait-until is set: max wall-clock wait. Omitted \u2192 delegation.locked uses a generous lock-finalization default (on-chain confirmation + indexer projection); later lifecycle phases (work/review/dispute-governed) auto-size to the live on-chain budget (read from the contract); other phases default to 300s. An explicit value is always honored as-is (a wait may span pre-lock acceptance/funding, so it can exceed the budget), bounded only by a generous 7-day wait-loop safety cap. Exit code 124 on timeout."
3248
+ ).option("--wait-interval <seconds>", "When --wait-until is set: poll cadence (default 3, bound [1, 60]).").option("--wait-verbose", "When --wait-until is set: emit one dim line per poll tick showing the current FSM state.", false).action(async (delegationId, opts) => {
3212
3249
  await runFund(delegationId, opts);
3213
3250
  });
3214
3251
  }
@@ -3306,11 +3343,11 @@ async function runFund(delegationId, opts) {
3306
3343
  relationshipId: result.relationshipId,
3307
3344
  untilPhase,
3308
3345
  waitIntervalSec: parseWaitInterval(opts.waitInterval),
3309
- // Lock finalization on devnet routinely takes 5–12 min
3310
- // (submit confirm indexer projection), so fund gets a
3311
- // larger default than the generic 300s a timeout here is
3312
- // almost never a failure, just a slow chain.
3313
- waitTimeoutSec: parseWaitTimeout(opts.waitTimeout ?? "1200"),
3346
+ // Lock finalization (submit confirm indexer projection) is
3347
+ // slow, so an omitted timeout auto-sizes to the live on-chain
3348
+ // budget (work+review+dispute + margin)generous by design;
3349
+ // a timeout here is almost never a failure, just a slow chain.
3350
+ waitTimeoutSec: await resolveWaitTimeoutSec(api, opts.waitTimeout, { untilPhase }),
3314
3351
  waitVerbose: !!opts.waitVerbose,
3315
3352
  json: false
3316
3353
  });
@@ -6373,8 +6410,8 @@ async function runPropose(recipientDid, delegationId, requestHashArg, responseHa
6373
6410
  );
6374
6411
  }
6375
6412
  recipientDid = await resolveRecipient(opts.server, "receipt propose", recipientDid, { json: opts.json });
6376
- delegationId = requireUuidNormalised2("receipt propose", delegationId, "<delegation-id>");
6377
- if (opts.relId) opts.relId = requireUuidNormalised2("receipt propose", opts.relId, "--rel-id");
6413
+ delegationId = requireUuidNormalised("receipt propose", delegationId, "<delegation-id>");
6414
+ if (opts.relId) opts.relId = requireUuidNormalised("receipt propose", opts.relId, "--rel-id");
6378
6415
  const verdict = parseVerdict("receipt propose", opts.verdict);
6379
6416
  const ttlSeconds = parseTtl2("receipt propose", opts.ttl);
6380
6417
  if (opts.notesHash) requireSha256("receipt propose", opts.notesHash, "--notes-hash");
@@ -6635,10 +6672,6 @@ function parseInteger(cmdName, flag, raw) {
6635
6672
  }
6636
6673
  return n;
6637
6674
  }
6638
- function requireUuidNormalised2(cmdName, raw, label) {
6639
- requireUuid3(cmdName, raw, label);
6640
- return raw.toLowerCase();
6641
- }
6642
6675
  function requireUuid3(cmdName, raw, label) {
6643
6676
  requireUuid(cmdName, raw, label);
6644
6677
  }
@@ -7874,7 +7907,7 @@ async function runRequest(recipientDid, delegationId, opts) {
7874
7907
  );
7875
7908
  }
7876
7909
  recipientDid = await resolveRecipient(opts.server, "work request", recipientDid, { json: opts.json });
7877
- delegationId = requireUuidNormalised3("work request", delegationId, "<delegation-id>");
7910
+ delegationId = requireUuidNormalised("work request", delegationId, "<delegation-id>");
7878
7911
  const ttlSeconds = parseTtl5("work request", opts.ttl);
7879
7912
  const params = parseParamsInput("work request", opts);
7880
7913
  const requestId = parseRequestId("work request", opts.requestId);
@@ -7931,8 +7964,8 @@ async function runRespond(relationshipId, delegationId, requestId, opts) {
7931
7964
  "work respond: --verbose and --json are mutually exclusive. --json emits the structured server response; --verbose adds dumps that would break `--json | jq`."
7932
7965
  );
7933
7966
  }
7934
- relationshipId = requireUuidNormalised3("work respond", relationshipId, "<relationship-id>");
7935
- delegationId = requireUuidNormalised3("work respond", delegationId, "<delegation-id>");
7967
+ relationshipId = requireUuidNormalised("work respond", relationshipId, "<relationship-id>");
7968
+ delegationId = requireUuidNormalised("work respond", delegationId, "<delegation-id>");
7936
7969
  const ttlSeconds = parseTtl5("work respond", opts.ttl);
7937
7970
  const responsePayload = parseResponsePayload("work respond", opts);
7938
7971
  const api = new ArpApiClient(opts.server);
@@ -8075,13 +8108,13 @@ function parseParamsInput(cmdName, opts) {
8075
8108
  return parseJsonObject(cmdName, "--params", opts.params ?? "{}");
8076
8109
  }
8077
8110
  function readJsonObjectFile(cmdName, flagName, path) {
8078
- const { existsSync: existsSync7, readFileSync: readFileSync9 } = require("fs");
8111
+ const { existsSync: existsSync7, readFileSync: readFileSync10 } = require("fs");
8079
8112
  if (!existsSync7(path)) {
8080
8113
  throw new Error(`${cmdName}: ${flagName} file not found at ${path}`);
8081
8114
  }
8082
8115
  let raw;
8083
8116
  try {
8084
- raw = readFileSync9(path, "utf8");
8117
+ raw = readFileSync10(path, "utf8");
8085
8118
  } catch (err) {
8086
8119
  const detail = err instanceof Error ? err.message : String(err);
8087
8120
  throw new Error(`${cmdName}: failed to read ${flagName} (${path}): ${detail}`);
@@ -8136,13 +8169,6 @@ function parseRequestId(cmdName, raw) {
8136
8169
  }
8137
8170
  return raw;
8138
8171
  }
8139
- function requireUuidNormalised3(cmdName, raw, label) {
8140
- requireUuid4(cmdName, raw, label);
8141
- return raw.toLowerCase();
8142
- }
8143
- function requireUuid4(cmdName, raw, label) {
8144
- requireUuid(cmdName, raw, label);
8145
- }
8146
8172
 
8147
8173
  // src/commands/work-list.ts
8148
8174
  var import_sdk32 = require("@heyanon-arp/sdk");
@@ -8301,19 +8327,104 @@ function unknownOptionHint(program, err) {
8301
8327
  }
8302
8328
  }
8303
8329
 
8304
- // src/cli.ts
8305
- async function checkForUpdates() {
8306
- if (package_default.private === true) return;
8330
+ // src/version-gate.ts
8331
+ var import_node_fs10 = require("fs");
8332
+ var import_node_path7 = require("path");
8333
+ init_paths();
8334
+ var CHECK_INTERVAL_MS = 60 * 60 * 1e3;
8335
+ var REGISTRY_TIMEOUT_MS = 1500;
8336
+ var VERSION_GATE_BYPASS_ENV = "HEYARP_IGNORE_VERSION_GATE";
8337
+ var cacheFile = () => (0, import_node_path7.join)(arpHomeDir(), "update-check.json");
8338
+ function compareVersions(a, b) {
8339
+ const seg = (v, i) => Number.parseInt(v.split(".")[i] ?? "0", 10) || 0;
8340
+ for (let i = 0; i < 3; i++) {
8341
+ const d = seg(a, i) - seg(b, i);
8342
+ if (d !== 0) return d > 0 ? 1 : -1;
8343
+ }
8344
+ return 0;
8345
+ }
8346
+ function isClientBlocked(current, latest) {
8347
+ if (!latest) return false;
8348
+ return compareVersions(current, latest) < 0;
8349
+ }
8350
+ function isVersionGateExempt(argv) {
8351
+ if (argv.length === 0) return true;
8352
+ if (argv[0] === "help") return true;
8353
+ return argv.includes("--version") || argv.includes("-V") || argv.includes("--help") || argv.includes("-h");
8354
+ }
8355
+ async function fetchLatest(pkgName) {
8356
+ const ctrl = new AbortController();
8357
+ const timer = setTimeout(() => ctrl.abort(), REGISTRY_TIMEOUT_MS);
8307
8358
  try {
8308
- await (0, import_simple_update_notifier.default)({
8309
- pkg: { name: package_default.name, version: package_default.version },
8310
- updateCheckInterval: 1e3 * 60 * 60 * 24
8359
+ const res = await fetch(`https://registry.npmjs.org/-/package/${pkgName.replace("/", "%2F")}/dist-tags`, {
8360
+ signal: ctrl.signal,
8361
+ headers: { accept: "application/json" }
8311
8362
  });
8363
+ if (!res.ok) return null;
8364
+ const body = await res.json();
8365
+ return typeof body.latest === "string" ? body.latest : null;
8312
8366
  } catch {
8367
+ return null;
8368
+ } finally {
8369
+ clearTimeout(timer);
8313
8370
  }
8314
8371
  }
8372
+ async function resolveLatest(pkgName) {
8373
+ try {
8374
+ const cached = JSON.parse((0, import_node_fs10.readFileSync)(cacheFile(), "utf8"));
8375
+ if (typeof cached.latest === "string" && typeof cached.checkedAt === "number" && Date.now() - cached.checkedAt < CHECK_INTERVAL_MS) {
8376
+ return cached.latest;
8377
+ }
8378
+ } catch {
8379
+ }
8380
+ const fresh = await fetchLatest(pkgName);
8381
+ if (!fresh) return null;
8382
+ try {
8383
+ const file = cacheFile();
8384
+ (0, import_node_fs10.mkdirSync)((0, import_node_path7.dirname)(file), { recursive: true });
8385
+ (0, import_node_fs10.writeFileSync)(file, JSON.stringify({ checkedAt: Date.now(), latest: fresh }));
8386
+ } catch {
8387
+ }
8388
+ return fresh;
8389
+ }
8390
+ async function enforceMinClientVersion(pkg, argv) {
8391
+ if (process.env[VERSION_GATE_BYPASS_ENV]) return;
8392
+ if (isVersionGateExempt(argv)) return;
8393
+ let latest;
8394
+ try {
8395
+ latest = await resolveLatest(pkg.name);
8396
+ } catch {
8397
+ return;
8398
+ }
8399
+ if (!isClientBlocked(pkg.version, latest)) return;
8400
+ const update = `npm i -g ${pkg.name}@latest`;
8401
+ if (argv.includes("--json")) {
8402
+ console.error(
8403
+ JSON.stringify({
8404
+ code: "CLI_VERSION_TOO_OLD",
8405
+ message: `heyarp ${pkg.version} is out of date and blocked; the current release is ${latest}. Update with \`${update}\`, then re-run.`,
8406
+ details: { current: pkg.version, latest, update, bypassEnv: VERSION_GATE_BYPASS_ENV }
8407
+ })
8408
+ );
8409
+ } else {
8410
+ process.stderr.write(
8411
+ `
8412
+ heyarp ${pkg.version} is out of date and is blocked.
8413
+ The current release is ${latest}. Update, then re-run:
8414
+
8415
+ ${update}
8416
+
8417
+ (emergency bypass: set ${VERSION_GATE_BYPASS_ENV}=1)
8418
+
8419
+ `
8420
+ );
8421
+ }
8422
+ process.exit(1);
8423
+ }
8424
+
8425
+ // src/cli.ts
8315
8426
  async function main() {
8316
- void checkForUpdates();
8427
+ await enforceMinClientVersion(package_default, process.argv.slice(2));
8317
8428
  const program = new import_commander.Command();
8318
8429
  program.name("heyarp").description("ARP \u2014 Agent Relationship Protocol CLI (talks to apps/arp-server)").version(package_default.version).usage("[global-options] <command> [options]").option("--trace", "Surface stack traces and error details on failure.", false);
8319
8430
  program.exitOverride();