@heyanon-arp/cli 0.0.16 → 0.0.17

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
@@ -722,7 +722,7 @@ var import_simple_update_notifier = __toESM(require("simple-update-notifier"));
722
722
  // package.json
723
723
  var package_default = {
724
724
  name: "@heyanon-arp/cli",
725
- version: "0.0.16",
725
+ version: "0.0.17",
726
726
  description: "Command-line client for the Agent Relationship Protocol \u2014 register agents, sign envelopes, run escrowed work cycles on Solana.",
727
727
  license: "MIT",
728
728
  keywords: ["arp", "agent-relationship-protocol", "did", "solana", "escrow", "ed25519", "agents", "a2a", "cli"],
@@ -804,6 +804,14 @@ function onboardingHelpFooter() {
804
804
  `${import_chalk.default.dim("Then:")} ${import_chalk.default.cyan("heyarp register")} ${import_chalk.default.dim("\u2192 handshake \u2192 delegation \u2192 work \u2192 receipt \u2192 on-chain claim.")}`
805
805
  ].join("\n");
806
806
  }
807
+ function optionPlacementHelpNote() {
808
+ return [
809
+ "",
810
+ `${import_chalk.default.bold("Option placement:")} only ${import_chalk.default.cyan("--trace")}, ${import_chalk.default.cyan("--help")}, ${import_chalk.default.cyan("--version")} are global (before the command).`,
811
+ `Everything else \u2014 ${import_chalk.default.cyan("--json")}, ${import_chalk.default.cyan("--from-did")}, ${import_chalk.default.cyan("--verbose")}, ${import_chalk.default.cyan("--server")}, \u2026 \u2014 is a command option and goes AFTER the command:`,
812
+ ` ${import_chalk.default.cyan("heyarp <command> [options]")} ${import_chalk.default.dim("e.g. heyarp relationships --json --from-did did:arp:\u2026")}`
813
+ ].join("\n");
814
+ }
807
815
  function toCliErrorJson(err, includeStack = false) {
808
816
  const { ApiError: ApiError2 } = (init_api(), __toCommonJS(api_exports));
809
817
  if (err instanceof ApiError2) {
@@ -821,7 +829,9 @@ function toCliErrorJson(err, includeStack = false) {
821
829
  }
822
830
  function emitError(err, opts) {
823
831
  if (opts.json) {
824
- console.error(JSON.stringify(toCliErrorJson(err, opts.verbose)));
832
+ const obj = toCliErrorJson(err, opts.verbose);
833
+ if (opts.hint) obj.hint = opts.hint;
834
+ console.error(JSON.stringify(obj));
825
835
  return;
826
836
  }
827
837
  const { ApiError: ApiError2 } = (init_api(), __toCommonJS(api_exports));
@@ -830,6 +840,7 @@ function emitError(err, opts) {
830
840
  } else {
831
841
  console.error(formatGenericError(err, opts.verbose));
832
842
  }
843
+ if (opts.hint) console.error(`${import_chalk.default.yellow("hint")}: ${opts.hint}`);
833
844
  }
834
845
  function emitActionError(err, command) {
835
846
  let root = command;
@@ -1162,19 +1173,28 @@ function listAgents() {
1162
1173
  var import_chalk2 = __toESM(require("chalk"));
1163
1174
  init_api();
1164
1175
  function registerLifecycleCommands(root) {
1165
- root.command("update").description("Update an agent profile (name / description / tags). At least one flag is required.").argument("<did>").option("--server <url>", "Override ARP server base URL").option("--name <s>", "New display name").option("--description <s>", "New description").option("--tag <s>", "Capability tag \u2014 REPLACES the existing list. Repeatable: --tag translation --tag fr", accumulate, []).option("--clear-tags", "Drop all tags (cannot be combined with --tag)", false).action(
1176
+ root.command("update").description("Update an agent profile (name / description / tags). At least one flag is required.").argument("[did]", "Agent DID (did:arp:...) \u2014 optional when --from-did is given OR exactly one agent is registered for the resolved server.").option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Alias for the positional <did> \u2014 accepted for consistency with other signed commands. Must match the positional if both are given.").option("--name <s>", "New display name").option("--description <s>", "New description").option("--tag <s>", "Capability tag \u2014 REPLACES the existing list. Repeatable: --tag translation --tag fr", accumulate, []).option("--clear-tags", "Drop all tags (cannot be combined with --tag)", false).option("--json", "Machine-readable mode \u2014 emit the updated profile as a single JSON object on stdout (AgentPublic). Prelude moves to stderr. ", false).action(
1166
1177
  async (did, opts) => {
1178
+ if (did !== void 0 && opts.fromDid !== void 0 && did !== opts.fromDid) {
1179
+ throw new Error(`update: positional <did> (${did}) and --from-did (${opts.fromDid}) disagree \u2014 pass only one`);
1180
+ }
1181
+ const explicitDid = did ?? opts.fromDid;
1182
+ const targetDid = (explicitDid !== void 0 ? loadAgentOrThrow(opts.server, explicitDid) : resolveSenderAgent("update", opts.server, void 0)).did;
1167
1183
  const body = buildUpdateBody(opts);
1168
1184
  if (Object.keys(body).length === 0) {
1169
1185
  throw new Error("update: pass at least one of --name / --description / --tag / --clear-tags");
1170
1186
  }
1171
- const agent = await actSigned(did, opts.server, (api, signer) => api.updateAgent(did, body, signer));
1172
- updateAgentLocal(opts.server, did, {
1187
+ const agent = await actSigned(targetDid, opts.server, opts.json, (api, signer) => api.updateAgent(targetDid, body, signer));
1188
+ updateAgentLocal(opts.server, targetDid, {
1173
1189
  name: agent.name,
1174
1190
  description: agent.description,
1175
1191
  tags: agent.tags
1176
1192
  });
1177
- printAgent("Updated", agent);
1193
+ if (opts.json) {
1194
+ jsonOut(agent);
1195
+ } else {
1196
+ printAgent("Updated", agent);
1197
+ }
1178
1198
  }
1179
1199
  );
1180
1200
  }
@@ -1195,11 +1215,11 @@ function buildUpdateBody(opts) {
1195
1215
  }
1196
1216
  return body;
1197
1217
  }
1198
- async function actSigned(did, serverOverride, act) {
1218
+ async function actSigned(did, serverOverride, json, act) {
1199
1219
  const local = loadAgentOrThrow(serverOverride, did);
1200
1220
  const api = new ArpApiClient(serverOverride);
1201
- console.log(import_chalk2.default.dim(`Server: ${api.serverUrl}`));
1202
- console.log(import_chalk2.default.dim(`Signer: ${local.did}`));
1221
+ progress(json, import_chalk2.default.dim(`Server: ${api.serverUrl}`));
1222
+ progress(json, import_chalk2.default.dim(`Signer: ${local.did}`));
1203
1223
  const signer = makeSigner(local);
1204
1224
  return act(api, signer);
1205
1225
  }
@@ -1245,10 +1265,10 @@ function registerAgentsCommand(root) {
1245
1265
  }
1246
1266
  async function runAgents(opts) {
1247
1267
  const limit = parseLimit(opts.limit);
1248
- if (opts.sort && opts.sort !== "reputation" && opts.sort !== "recent" && opts.sort !== "created") {
1268
+ if (opts.sort && opts.sort !== import_sdk2.DiscoverySorts.REPUTATION && opts.sort !== import_sdk2.DiscoverySorts.RECENT && opts.sort !== import_sdk2.DiscoverySorts.CREATED) {
1249
1269
  throw new Error(`agents: --sort must be 'reputation', 'recent', or 'created' (got '${opts.sort}')`);
1250
1270
  }
1251
- const sort = opts.sort === "recent" ? "recent" : opts.sort === "created" ? "created" : "reputation";
1271
+ const sort = opts.sort === import_sdk2.DiscoverySorts.RECENT ? import_sdk2.DiscoverySorts.RECENT : opts.sort === import_sdk2.DiscoverySorts.CREATED ? import_sdk2.DiscoverySorts.CREATED : import_sdk2.DiscoverySorts.REPUTATION;
1252
1272
  const api = new ArpApiClient(opts.server);
1253
1273
  progress(opts.json, import_chalk3.default.dim(`Server: ${api.serverUrl}`));
1254
1274
  const query = { limit, sort };
@@ -1257,7 +1277,7 @@ async function runAgents(opts) {
1257
1277
  if (opts.accountId) query.accountId = opts.accountId;
1258
1278
  if (opts.accepts) query.accepts = resolveAcceptsAsset(opts.accepts);
1259
1279
  if (opts.online) query.online = true;
1260
- if (sort === "created") {
1280
+ if (sort === import_sdk2.DiscoverySorts.CREATED) {
1261
1281
  if (opts.after) query.after = opts.after;
1262
1282
  } else {
1263
1283
  query.page = parsePage(opts.page);
@@ -1296,10 +1316,10 @@ ${rule}`);
1296
1316
  `);
1297
1317
  }
1298
1318
  if (pagination.hasMore) {
1299
- if (sort === "created" && pagination.nextCursor) {
1319
+ if (sort === import_sdk2.DiscoverySorts.CREATED && pagination.nextCursor) {
1300
1320
  console.log(import_chalk3.default.dim(`
1301
1321
  Next page: re-run with --after ${pagination.nextCursor}`));
1302
- } else if (sort !== "created") {
1322
+ } else if (sort !== import_sdk2.DiscoverySorts.CREATED) {
1303
1323
  console.log(import_chalk3.default.dim(`
1304
1324
  Next page: re-run with --page ${parsePage(opts.page) + 1}`));
1305
1325
  }
@@ -1495,13 +1515,14 @@ function registerAssetsCommand(root) {
1495
1515
  }
1496
1516
 
1497
1517
  // src/commands/block.ts
1518
+ var import_sdk4 = require("@heyanon-arp/sdk");
1498
1519
  var import_chalk5 = __toESM(require("chalk"));
1499
1520
  init_api();
1500
1521
  function resolveSigningAgent(opts) {
1501
1522
  return opts.fromDid !== void 0 ? loadAgentOrThrow(opts.server, opts.fromDid) : resolveSenderAgent("block", opts.server, void 0);
1502
1523
  }
1503
1524
  function formatBlockRow(b) {
1504
- const scope = b.scope === "account" ? import_chalk5.default.yellow("account") : import_chalk5.default.cyan("did");
1525
+ const scope = b.scope === import_sdk4.InboxBlockScopes.ACCOUNT ? import_chalk5.default.yellow("account") : import_chalk5.default.cyan("did");
1505
1526
  const reason = b.reason ? import_chalk5.default.dim(` \u2014 ${b.reason}`) : "";
1506
1527
  return `${scope.padEnd(7)} ${b.did}${reason} ${import_chalk5.default.dim(b.blockedAt)}`;
1507
1528
  }
@@ -1526,7 +1547,7 @@ async function runAdd(did, opts) {
1526
1547
  jsonOut(result);
1527
1548
  return;
1528
1549
  }
1529
- const what = result.scope === "account" ? `the owner account of ${did} (all its agents)` : did;
1550
+ const what = result.scope === import_sdk4.InboxBlockScopes.ACCOUNT ? `the owner account of ${did} (all its agents)` : did;
1530
1551
  console.log(import_chalk5.default.green(`\u2713 Blocked ${what}.`));
1531
1552
  }
1532
1553
  async function runRemove(did, opts) {
@@ -1642,14 +1663,15 @@ function unknownKey(key) {
1642
1663
 
1643
1664
  // src/commands/delegation.ts
1644
1665
  var import_node_fs4 = require("fs");
1645
- var import_sdk7 = require("@heyanon-arp/sdk");
1666
+ var import_sdk10 = require("@heyanon-arp/sdk");
1646
1667
  var import_chalk8 = __toESM(require("chalk"));
1647
1668
  init_api();
1648
1669
 
1649
1670
  // src/id-format.ts
1671
+ var import_sdk5 = require("@heyanon-arp/sdk");
1650
1672
  var UUID_RE = /^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$/;
1651
1673
  var OBJECT_ID_24_HEX_RE = /^[0-9a-f]{24}$/;
1652
- var SHA256_PREFIX_RE = /^sha256:[0-9a-f]{64}$/;
1674
+ var SHA256_PREFIX_RE = import_sdk5.SHA256_HEX_RE;
1653
1675
  var UUID_NO_DASHES_RE = /^[a-fA-F0-9]{32}$/;
1654
1676
  function describeNonUuidShape(raw) {
1655
1677
  if (raw === "") return "empty string";
@@ -1681,7 +1703,7 @@ function requireUuid(cmdName, raw, label) {
1681
1703
  }
1682
1704
 
1683
1705
  // src/commands/status.ts
1684
- var import_sdk4 = require("@heyanon-arp/sdk");
1706
+ var import_sdk6 = require("@heyanon-arp/sdk");
1685
1707
  var import_chalk7 = __toESM(require("chalk"));
1686
1708
  init_api();
1687
1709
  var UNTIL_PHASES = [
@@ -1782,13 +1804,13 @@ async function awaitFsmTransitionAfterAction(input) {
1782
1804
  }
1783
1805
  }
1784
1806
  async function runWaitLoop(opts) {
1785
- const isTerminal = (s) => s.cycleComplete || s.relationshipState === "closed" || s.relationshipState === "not_found";
1807
+ const isTerminal = (s) => s.cycleComplete || s.relationshipState === import_sdk6.RelationshipStates.CLOSED || s.relationshipState === "not_found";
1786
1808
  const isActionable = (s) => {
1787
1809
  if (s.nextActionOwner === "me") {
1788
1810
  if (s.relationshipState === "unknown") return false;
1789
1811
  return true;
1790
1812
  }
1791
- if (s.nextActionOwner === "either" && s.relationshipState === "active") {
1813
+ if (s.nextActionOwner === "either" && s.relationshipState === import_sdk6.RelationshipStates.ACTIVE) {
1792
1814
  return true;
1793
1815
  }
1794
1816
  return false;
@@ -1924,41 +1946,41 @@ function parseUntilPhase(raw) {
1924
1946
  function isPhaseTerminallyUnreachable(phase, s) {
1925
1947
  if (untilPhaseMatched(phase, s)) return false;
1926
1948
  if (s.relationshipState === "not_found") return true;
1927
- if (s.relationshipState === "closed" && phase !== "relationship.closed") return true;
1949
+ if (s.relationshipState === import_sdk6.RelationshipStates.CLOSED && phase !== "relationship.closed") return true;
1928
1950
  return false;
1929
1951
  }
1930
1952
  function untilPhaseMatched(phase, s) {
1931
1953
  switch (phase) {
1932
1954
  case "delegation.offered":
1933
- return s.latestDelegation?.state === "offered";
1955
+ return s.latestDelegation?.state === import_sdk6.DelegationStates.OFFERED;
1934
1956
  case "delegation.accepted":
1935
- return s.latestDelegation?.state === "accepted";
1957
+ return s.latestDelegation?.state === import_sdk6.DelegationStates.ACCEPTED;
1936
1958
  case "delegation.locked":
1937
- return s.latestDelegation?.state === "locked";
1959
+ return s.latestDelegation?.state === import_sdk6.DelegationStates.LOCKED;
1938
1960
  case "delegation.disputing":
1939
- return s.latestDelegation?.state === "disputing";
1961
+ return s.latestDelegation?.state === import_sdk6.DelegationStates.DISPUTING;
1940
1962
  case "delegation.canceled":
1941
- return s.latestDelegation?.state === "canceled";
1963
+ return s.latestDelegation?.state === import_sdk6.DelegationStates.CANCELED;
1942
1964
  case "delegation.declined":
1943
- return s.latestDelegation?.state === "declined";
1965
+ return s.latestDelegation?.state === import_sdk6.DelegationStates.DECLINED;
1944
1966
  case "work.requested":
1945
- return s.latestWorkLog?.state === "requested";
1967
+ return s.latestWorkLog?.state === import_sdk6.WorkLogStates.REQUESTED;
1946
1968
  case "work.responded":
1947
- return s.latestWorkLog?.state === "responded";
1969
+ return s.latestWorkLog?.state === import_sdk6.WorkLogStates.RESPONDED;
1948
1970
  case "receipt.proposed":
1949
1971
  return s.latestReceipt != null;
1950
1972
  case "relationship.pending":
1951
- return s.relationshipState === "pending";
1973
+ return s.relationshipState === import_sdk6.RelationshipStates.PENDING;
1952
1974
  case "relationship.active":
1953
- return s.relationshipState === "active";
1975
+ return s.relationshipState === import_sdk6.RelationshipStates.ACTIVE;
1954
1976
  case "relationship.paused":
1955
- return s.relationshipState === "paused";
1977
+ return s.relationshipState === import_sdk6.RelationshipStates.PAUSED;
1956
1978
  case "relationship.closed":
1957
- return s.relationshipState === "closed";
1979
+ return s.relationshipState === import_sdk6.RelationshipStates.CLOSED;
1958
1980
  case "cycle.complete":
1959
1981
  return s.cycleComplete;
1960
1982
  case "cycle.released":
1961
- return s.latestDelegation?.state === "completed";
1983
+ return s.latestDelegation?.state === import_sdk6.DelegationStates.COMPLETED;
1962
1984
  }
1963
1985
  }
1964
1986
  function parseWaitInterval(raw) {
@@ -2009,7 +2031,7 @@ async function composeStatus(api, signerDid, relationshipId, signer) {
2009
2031
  const relationship = relationships.find((r) => r.relationshipId === relationshipId);
2010
2032
  const counterpartyDid = relationship ? relationship.pairDidA === signerDid ? relationship.pairDidB : relationship.pairDidA : inferCounterpartyFromEntities(signerDid, allDelegations);
2011
2033
  const delegations = allDelegations;
2012
- const latestDelegation = pickLatestLive(delegations, ["offered", "accepted", "pending_lock_finalization", "locked"]);
2034
+ const latestDelegation = pickLatestLive(delegations, [...import_sdk6.DELEGATION_ACTIVE_STATES]);
2013
2035
  const [workLogs, receipts] = await Promise.all([
2014
2036
  latestDelegation ? fetchAllPages(
2015
2037
  (after) => api.listWorkLogs(relationshipId, signer, { limit: 100, delegationId: latestDelegation.delegationId, ...after ? { after } : {} })
@@ -2073,8 +2095,8 @@ function findReceiptForWorkLog(receipts, workLog, allWorkLogs = [workLog]) {
2073
2095
  };
2074
2096
  const responseBody = workLog.responseOutput !== void 0 ? { type: "work_response", content: { delegation_id: workLog.delegationId, request_id: workLog.requestId, output: workLog.responseOutput } } : workLog.responseError !== void 0 ? { type: "work_response", content: { delegation_id: workLog.delegationId, request_id: workLog.requestId, error: workLog.responseError } } : null;
2075
2097
  if (!responseBody) return null;
2076
- const expectedRequestHash = (0, import_sdk4.canonicalSha256Hex)(requestBody);
2077
- const expectedResponseHash = (0, import_sdk4.canonicalSha256Hex)(responseBody);
2098
+ const expectedRequestHash = (0, import_sdk6.canonicalSha256Hex)(requestBody);
2099
+ const expectedResponseHash = (0, import_sdk6.canonicalSha256Hex)(responseBody);
2078
2100
  const matches = receipts.filter((r) => r.requestHash === expectedRequestHash && r.responseHash === expectedResponseHash);
2079
2101
  if (matches.length > 0) return pickLatest(matches);
2080
2102
  const respondedSiblings = allWorkLogs.filter((wl) => wl.delegationId === workLog.delegationId && (wl.responseOutput !== void 0 || wl.responseError !== void 0)).length;
@@ -2116,49 +2138,49 @@ function nextAction(input) {
2116
2138
  complete: false
2117
2139
  };
2118
2140
  }
2119
- if (relationship.state === "pending") {
2141
+ if (relationship.state === import_sdk6.RelationshipStates.PENDING) {
2120
2142
  return {
2121
2143
  hint: "Relationship is PENDING \u2014 one side owes `heyarp send-handshake-response <peer> --decision accept | decline`",
2122
2144
  owner: "either",
2123
2145
  complete: false
2124
2146
  };
2125
2147
  }
2126
- if (relationship.state === "paused") {
2148
+ if (relationship.state === import_sdk6.RelationshipStates.PAUSED) {
2127
2149
  return { hint: "Relationship is PAUSED \u2014 owner must `heyarp unpause` before any envelopes flow", owner: "either", complete: false };
2128
2150
  }
2129
- if (relationship.state === "closed") {
2151
+ if (relationship.state === import_sdk6.RelationshipStates.CLOSED) {
2130
2152
  return { hint: "Relationship is CLOSED \u2014 terminal state, no further action possible. Start a fresh handshake to reopen.", owner: "none", complete: false };
2131
2153
  }
2132
- if (!latestDelegation || latestDelegation.state === "declined" || latestDelegation.state === "canceled") {
2154
+ if (!latestDelegation || latestDelegation.state === import_sdk6.DelegationStates.DECLINED || latestDelegation.state === import_sdk6.DelegationStates.CANCELED) {
2133
2155
  return {
2134
2156
  hint: "No live delegation \u2014 buyer offers (terms only, no lock) `heyarp delegation offer <peer> --delegation-id <new-uuid> --title \u2026 --scope \u2026 --amount \u2026 --currency SOL:solana-devnet --deadline \u2026`, then funds the escrow lock AFTER the worker accepts via `heyarp delegation fund <del-id> --escrow-lock-from-file <path>`",
2135
2157
  owner: "either",
2136
2158
  complete: false
2137
2159
  };
2138
2160
  }
2139
- if (latestDelegation.state === "disputing") {
2161
+ if (latestDelegation.state === import_sdk6.DelegationStates.DISPUTING) {
2140
2162
  return {
2141
2163
  hint: `Delegation ${latestDelegation.delegationId} is DISPUTING \u2014 the buyer opened an on-chain dispute (their stake is escrowed too). The operator may rule until the dispute window lapses; after that either party closes with \`heyarp escrow dispute close ${latestDelegation.delegationId}\` (escrow returns to the buyer, both stakes return). Deadline: \`heyarp escrow show ${latestDelegation.delegationId}\`. No envelopes to send meanwhile.`,
2142
2164
  owner: "none",
2143
2165
  complete: false
2144
2166
  };
2145
2167
  }
2146
- if (latestDelegation.state === "completed") {
2168
+ if (latestDelegation.state === import_sdk6.DelegationStates.COMPLETED) {
2147
2169
  return {
2148
2170
  hint: "Cycle COMPLETE \u2014 payment claimed on chain (lock paid; the receipt carries releaseStatus=paid)",
2149
2171
  owner: "none",
2150
2172
  complete: true
2151
2173
  };
2152
2174
  }
2153
- if (latestDelegation.state === "failed" || latestDelegation.state === "refunded" || latestDelegation.state === "dispute_resolved") {
2175
+ if (latestDelegation.state === import_sdk6.DelegationStates.FAILED || latestDelegation.state === import_sdk6.DelegationStates.REFUNDED || latestDelegation.state === import_sdk6.DelegationStates.DISPUTE_RESOLVED) {
2154
2176
  const stateLabel = latestDelegation.state.toUpperCase();
2155
- const reason = latestDelegation.state === "failed" ? "on-chain escrow lock failed to finalise (typical causes: insufficient payer funds, wrong program-id, ProgramState PDA uninitialised \u2014 check `heyarp delegations <rel-id> --json | jq .[].escrowError` for the worker-side reason)" : latestDelegation.state === "refunded" ? (
2177
+ const reason = latestDelegation.state === import_sdk6.DelegationStates.FAILED ? "on-chain escrow lock failed to finalise (typical causes: insufficient payer funds, wrong program-id, ProgramState PDA uninitialised \u2014 check `heyarp delegations <rel-id> --json | jq .[].escrowError` for the worker-side reason)" : latestDelegation.state === import_sdk6.DelegationStates.REFUNDED ? (
2156
2178
  // Keyed on the DELEGATION row's releaseStatus, not the
2157
2179
  // receipt's: in the claim-expired path the worker never
2158
2180
  // submitted anything, so no receipt row exists to carry
2159
2181
  // the outcome — the delegation copy is always present
2160
2182
  // on indexer-driven terminals.
2161
- latestDelegation.releaseStatus === "dispute_closed" ? "the dispute window lapsed without an operator ruling and the dispute was closed \u2014 escrow returned to the buyer, both stakes returned (work was delivered but not paid)" : latestDelegation.releaseStatus === "revoked" ? "the work window lapsed without an on-chain submit \u2014 the buyer reclaimed the escrow AND the worker stake (claim_expired_work)" : "lock was refunded after expiry \u2014 no work was delivered against this delegation"
2183
+ latestDelegation.releaseStatus === import_sdk6.LockStates.DISPUTE_CLOSED ? "the dispute window lapsed without an operator ruling and the dispute was closed \u2014 escrow returned to the buyer, both stakes returned (work was delivered but not paid)" : latestDelegation.releaseStatus === import_sdk6.LockStates.REVOKED ? "the work window lapsed without an on-chain submit \u2014 the buyer reclaimed the escrow AND the worker stake (claim_expired_work)" : "lock was refunded after expiry \u2014 no work was delivered against this delegation"
2162
2184
  ) : "admin closed the delegation via the dispute-resolution path";
2163
2185
  return {
2164
2186
  hint: `Delegation ${latestDelegation.delegationId} is ${stateLabel} (terminal) \u2014 ${reason}. Issue a fresh \`heyarp delegation offer <peer> --delegation-id <new-uuid> \u2026\` to retry (FSM does not auto-retry).`,
@@ -2166,7 +2188,7 @@ function nextAction(input) {
2166
2188
  complete: false
2167
2189
  };
2168
2190
  }
2169
- if (latestDelegation.state === "offered") {
2191
+ if (latestDelegation.state === import_sdk6.DelegationStates.OFFERED) {
2170
2192
  const iAmOfferer = latestDelegation.offererDid === signerDid;
2171
2193
  const stateLabel = "OFFERED";
2172
2194
  return {
@@ -2175,7 +2197,7 @@ function nextAction(input) {
2175
2197
  complete: false
2176
2198
  };
2177
2199
  }
2178
- if (latestDelegation.state === "accepted" && !latestWorkLog) {
2200
+ if (latestDelegation.state === import_sdk6.DelegationStates.ACCEPTED && !latestWorkLog) {
2179
2201
  const iAmOfferer = latestDelegation.offererDid === signerDid;
2180
2202
  return {
2181
2203
  hint: iAmOfferer ? `Delegation ${latestDelegation.delegationId} is ACCEPTED \u2014 you (buyer) owe \`heyarp wallet create-lock --delegation-id ${latestDelegation.delegationId} --condition-hash <hex> ... > lock.json\` then \`heyarp delegation fund ${latestDelegation.delegationId} --escrow-lock-from-file lock.json\` (fund the escrow lock; work begins once it is LOCKED on chain)` : `Delegation ${latestDelegation.delegationId} is ACCEPTED \u2014 counterparty (the buyer) owes \`heyarp delegation fund\` to lock the escrow; wait for the delegation to reach LOCKED before working`,
@@ -2183,7 +2205,7 @@ function nextAction(input) {
2183
2205
  complete: false
2184
2206
  };
2185
2207
  }
2186
- if (latestDelegation.state === "pending_lock_finalization" && !latestWorkLog) {
2208
+ if (latestDelegation.state === import_sdk6.DelegationStates.PENDING_LOCK_FINALIZATION && !latestWorkLog) {
2187
2209
  return {
2188
2210
  hint: `Delegation ${latestDelegation.delegationId} is PENDING_LOCK_FINALIZATION \u2014 the escrow lock was funded and is awaiting on-chain confirmation; both sides wait (auto-advances to LOCKED). Poll with \`heyarp status ${relationship.relationshipId} --wait --until delegation.locked\`.`,
2189
2211
  owner: "none",
@@ -2198,7 +2220,7 @@ function nextAction(input) {
2198
2220
  complete: false
2199
2221
  };
2200
2222
  }
2201
- if (latestWorkLog.state === "requested") {
2223
+ if (latestWorkLog.state === import_sdk6.WorkLogStates.REQUESTED) {
2202
2224
  const iAmPayee = latestWorkLog.payeeDid === signerDid;
2203
2225
  return {
2204
2226
  hint: iAmPayee ? `work_request ${latestWorkLog.requestId} is REQUESTED \u2014 you owe \`heyarp work respond\` (output OR --error)` : `work_request ${latestWorkLog.requestId} is REQUESTED \u2014 counterparty (payee) owes \`heyarp work respond\``,
@@ -2294,13 +2316,13 @@ function cycleHeadline(s) {
2294
2316
  return `${import_chalk7.default.bold("Cycle:")} ${import_chalk7.default.red.bold("NOT FOUND")} ${import_chalk7.default.dim("\u2014 relationship missing or signer is not a member")}`;
2295
2317
  case "unknown":
2296
2318
  return `${import_chalk7.default.bold("Cycle:")} ${import_chalk7.default.yellow.bold("UNKNOWN")} ${import_chalk7.default.dim("\u2014 state probe inconclusive; hint below is advisory")}`;
2297
- case "closed":
2319
+ case import_sdk6.RelationshipStates.CLOSED:
2298
2320
  return `${import_chalk7.default.bold("Cycle:")} ${import_chalk7.default.red.bold("CLOSED")} ${import_chalk7.default.dim("\u2014 relationship terminal, no further protocol action")}`;
2299
- case "paused":
2321
+ case import_sdk6.RelationshipStates.PAUSED:
2300
2322
  return `${import_chalk7.default.bold("Cycle:")} ${import_chalk7.default.yellow.bold("PAUSED")} ${import_chalk7.default.dim("\u2014 relationship soft-disabled, resume to continue")}`;
2301
- case "pending":
2323
+ case import_sdk6.RelationshipStates.PENDING:
2302
2324
  return `${import_chalk7.default.bold("Cycle:")} ${import_chalk7.default.yellow.bold("PENDING")} ${import_chalk7.default.dim("\u2014 awaiting handshake_response")}`;
2303
- case "active":
2325
+ case import_sdk6.RelationshipStates.ACTIVE:
2304
2326
  default:
2305
2327
  return `${import_chalk7.default.bold("Cycle:")} ${import_chalk7.default.cyan.bold("ACTIVE")} ${import_chalk7.default.dim('\u2014 work in progress; see "Next action" below')}`;
2306
2328
  }
@@ -2315,41 +2337,41 @@ function stateColor(state) {
2315
2337
  }
2316
2338
 
2317
2339
  // src/commands/wallet.ts
2318
- var import_sdk6 = require("@heyanon-arp/sdk");
2340
+ var import_sdk9 = require("@heyanon-arp/sdk");
2319
2341
  var import_utils = require("@noble/hashes/utils");
2320
2342
  var import_web32 = require("@solana/web3.js");
2321
2343
  init_api();
2322
2344
  init_config();
2323
2345
 
2324
2346
  // src/solana/escrow-ix.ts
2325
- var import_sdk5 = require("@heyanon-arp/sdk");
2347
+ var import_sdk7 = require("@heyanon-arp/sdk");
2326
2348
  var import_web3 = require("@solana/web3.js");
2327
- var SPL_TOKEN_PROGRAM_ID = new import_web3.PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
2328
- var ASSOCIATED_TOKEN_PROGRAM_ID = new import_web3.PublicKey("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
2329
- var NATIVE_SOL_MINT = new import_web3.PublicKey(new Uint8Array(32));
2349
+ var SPL_TOKEN_PROGRAM_ID = new import_web3.PublicKey(import_sdk7.SPL_TOKEN_PROGRAM_ID_BASE58);
2350
+ var ASSOCIATED_TOKEN_PROGRAM_ID = new import_web3.PublicKey(import_sdk7.ASSOCIATED_TOKEN_PROGRAM_ID_BASE58);
2351
+ var NATIVE_SOL_MINT = new import_web3.PublicKey(import_sdk7.NATIVE_SOL_MINT_BASE58);
2330
2352
  function deriveLockPda(programId, lockId) {
2331
- return import_web3.PublicKey.findProgramAddressSync([Buffer.from(import_sdk5.ESCROW_PDA_SEEDS.LOCK), Buffer.from(lockId)], programId)[0];
2353
+ return import_web3.PublicKey.findProgramAddressSync([Buffer.from(import_sdk7.ESCROW_PDA_SEEDS.LOCK), Buffer.from(lockId)], programId)[0];
2332
2354
  }
2333
2355
  function deriveEscrowPda(programId, lockId) {
2334
- return import_web3.PublicKey.findProgramAddressSync([Buffer.from(import_sdk5.ESCROW_PDA_SEEDS.ESCROW), Buffer.from(lockId)], programId)[0];
2356
+ return import_web3.PublicKey.findProgramAddressSync([Buffer.from(import_sdk7.ESCROW_PDA_SEEDS.ESCROW), Buffer.from(lockId)], programId)[0];
2335
2357
  }
2336
2358
  function deriveConfigPda(programId) {
2337
- return import_web3.PublicKey.findProgramAddressSync([Buffer.from(import_sdk5.ESCROW_PDA_SEEDS.CONFIG)], programId)[0];
2359
+ return import_web3.PublicKey.findProgramAddressSync([Buffer.from(import_sdk7.ESCROW_PDA_SEEDS.CONFIG)], programId)[0];
2338
2360
  }
2339
2361
  function deriveStakeVaultPda(programId) {
2340
- return import_web3.PublicKey.findProgramAddressSync([Buffer.from(import_sdk5.ESCROW_PDA_SEEDS.STAKE_VAULT)], programId)[0];
2362
+ return import_web3.PublicKey.findProgramAddressSync([Buffer.from(import_sdk7.ESCROW_PDA_SEEDS.STAKE_VAULT)], programId)[0];
2341
2363
  }
2342
2364
  function deriveCollateralConfigPda(programId, mint) {
2343
- return import_web3.PublicKey.findProgramAddressSync([Buffer.from(import_sdk5.ESCROW_PDA_SEEDS.COLLATERAL), mint.toBuffer()], programId)[0];
2365
+ return import_web3.PublicKey.findProgramAddressSync([Buffer.from(import_sdk7.ESCROW_PDA_SEEDS.COLLATERAL), mint.toBuffer()], programId)[0];
2344
2366
  }
2345
2367
  function deriveDisputeResolutionPda(programId, lockId) {
2346
- return import_web3.PublicKey.findProgramAddressSync([Buffer.from(import_sdk5.ESCROW_PDA_SEEDS.DISPUTE_RESOLUTION), Buffer.from(lockId)], programId)[0];
2368
+ return import_web3.PublicKey.findProgramAddressSync([Buffer.from(import_sdk7.ESCROW_PDA_SEEDS.DISPUTE_RESOLUTION), Buffer.from(lockId)], programId)[0];
2347
2369
  }
2348
2370
  function deriveOperatorAuthPda(programId, operator) {
2349
- return import_web3.PublicKey.findProgramAddressSync([Buffer.from(import_sdk5.ESCROW_PDA_SEEDS.OPERATOR_AUTH), operator.toBuffer()], programId)[0];
2371
+ return import_web3.PublicKey.findProgramAddressSync([Buffer.from(import_sdk7.ESCROW_PDA_SEEDS.OPERATOR_AUTH), operator.toBuffer()], programId)[0];
2350
2372
  }
2351
2373
  function deriveEventAuthorityPda(programId) {
2352
- return import_web3.PublicKey.findProgramAddressSync([Buffer.from(import_sdk5.ESCROW_PDA_SEEDS.EVENT_AUTHORITY)], programId)[0];
2374
+ return import_web3.PublicKey.findProgramAddressSync([Buffer.from(import_sdk7.ESCROW_PDA_SEEDS.EVENT_AUTHORITY)], programId)[0];
2353
2375
  }
2354
2376
  function deriveAta(owner, mint) {
2355
2377
  return import_web3.PublicKey.findProgramAddressSync([owner.toBuffer(), SPL_TOKEN_PROGRAM_ID.toBuffer(), mint.toBuffer()], ASSOCIATED_TOKEN_PROGRAM_ID)[0];
@@ -2364,12 +2386,12 @@ function meta(pubkey, opts = {}) {
2364
2386
  return { pubkey, isSigner: opts.signer === true, isWritable: opts.writable === true };
2365
2387
  }
2366
2388
  function noArgIx(programId, name, keys) {
2367
- return new import_web3.TransactionInstruction({ programId, keys, data: Buffer.from((0, import_sdk5.buildLifecycleIxData)(name)) });
2389
+ return new import_web3.TransactionInstruction({ programId, keys, data: Buffer.from((0, import_sdk7.buildLifecycleIxData)(name)) });
2368
2390
  }
2369
2391
  function buildCreateLockIx(input) {
2370
2392
  const { programId, lockId } = input;
2371
2393
  const native = input.mint === null;
2372
- const data = Buffer.from((0, import_sdk5.buildCreateLockIxData)({ lockId, amount: input.amount, conditionHash: input.conditionHash }, { native }));
2394
+ const data = Buffer.from((0, import_sdk7.buildCreateLockIxData)({ lockId, amount: input.amount, conditionHash: input.conditionHash }, { native }));
2373
2395
  const head = [
2374
2396
  meta(input.payer, { signer: true, writable: true }),
2375
2397
  meta(input.payee),
@@ -2505,7 +2527,7 @@ function buildOpenDisputeIx(input) {
2505
2527
  function buildResolveDisputeIx(input) {
2506
2528
  const { programId, lockId } = input;
2507
2529
  const native = input.mint === null;
2508
- const data = Buffer.from((0, import_sdk5.buildResolveDisputeIxData)(input.args, { native }));
2530
+ const data = Buffer.from((0, import_sdk7.buildResolveDisputeIxData)(input.args, { native }));
2509
2531
  if (native) {
2510
2532
  return new import_web3.TransactionInstruction({
2511
2533
  programId,
@@ -2582,16 +2604,17 @@ function buildCloseDisputeIx(input) {
2582
2604
  ]);
2583
2605
  }
2584
2606
  async function fetchLockAccount(conn, programId, delegationId) {
2585
- const lockId = (0, import_sdk5.deriveLockId)(delegationId);
2607
+ const lockId = (0, import_sdk7.deriveLockId)(delegationId);
2586
2608
  const lockPda = deriveLockPda(programId, lockId);
2587
2609
  const info = await conn.getAccountInfo(lockPda);
2588
2610
  if (!info) return null;
2589
- return { lockId, lockPda, lock: (0, import_sdk5.decodeLockAccount)(Uint8Array.from(info.data)) };
2611
+ return { lockId, lockPda, lock: (0, import_sdk7.decodeLockAccount)(Uint8Array.from(info.data)) };
2590
2612
  }
2591
2613
 
2592
2614
  // src/commands/token-amount.ts
2615
+ var import_sdk8 = require("@heyanon-arp/sdk");
2593
2616
  function toBaseUnits(amountDecimal, decimals) {
2594
- if (!/^[0-9]+(\.[0-9]+)?$/.test(amountDecimal)) {
2617
+ if (!(0, import_sdk8.isDecimalAmountString)(amountDecimal)) {
2595
2618
  throw new Error(`amount '${amountDecimal}' is not a non-negative decimal number`);
2596
2619
  }
2597
2620
  if (!Number.isInteger(decimals) || decimals < 0 || decimals > 255) {
@@ -2620,8 +2643,8 @@ function normaliseDelegationId(raw) {
2620
2643
  }
2621
2644
  throw new Error(`wallet: --delegation-id must be either 'del_<uuid>' or a bare canonical-lowercase UUID (got '${raw}')`);
2622
2645
  }
2623
- var SPL_TOKEN_PROGRAM_ID2 = new import_web32.PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
2624
- var ASSOCIATED_TOKEN_PROGRAM_ID2 = new import_web32.PublicKey("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
2646
+ var SPL_TOKEN_PROGRAM_ID2 = new import_web32.PublicKey(import_sdk9.SPL_TOKEN_PROGRAM_ID_BASE58);
2647
+ var ASSOCIATED_TOKEN_PROGRAM_ID2 = new import_web32.PublicKey(import_sdk9.ASSOCIATED_TOKEN_PROGRAM_ID_BASE58);
2625
2648
  var FALLBACK_RPC_URL = "https://api.mainnet-beta.solana.com";
2626
2649
  function resolveRpcUrl(opts) {
2627
2650
  if (opts.rpcUrl !== void 0 && opts.rpcUrl !== "") return opts.rpcUrl;
@@ -2641,7 +2664,7 @@ function redactRpcUrl(url) {
2641
2664
  return "<redacted>";
2642
2665
  }
2643
2666
  }
2644
- var FALLBACK_PROGRAM_ID = "7trAdKybX4kMKARia9nrRPj9rBuUjDTxRzExzwFTvvXg";
2667
+ var FALLBACK_PROGRAM_ID = import_sdk9.ESCROW_PROGRAM_ID_BASE58;
2645
2668
  async function resolveProgramIdWithSource(api, opts) {
2646
2669
  if (opts.programId !== void 0 && opts.programId !== "") {
2647
2670
  return { programId: opts.programId, source: "flag" };
@@ -2725,7 +2748,7 @@ async function derivePdasHandler(opts) {
2725
2748
  const normalisedDelegationId = normaliseDelegationId(opts.delegationId);
2726
2749
  const api = new ArpApiClient(opts.server);
2727
2750
  const programId = new import_web32.PublicKey(await resolveProgramIdStrict(api, opts, "wallet derive-pdas"));
2728
- const lockIdBytes = (0, import_sdk6.deriveLockId)(normalisedDelegationId);
2751
+ const lockIdBytes = (0, import_sdk9.deriveLockId)(normalisedDelegationId);
2729
2752
  const lockIdSeed = Buffer.from(lockIdBytes);
2730
2753
  const lockIdHex = Buffer.from(lockIdBytes).toString("hex");
2731
2754
  const [lockPda] = import_web32.PublicKey.findProgramAddressSync([Buffer.from("lock"), lockIdSeed], programId);
@@ -2744,11 +2767,11 @@ async function derivePdasHandler(opts) {
2744
2767
  };
2745
2768
  }
2746
2769
  var TERMINAL_METHOD = {
2747
- paid: "claim_work_payment",
2748
- canceled: "cancel_lock",
2749
- revoked: "claim_expired_work",
2750
- dispute_resolved: "resolve_dispute",
2751
- dispute_closed: "close_dispute"
2770
+ [import_sdk9.LockStates.PAID]: "claim_work_payment",
2771
+ [import_sdk9.LockStates.CANCELED]: "cancel_lock",
2772
+ [import_sdk9.LockStates.REVOKED]: "claim_expired_work",
2773
+ [import_sdk9.LockStates.DISPUTE_RESOLVED]: "resolve_dispute",
2774
+ [import_sdk9.LockStates.DISPUTE_CLOSED]: "close_dispute"
2752
2775
  };
2753
2776
  function registerVerifyRelease(cmd) {
2754
2777
  cmd.command("verify-release").description(
@@ -2794,7 +2817,7 @@ async function verifyReleaseHandler(opts) {
2794
2817
  const rpcUrl = resolveRpcUrlStrict(opts);
2795
2818
  const conn = new import_web32.Connection(rpcUrl, "confirmed");
2796
2819
  const fetched = await fetchLockAccount(conn, programId, normalisedDelegationId);
2797
- const lockId = (0, import_sdk6.deriveLockId)(normalisedDelegationId);
2820
+ const lockId = (0, import_sdk9.deriveLockId)(normalisedDelegationId);
2798
2821
  const lockPda = deriveLockPda(programId, lockId);
2799
2822
  const escrowPda = deriveEscrowPda(programId, lockId);
2800
2823
  if (!fetched) {
@@ -2815,7 +2838,7 @@ async function verifyReleaseHandler(opts) {
2815
2838
  lock_pda: lockPda.toBase58(),
2816
2839
  escrow_pda: escrowPda.toBase58(),
2817
2840
  lock_account_exists: true,
2818
- released: status === "paid",
2841
+ released: status === import_sdk9.LockStates.PAID,
2819
2842
  status,
2820
2843
  ...TERMINAL_METHOD[status] !== void 0 ? { release_method: TERMINAL_METHOD[status] } : {},
2821
2844
  lock_state: lock.stateByte,
@@ -2825,23 +2848,23 @@ async function verifyReleaseHandler(opts) {
2825
2848
  }
2826
2849
  function renderStatusLine(status) {
2827
2850
  switch (status) {
2828
- case "paid":
2851
+ case import_sdk9.LockStates.PAID:
2829
2852
  return "\u2713 paid \u2014 claim_work_payment landed (buyer approval or post-review self-claim); the payee was paid net of any protocol fee";
2830
- case "created":
2853
+ case import_sdk9.LockStates.CREATED:
2831
2854
  return "\u2717 created \u2014 funded, awaiting the worker accept_lock (stake) \u2014 or a buyer cancel";
2832
- case "in_progress":
2855
+ case import_sdk9.LockStates.IN_PROGRESS:
2833
2856
  return "\u2717 in_progress \u2014 worker accepted + staked; work window running";
2834
- case "submitted":
2857
+ case import_sdk9.LockStates.SUBMITTED:
2835
2858
  return "\u2717 submitted \u2014 work delivered on-chain; review window running (buyer claims to approve, disputes to refuse; worker self-claims after expiry)";
2836
- case "disputing":
2859
+ case import_sdk9.LockStates.DISPUTING:
2837
2860
  return "\u2717 disputing \u2014 buyer disputed; operator has the dispute window to rule, after that either party can close";
2838
- case "canceled":
2861
+ case import_sdk9.LockStates.CANCELED:
2839
2862
  return "\u2717 canceled \u2014 buyer canceled pre-accept; escrow returned";
2840
- case "revoked":
2863
+ case import_sdk9.LockStates.REVOKED:
2841
2864
  return "\u2717 revoked \u2014 work window lapsed unsubmitted; buyer reclaimed the escrow + the worker stake";
2842
- case "dispute_resolved":
2865
+ case import_sdk9.LockStates.DISPUTE_RESOLVED:
2843
2866
  return "\u2717 dispute_resolved \u2014 operator ruled; winner took the escrow per the on-chain split";
2844
- case "dispute_closed":
2867
+ case import_sdk9.LockStates.DISPUTE_CLOSED:
2845
2868
  return "\u2717 dispute_closed \u2014 dispute window lapsed unresolved; escrow returned to the buyer, stakes returned";
2846
2869
  case "lock_never_created":
2847
2870
  return "\u2717 lock_never_created \u2014 no Lock PDA on this cluster/program; create_lock never fired (or wrong --rpc-url/--program-id)";
@@ -3027,7 +3050,7 @@ async function createLockHandler(opts) {
3027
3050
  if (clusterTag !== 0 && clusterTag !== 1) {
3028
3051
  throw new Error(`--cluster-tag must be 0 (devnet) or 1 (mainnet) (got ${clusterTag})`);
3029
3052
  }
3030
- const clusterCaip2 = clusterTag === 1 ? import_sdk6.SOLANA_CLUSTER_IDS["solana-mainnet"] : import_sdk6.SOLANA_CLUSTER_IDS["solana-devnet"];
3053
+ const clusterCaip2 = clusterTag === 1 ? import_sdk9.SOLANA_CLUSTER_IDS["solana-mainnet"] : import_sdk9.SOLANA_CLUSTER_IDS["solana-devnet"];
3031
3054
  const expectedLockAsset = typeof opts.mintPubkey === "string" && opts.mintPubkey !== "" ? { kind: "spl", mint: parsePubkey(opts.mintPubkey, "--mint-pubkey").toBase58(), cluster: clusterCaip2 } : { kind: "native" };
3032
3055
  if (!offlineMode) {
3033
3056
  await preflightLockCurrency(api, agent, normalisedDelegationId, expectedLockAsset);
@@ -3037,7 +3060,7 @@ async function createLockHandler(opts) {
3037
3060
  const conn = new import_web32.Connection(rpcUrl, "confirmed");
3038
3061
  const asset = await resolveCreateLockAsset(conn, opts, payerKp.publicKey, clusterCaip2);
3039
3062
  const amount = asset.amount;
3040
- const lockIdBytes = (0, import_sdk6.deriveLockId)(normalisedDelegationId);
3063
+ const lockIdBytes = (0, import_sdk9.deriveLockId)(normalisedDelegationId);
3041
3064
  const ix = buildCreateLockIx({
3042
3065
  programId,
3043
3066
  lockId: lockIdBytes,
@@ -3121,63 +3144,12 @@ function registerDelegationCommands(root) {
3121
3144
  registerDecline(cmd);
3122
3145
  registerCancel(cmd);
3123
3146
  }
3124
- var POST_COMMIT_ERROR_CODES = /* @__PURE__ */ new Set([
3125
- "DELEGATION_ALREADY_EXISTS",
3126
- "DELEGATION_INVALID_STATE",
3127
- "DELEGATION_NOT_FOUND",
3128
- "DELEGATION_RELATIONSHIP_MISMATCH",
3129
- "DELEGATION_ACCEPTER_IS_OFFERER",
3130
- "DELEGATION_DECLINER_IS_OFFERER",
3131
- "DELEGATION_CANCELER_NOT_OFFERER",
3132
- // PricingPolicy: the offer's terms fell outside the RECIPIENT's
3133
- // published accept-prefs. Fires in `handleOffer` AFTER the event
3134
- // row commit (pre-materialization on the server), so the sequence
3135
- // was consumed — advance it or the corrected re-offer trips
3136
- // ENV_SEQUENCE_BACKWARDS.
3137
- // Asset whitelist gate: the offer currency is not a whitelisted
3138
- // payment asset on this server (or its decimals diverge from the
3139
- // canonical entry). Same lifecycle as the pricing gate — sequence
3140
- // consumed. Fix the --currency and re-offer.
3141
- "DELEGATION_ASSET_NOT_ALLOWED",
3142
- "DELEGATION_PRICING_MISMATCH",
3143
- // Capacity gate: the recipient is at its published
3144
- // maxActiveDelegations cap (or closed with 0). Same lifecycle as
3145
- // the pricing gate — sequence consumed. TRANSIENT: retry later.
3146
- "DELEGATION_CAPACITY_EXCEEDED",
3147
- // `DELEGATION_PENDING_LOCK` fires from the body handler's
3148
- // `requireDelegationInState` AFTER the event row is persisted
3149
- // (same code path as DELEGATION_INVALID_STATE), so an accept
3150
- // against a PENDING_LOCK delegation consumes sender_sequence
3151
- // even though it rejects.
3152
- "DELEGATION_PENDING_LOCK",
3153
- // The `fund` body handler (`handleFund`) emits
3154
- // these AFTER the event row is committed (same lifecycle as
3155
- // DELEGATION_INVALID_STATE). `DELEGATION_FUNDER_NOT_OFFERER` (403)
3156
- // when a non-offerer attempts the fund; `DELEGATION_ALREADY_FUNDED`
3157
- // (409) on a re-fund of a delegation that already moved into the
3158
- // lock lifecycle (pending_lock_finalization / locked, or an
3159
- // in-flight create_lock op). Both consume sender_sequence, so the
3160
- // CLI must advance `lastSenderSequence` or a retry trips
3161
- // `ENV_SEQUENCE_BACKWARDS`.
3162
- "DELEGATION_FUNDER_NOT_OFFERER",
3163
- "DELEGATION_ALREADY_FUNDED",
3164
- // Lock-validator mint.owner pre-flight — kept here for
3165
- // documentation, but the `isPostCommitErrorCode` helper below
3166
- // also matches any `ESC_LOCK_*` prefix. The full lock-validator
3167
- // cross-check set (ID_MISMATCH, CONDITION_HASH_MISMATCH,
3168
- // AMOUNT_DELEGATION_MISMATCH, EXPIRY_TOO_*, PDA_*, etc.) ALSO
3169
- // fires after the delegation event is committed; missing
3170
- // entries here would leave `lastSenderSequence` stale and stall
3171
- // retries on ENV_SEQUENCE_BACKWARDS.
3172
- "ESC_LOCK_MINT_RPC_FAILED",
3173
- "ESC_LOCK_MINT_NOT_FOUND",
3174
- "ESC_LOCK_MINT_OWNER_MISMATCH"
3175
- ]);
3176
- function isPostCommitErrorCode(code) {
3177
- return POST_COMMIT_ERROR_CODES.has(code) || code.startsWith("ESC_LOCK_") || code.startsWith("SDK_");
3178
- }
3179
3147
  function registerOffer(parent) {
3180
- parent.command("offer").description("Open a new delegation addressed to <recipient-did> with the agreed terms INLINE (no escrow lock \u2014 fund AFTER acceptance via `delegation fund`).").argument("<recipient-did>", "Recipient agent DID (did:arp:...)").option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Sender DID \u2014 required only if multiple agents are registered against this server").option("--delegation-id <uuid>", "Override the auto-generated delegation id (UUID). Reuse the SAME id at `heyarp delegation fund` time. Useful for replay / scripting.").option("--title <s>", "Required: human-readable title for the offer").option("--brief <json>", "Optional structured brief (JSON object)").option("--criterion <s>", "acceptance_criteria \u2014 repeatable; pass --criterion once per bullet", collectRepeated, []).option("--deadline <rfc3339>", 'Optional RFC 3339 deadline (e.g. "2026-12-31T23:59:59Z")').option("--scope <text>", "scope_summary \u2014 short prose describing the agreed work. Required.").option("--amount <s>", 'Optional decimal-as-string amount (e.g. "10.00"). REQUIRES --currency if set.').option("--currency <s>", `Asset identifier: shorthand (${import_sdk7.WELL_KNOWN_ASSET_KEYS.join("|")}) OR raw CAIP-19 string.`).option("--currency-decimals <n>", "Decimal places for base-unit conversion (0-18). Required only when --currency is raw CAIP-19.").option("--currency-symbol <s>", 'Optional UI hint ("USDC", "SOL"). Max 16 chars.').option("--ttl <seconds>", "Envelope TTL in seconds (max 86400 = 24h)", "3600").option("--verbose", "Print the full envelope before sending and the full server response", false).option(
3148
+ parent.command("offer").description("Open a new delegation addressed to <recipient-did> with the agreed terms INLINE (no escrow lock \u2014 fund AFTER acceptance via `delegation fund`).").argument("<recipient-did>", "Recipient agent DID (did:arp:...)").option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Sender DID \u2014 required only if multiple agents are registered against this server").option("--delegation-id <uuid>", "Override the auto-generated delegation id (UUID). Reuse the SAME id at `heyarp delegation fund` time. Useful for replay / scripting.").option("--title <s>", "Required: human-readable title for the offer").option("--brief <json>", "Optional structured brief (JSON object)").option("--criterion <s>", "acceptance_criteria \u2014 repeatable; pass --criterion once per bullet", collectRepeated, []).option("--deadline <rfc3339>", 'Optional RFC 3339 deadline (e.g. "2026-12-31T23:59:59Z")').option("--scope <text>", "scope_summary \u2014 short prose describing the agreed work. Required.").option("--amount <s>", 'Optional decimal-as-string amount (e.g. "10.00"). REQUIRES --currency if set.').option("--currency <s>", `Asset identifier: shorthand (${import_sdk10.WELL_KNOWN_ASSET_KEYS.join("|")}) OR raw CAIP-19 string.`).option("--currency-decimals <n>", "Decimal places for base-unit conversion (0-18). Required only when --currency is raw CAIP-19.").option("--currency-symbol <s>", 'Optional UI hint ("USDC", "SOL"). Max 16 chars.').option("--ttl <seconds>", `Envelope TTL in seconds (max ${import_sdk10.MAX_ENVELOPE_TTL_SECONDS} = 24h)`, "3600").option("--verbose", "Print the full envelope before sending and the full server response. Mutually exclusive with --json.", false).option(
3149
+ "--json",
3150
+ 'Machine-readable mode \u2014 emit a single JSON object on stdout ({ok, action:"offer", delegationId, eventId, relationshipId, relationshipEventIndex, serverTimestamp, serverEventHash}). Prelude + the "reference this delegation" hints move to stderr; on failure stderr carries `{code, message}`. Mutually exclusive with --verbose.',
3151
+ false
3152
+ ).option(
3181
3153
  "--wait-until <phase>",
3182
3154
  '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.'
3183
3155
  ).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(
@@ -3283,6 +3255,11 @@ function assembleEscrowLockAttachment(opts) {
3283
3255
  };
3284
3256
  }
3285
3257
  async function runOffer(recipientDid, opts) {
3258
+ if (opts.verbose && opts.json) {
3259
+ throw new Error(
3260
+ "delegation offer: --verbose and --json are mutually exclusive. --json emits the structured server response; --verbose adds dumps that would break `--json | jq`."
3261
+ );
3262
+ }
3286
3263
  requireDid("delegation offer", recipientDid, "<recipient-did>");
3287
3264
  const ttlSeconds = parseTtl("delegation offer", opts.ttl);
3288
3265
  if (!opts.title || opts.title.length === 0) {
@@ -3290,7 +3267,7 @@ async function runOffer(recipientDid, opts) {
3290
3267
  }
3291
3268
  const terms = parseOfferTerms("delegation offer", opts);
3292
3269
  const offeredAssetId = terms.currency?.asset_id;
3293
- if (offeredAssetId && !(0, import_sdk7.isWhitelistedAssetId)(offeredAssetId)) {
3270
+ if (offeredAssetId && !(0, import_sdk10.isWhitelistedAssetId)(offeredAssetId)) {
3294
3271
  console.error(
3295
3272
  import_chalk8.default.yellow(
3296
3273
  `warning: currency '${offeredAssetId}' is not in the static payment whitelist \u2014 the server will reject the offer (DELEGATION_ASSET_NOT_ALLOWED) unless its deploy registers this asset. See 'heyarp assets'.`
@@ -3311,21 +3288,21 @@ async function runOffer(recipientDid, opts) {
3311
3288
  const api = new ArpApiClient(opts.server);
3312
3289
  const sender = resolveSenderAgent("delegation offer", opts.server, opts.fromDid);
3313
3290
  const content = {
3314
- action: "offer",
3291
+ action: import_sdk10.DelegationActions.OFFER,
3315
3292
  delegation_id: delegationId,
3316
3293
  title: opts.title,
3317
3294
  ...terms
3318
3295
  };
3319
3296
  const body = { type: "delegation", content };
3320
- console.log(import_chalk8.default.dim(`Server: ${api.serverUrl}`));
3321
- console.log(import_chalk8.default.dim(`Sender: ${sender.did}`));
3322
- console.log(import_chalk8.default.dim(`Recipient: ${recipientDid}`));
3323
- console.log(import_chalk8.default.dim(`Delegation: ${delegationId}`));
3297
+ progress(opts.json, import_chalk8.default.dim(`Server: ${api.serverUrl}`));
3298
+ progress(opts.json, import_chalk8.default.dim(`Sender: ${sender.did}`));
3299
+ progress(opts.json, import_chalk8.default.dim(`Recipient: ${recipientDid}`));
3300
+ progress(opts.json, import_chalk8.default.dim(`Delegation: ${delegationId}`));
3324
3301
  let result;
3325
3302
  try {
3326
3303
  result = await sendDelegationEnvelope({ api, sender, recipientDid, body, ttlSeconds, verbose: opts.verbose, server: opts.server });
3327
3304
  } catch (err) {
3328
- if (err instanceof ApiError && err.payload.code === "DELEGATION_ASSET_NOT_ALLOWED") {
3305
+ if (err instanceof ApiError && err.payload.code === import_sdk10.DelegationOfferRejectionCodes.ASSET_NOT_ALLOWED) {
3329
3306
  const d = err.payload.details;
3330
3307
  console.error(import_chalk8.default.yellow(`
3331
3308
  The server's payment-asset whitelist rejected this currency.`));
@@ -3338,7 +3315,7 @@ See the whitelist (shorthand keys + canonical decimals):
3338
3315
  heyarp assets
3339
3316
  then re-offer with a whitelisted --currency.`));
3340
3317
  }
3341
- if (err instanceof ApiError && err.payload.code === "DELEGATION_PRICING_MISMATCH") {
3318
+ if (err instanceof ApiError && err.payload.code === import_sdk10.DelegationOfferRejectionCodes.PRICING_MISMATCH) {
3342
3319
  const d = err.payload.details;
3343
3320
  console.error(import_chalk8.default.yellow(`
3344
3321
  The recipient's published accept-prefs rejected this offer${d?.reason ? ` (mismatch: ${d.reason})` : ""}.`));
@@ -3355,7 +3332,7 @@ then re-run with matching --currency / --amount.`
3355
3332
  )
3356
3333
  );
3357
3334
  }
3358
- if (err instanceof ApiError && err.payload.code === "DELEGATION_CAPACITY_EXCEEDED") {
3335
+ if (err instanceof ApiError && err.payload.code === import_sdk10.DelegationOfferRejectionCodes.CAPACITY_EXCEEDED) {
3359
3336
  const d = err.payload.details;
3360
3337
  const cap = d?.maxActive === 0 ? "closed for new offers (busy)" : `at capacity (${d?.currentActive}/${d?.maxActive} active delegations)`;
3361
3338
  console.error(import_chalk8.default.yellow(`
@@ -3364,17 +3341,31 @@ The recipient is ${cap}.`));
3364
3341
  }
3365
3342
  throw err;
3366
3343
  }
3367
- printIngestResult(result);
3368
- console.log(import_chalk8.default.dim(`
3344
+ if (opts.json) {
3345
+ jsonOut({
3346
+ ok: true,
3347
+ action: "offer",
3348
+ delegationId,
3349
+ eventId: result.eventId,
3350
+ relationshipId: result.relationshipId,
3351
+ relationshipEventIndex: result.relationshipEventIndex,
3352
+ serverTimestamp: result.serverTimestamp,
3353
+ serverEventHash: result.serverEventHash,
3354
+ prevServerEventHash: result.prevServerEventHash ?? null
3355
+ });
3356
+ } else {
3357
+ printIngestResult(result);
3358
+ console.log(import_chalk8.default.dim(`
3369
3359
  Reference this delegation on subsequent calls with:`));
3370
- console.log(import_chalk8.default.dim(` heyarp delegation accept ${result.relationshipId} ${delegationId}`));
3371
- console.log(import_chalk8.default.dim(` heyarp delegation decline ${result.relationshipId} ${delegationId}`));
3372
- console.log(import_chalk8.default.dim(` heyarp delegation cancel ${result.relationshipId} ${delegationId}`));
3373
- console.log(import_chalk8.default.dim(`
3360
+ console.log(import_chalk8.default.dim(` heyarp delegation accept ${result.relationshipId} ${delegationId}`));
3361
+ console.log(import_chalk8.default.dim(` heyarp delegation decline ${result.relationshipId} ${delegationId}`));
3362
+ console.log(import_chalk8.default.dim(` heyarp delegation cancel ${result.relationshipId} ${delegationId}`));
3363
+ console.log(import_chalk8.default.dim(`
3374
3364
  After the worker accepts, fund the escrow lock:`));
3375
- console.log(import_chalk8.default.dim(` heyarp wallet create-lock --delegation-id ${delegationId} --condition-hash <hex> ... > lock.json`));
3376
- console.log(import_chalk8.default.dim(` heyarp delegation fund ${delegationId} --escrow-lock-from-file lock.json`));
3377
- if (opts.waitUntil) {
3365
+ console.log(import_chalk8.default.dim(` heyarp wallet create-lock --delegation-id ${delegationId} --condition-hash <hex> ... > lock.json`));
3366
+ console.log(import_chalk8.default.dim(` heyarp delegation fund ${delegationId} --escrow-lock-from-file lock.json`));
3367
+ }
3368
+ if (opts.waitUntil && !opts.json) {
3378
3369
  const untilPhase = parseUntilPhase(opts.waitUntil);
3379
3370
  if (untilPhase === void 0) {
3380
3371
  throw new Error(`delegation offer: --wait-until requires a phase value (got ${JSON.stringify(opts.waitUntil)})`);
@@ -3394,7 +3385,7 @@ After the worker accepts, fund the escrow lock:`));
3394
3385
  }
3395
3386
  }
3396
3387
  function registerFund(parent) {
3397
- parent.command("fund").description("Fund an ACCEPTED delegation with the escrow lock (buyer-only). Run AFTER the worker accepts the offer.").argument("<delegation-id>", "Delegation UUID (the one you offered + the worker accepted)").option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Sender DID \u2014 required only if multiple agents are registered against this server").option("--ttl <seconds>", "Envelope TTL in seconds (max 86400 = 24h)", "3600").option("--verbose", "Print the full envelope before sending and the full server response. Mutually exclusive with --json.", false).option(
3388
+ parent.command("fund").description("Fund an ACCEPTED delegation with the escrow lock (buyer-only). Run AFTER the worker accepts the offer.").argument("<delegation-id>", "Delegation UUID (the one you offered + the worker accepted)").option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Sender DID \u2014 required only if multiple agents are registered against this server").option("--ttl <seconds>", `Envelope TTL in seconds (max ${import_sdk10.MAX_ENVELOPE_TTL_SECONDS} = 24h)`, "3600").option("--verbose", "Print the full envelope before sending and the full server response. Mutually exclusive with --json.", false).option(
3398
3389
  "--json",
3399
3390
  'Machine-readable mode \u2014 emit a single JSON object on stdout ({ok, action:"fund", delegationId, eventId, relationshipId, relationshipEventIndex, serverTimestamp, serverEventHash}). Prelude moves off stdout; on failure stderr carries `{code, message}`. Mutually exclusive with --verbose.',
3400
3391
  false
@@ -3458,7 +3449,7 @@ async function runFund(delegationId, opts) {
3458
3449
  );
3459
3450
  }
3460
3451
  }
3461
- const content = { action: "fund", delegation_id: delegationId };
3452
+ const content = { action: import_sdk10.DelegationActions.FUND, delegation_id: delegationId };
3462
3453
  const body = { type: "delegation", content };
3463
3454
  const attachments = { escrow_lock: escrowResult.attachment };
3464
3455
  progress(opts.json, import_chalk8.default.dim(`Server: ${api.serverUrl}`));
@@ -3559,8 +3550,12 @@ function registerDecline(parent) {
3559
3550
  "--reason <code>",
3560
3551
  // surface the closed enum at help time so operators
3561
3552
  // don't have to read source to find acceptable values.
3562
- `Required: decline reason code (one of: ${import_sdk7.DECLINE_REASONS.join(", ")}). Carried in body.content.reason so the counterparty's reactor can branch on it.`
3563
- ).option("--reason-detail <s>", 'Optional free-text elaboration alongside --reason (e.g. "rate floor 0.20 USDC"). Max 512 chars.').option("--verbose", "Print the full envelope before sending and the full server response", false).option(
3553
+ `Required: decline reason code (one of: ${import_sdk10.DECLINE_REASONS.join(", ")}). Carried in body.content.reason so the counterparty's reactor can branch on it.`
3554
+ ).option("--reason-detail <s>", 'Optional free-text elaboration alongside --reason (e.g. "rate floor 0.20 USDC"). Max 512 chars.').option("--verbose", "Print the full envelope before sending and the full server response. Mutually exclusive with --json.", false).option(
3555
+ "--json",
3556
+ 'Machine-readable mode \u2014 emit a single JSON object on stdout ({ok, action:"decline", delegationId, eventId, relationshipId, relationshipEventIndex, serverTimestamp, serverEventHash}). Prelude + pending-lock poll chatter move off stdout; on failure stderr carries `{code, message}`. Mutually exclusive with --verbose.',
3557
+ false
3558
+ ).option(
3564
3559
  "--no-wait-for-lock",
3565
3560
  "Opt-out of the pending-lock pre-flight poll. Default is ON: when the resolved delegation is in `pending_lock_finalization`, the CLI polls until the on-chain lock is confirmed before signing the envelope (avoids the racy DELEGATION_PENDING_LOCK 409 that consumes sender_sequence)."
3566
3561
  ).option("--lock-wait-timeout <seconds>", "Max wall-clock seconds to wait for pending-lock pre-flight (default 300).", "300").option("--lock-wait-interval <seconds>", "Poll cadence for pending-lock pre-flight in seconds. Bound to [1, 60].", "3").action(async (relationshipId, delegationId, opts) => {
@@ -3568,7 +3563,11 @@ function registerDecline(parent) {
3568
3563
  });
3569
3564
  }
3570
3565
  function registerCancel(parent) {
3571
- parent.command("cancel").description("Cancel an OFFERED delegation \u2014 moves to CANCELED. Offerer-only (the OTHER side uses decline).").argument("<relationship-id>", "Relationship UUID").argument("<delegation-id>", "Delegation UUID").option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Sender DID \u2014 required only if multiple agents are registered against this server").option("--ttl <seconds>", "Envelope TTL in seconds", "3600").option("--verbose", "Print the full envelope before sending and the full server response", false).option(
3566
+ parent.command("cancel").description("Cancel an OFFERED delegation \u2014 moves to CANCELED. Offerer-only (the OTHER side uses decline).").argument("<relationship-id>", "Relationship UUID").argument("<delegation-id>", "Delegation UUID").option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Sender DID \u2014 required only if multiple agents are registered against this server").option("--ttl <seconds>", "Envelope TTL in seconds", "3600").option("--verbose", "Print the full envelope before sending and the full server response. Mutually exclusive with --json.", false).option(
3567
+ "--json",
3568
+ 'Machine-readable mode \u2014 emit a single JSON object on stdout ({ok, action:"cancel", delegationId, eventId, relationshipId, relationshipEventIndex, serverTimestamp, serverEventHash}). Prelude + pending-lock poll chatter move off stdout; on failure stderr carries `{code, message}`. Mutually exclusive with --verbose.',
3569
+ false
3570
+ ).option(
3572
3571
  "--no-wait-for-lock",
3573
3572
  "Opt-out of the pending-lock pre-flight poll. Default is ON: when the resolved delegation is in `pending_lock_finalization`, the CLI polls until the on-chain lock is confirmed before signing the envelope (avoids the racy DELEGATION_PENDING_LOCK 409 that consumes sender_sequence)."
3574
3573
  ).option("--lock-wait-timeout <seconds>", "Max wall-clock seconds to wait for pending-lock pre-flight (default 300).", "300").option("--lock-wait-interval <seconds>", "Poll cadence for pending-lock pre-flight in seconds. Bound to [1, 60].", "3").action(async (relationshipId, delegationId, opts) => {
@@ -3588,7 +3587,7 @@ async function runFollowupAction(relationshipId, delegationId, action, opts) {
3588
3587
  const lockWaitTimeoutSec = parseLockWaitTimeout(cmdName, opts.lockWaitTimeout);
3589
3588
  const lockWaitIntervalSec = parseLockWaitInterval(cmdName, opts.lockWaitInterval);
3590
3589
  let declinePayload = null;
3591
- if (action === "decline") {
3590
+ if (action === import_sdk10.DelegationActions.DECLINE) {
3592
3591
  const reason = parseDeclineReason(cmdName, opts.reason);
3593
3592
  const validatedDetail = parseReasonDetail(cmdName, opts.reasonDetail);
3594
3593
  declinePayload = validatedDetail ? { reason, reasonDetail: validatedDetail } : { reason };
@@ -3597,7 +3596,7 @@ async function runFollowupAction(relationshipId, delegationId, action, opts) {
3597
3596
  const sender = resolveSenderAgent(cmdName, opts.server, opts.fromDid);
3598
3597
  const signer = makeSigner(sender);
3599
3598
  const resolved = await resolveDelegationRefs(cmdName, api, signer, { relationshipId, delegationId, action, selfDid: sender.did });
3600
- if (resolved.state === "pending_lock_finalization" && opts.waitForLock !== false) {
3599
+ if (resolved.state === import_sdk10.DelegationStates.PENDING_LOCK_FINALIZATION && opts.waitForLock !== false) {
3601
3600
  await awaitDelegationLockFinalized(cmdName, api, signer, {
3602
3601
  relationshipId,
3603
3602
  delegationId,
@@ -3622,7 +3621,7 @@ async function runFollowupAction(relationshipId, delegationId, action, opts) {
3622
3621
  progress(opts.json, import_chalk8.default.dim(`Server: ${api.serverUrl}`));
3623
3622
  progress(opts.json, import_chalk8.default.dim(`Sender: ${sender.did}`));
3624
3623
  progress(opts.json, import_chalk8.default.dim(`Relationship: ${relationshipId}`));
3625
- progress(opts.json, import_chalk8.default.dim(`Delegation: ${delegationId} (action=${action}${action === "decline" ? `, reason=${content.reason}` : ""})`));
3624
+ progress(opts.json, import_chalk8.default.dim(`Delegation: ${delegationId} (action=${action}${action === import_sdk10.DelegationActions.DECLINE ? `, reason=${content.reason}` : ""})`));
3626
3625
  const result = await sendDelegationEnvelope({
3627
3626
  api,
3628
3627
  sender,
@@ -3651,9 +3650,9 @@ async function runFollowupAction(relationshipId, delegationId, action, opts) {
3651
3650
  async function sendDelegationEnvelope(args) {
3652
3651
  const nextSequence = (args.sender.lastSenderSequence ?? 0) + 1;
3653
3652
  const protectedBlock = {
3654
- protocol_version: "arp/0.1",
3655
- purpose: import_sdk7.Purpose.ENVELOPE,
3656
- message_id: (0, import_sdk7.uuidV4)(),
3653
+ protocol_version: import_sdk10.CURRENT_PROTOCOL_VERSION,
3654
+ purpose: import_sdk10.Purpose.ENVELOPE,
3655
+ message_id: (0, import_sdk10.uuidV4)(),
3657
3656
  sender_did: args.sender.did,
3658
3657
  recipient_did: args.recipientDid,
3659
3658
  // `relationship_id: null` matches the handshake
@@ -3662,13 +3661,13 @@ async function sendDelegationEnvelope(args) {
3662
3661
  // existing relationship row.
3663
3662
  relationship_id: null,
3664
3663
  sender_sequence: nextSequence,
3665
- sender_nonce: (0, import_sdk7.senderNonce)(),
3666
- timestamp: (0, import_sdk7.rfc3339)(),
3667
- expires_at: (0, import_sdk7.expiresAt)(args.ttlSeconds),
3664
+ sender_nonce: (0, import_sdk10.senderNonce)(),
3665
+ timestamp: (0, import_sdk10.rfc3339)(),
3666
+ expires_at: (0, import_sdk10.expiresAt)(args.ttlSeconds),
3668
3667
  delivery_id: null
3669
3668
  };
3670
3669
  const signer = makeSigner(args.sender);
3671
- const envelope = (0, import_sdk7.signEnvelope)({
3670
+ const envelope = (0, import_sdk10.signEnvelope)({
3672
3671
  protected: protectedBlock,
3673
3672
  body: args.body,
3674
3673
  identitySecretKey: signer.identitySecretKey,
@@ -3683,7 +3682,7 @@ async function sendDelegationEnvelope(args) {
3683
3682
  updateAgentLocal(args.server, args.sender.did, { lastSenderSequence: nextSequence });
3684
3683
  return result;
3685
3684
  } catch (err) {
3686
- if (err instanceof ApiError && isPostCommitErrorCode(err.payload.code)) {
3685
+ if (err instanceof ApiError && (0, import_sdk10.isPostCommitErrorCode)(err.payload.code)) {
3687
3686
  updateAgentLocal(args.server, args.sender.did, { lastSenderSequence: nextSequence });
3688
3687
  }
3689
3688
  throw err;
@@ -3705,7 +3704,7 @@ async function resolveDelegationRefs(cmdName, api, signer, args) {
3705
3704
  );
3706
3705
  }
3707
3706
  let recipientDid;
3708
- if (args.action === "cancel") {
3707
+ if (args.action === import_sdk10.DelegationActions.CANCEL) {
3709
3708
  const firstEvents = await api.listEvents(args.relationshipId, signer, { since: 0, limit: 1 });
3710
3709
  const handshake = firstEvents[0];
3711
3710
  if (!handshake) {
@@ -3745,7 +3744,7 @@ async function awaitDelegationLockFinalized(cmdName, api, signer, args) {
3745
3744
  after = page[page.length - 1].id;
3746
3745
  }
3747
3746
  };
3748
- const outcome = await (0, import_sdk7.pollUntil)({
3747
+ const outcome = await (0, import_sdk10.pollUntil)({
3749
3748
  fetch: fetchRow,
3750
3749
  // Match on either "row not found at all" OR "state moved
3751
3750
  // past pending_lock_finalization". The post-poll branch
@@ -3755,7 +3754,7 @@ async function awaitDelegationLockFinalized(cmdName, api, signer, args) {
3755
3754
  // rows return cleanly. Polling on null would loop pointlessly
3756
3755
  // until the deadline fires and surface a misleading "timed
3757
3756
  // out" message for what's actually a wrong-id problem.
3758
- predicate: (row2) => row2 === null || row2.state !== "pending_lock_finalization",
3757
+ predicate: (row2) => row2 === null || row2.state !== import_sdk10.DelegationStates.PENDING_LOCK_FINALIZATION,
3759
3758
  intervalMs: args.intervalSec * 1e3,
3760
3759
  timeoutMs: args.timeoutSec * 1e3,
3761
3760
  // Swallow ONLY transient errors. A 4xx during the poll is a
@@ -3786,7 +3785,7 @@ async function awaitDelegationLockFinalized(cmdName, api, signer, args) {
3786
3785
  `${cmdName}: delegation ${args.delegationId} disappeared from relationship ${args.relationshipId} during --wait-for-lock pre-flight. Re-check the delegation id with \`heyarp delegations ${args.relationshipId}\`.`
3787
3786
  );
3788
3787
  }
3789
- if (row.state !== "offered") {
3788
+ if (row.state !== import_sdk10.DelegationStates.OFFERED) {
3790
3789
  throw new Error(
3791
3790
  `${cmdName}: delegation ${args.delegationId} transitioned to '${row.state}' while waiting for on-chain lock confirmation; cannot ${args.action}. Re-read the delegation timeline with \`heyarp delegations ${args.relationshipId}\` to see the latest state.`
3792
3791
  );
@@ -3846,7 +3845,7 @@ function parseOfferTerms(cmdName, opts) {
3846
3845
  if (opts.amount) {
3847
3846
  out.amount = opts.amount;
3848
3847
  if (!opts.currency) {
3849
- throw new Error(`${cmdName}: --amount requires --currency. Shorthand: ${import_sdk7.WELL_KNOWN_ASSET_KEYS.join(", ")}, or raw CAIP-19 + --currency-decimals.`);
3848
+ throw new Error(`${cmdName}: --amount requires --currency. Shorthand: ${import_sdk10.WELL_KNOWN_ASSET_KEYS.join(", ")}, or raw CAIP-19 + --currency-decimals.`);
3850
3849
  }
3851
3850
  out.currency = buildAssetIdentifier(cmdName, DELEGATION_CURRENCY_FLAGS, opts.currency, opts.currencyDecimals, opts.currencySymbol);
3852
3851
  } else if (opts.currency) {
@@ -3869,7 +3868,7 @@ function resolveOfferDelegationId(rawCliId, escrow) {
3869
3868
  cliId = rawCliId.toLowerCase();
3870
3869
  }
3871
3870
  if (escrow === void 0) {
3872
- return cliId ?? (0, import_sdk7.uuidV4)();
3871
+ return cliId ?? (0, import_sdk10.uuidV4)();
3873
3872
  }
3874
3873
  if (escrow.delegationIdFromLock !== void 0) {
3875
3874
  const fileId = escrow.delegationIdFromLock;
@@ -3904,10 +3903,10 @@ function collectRepeated(value, previous) {
3904
3903
  }
3905
3904
  function parseDeclineReason(cmdName, raw) {
3906
3905
  if (raw === void 0 || raw === "") {
3907
- throw new Error(`${cmdName}: --reason is required when declining (one of: ${import_sdk7.DECLINE_REASONS.join(", ")})`);
3906
+ throw new Error(`${cmdName}: --reason is required when declining (one of: ${import_sdk10.DECLINE_REASONS.join(", ")})`);
3908
3907
  }
3909
- if (!(0, import_sdk7.isDeclineReason)(raw)) {
3910
- throw new Error(`${cmdName}: --reason must be one of ${import_sdk7.DECLINE_REASONS.join(", ")} (got '${raw}')`);
3908
+ if (!(0, import_sdk10.isDeclineReason)(raw)) {
3909
+ throw new Error(`${cmdName}: --reason must be one of ${import_sdk10.DECLINE_REASONS.join(", ")} (got '${raw}')`);
3911
3910
  }
3912
3911
  return raw;
3913
3912
  }
@@ -3922,10 +3921,10 @@ function parseReasonDetail(cmdName, raw) {
3922
3921
  return raw;
3923
3922
  }
3924
3923
  function buildAssetIdentifier(cmdName, labels, rawCurrency, rawDecimals, rawSymbol) {
3925
- const resolved = (0, import_sdk7.resolveAsset)(rawCurrency);
3924
+ const resolved = (0, import_sdk10.resolveAsset)(rawCurrency);
3926
3925
  if (!resolved) {
3927
3926
  throw new Error(
3928
- `${cmdName}: ${labels.currencyFlag} '${rawCurrency}' is not a known shorthand or a valid CAIP-19 string. Shorthand: ${import_sdk7.WELL_KNOWN_ASSET_KEYS.join(", ")}. Or pass a raw CAIP-19 id (e.g. "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/spl:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v") with ${labels.decimalsFlag}.`
3927
+ `${cmdName}: ${labels.currencyFlag} '${rawCurrency}' is not a known shorthand or a valid CAIP-19 string. Shorthand: ${import_sdk10.WELL_KNOWN_ASSET_KEYS.join(", ")}. Or pass a raw CAIP-19 id (e.g. "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/spl:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v") with ${labels.decimalsFlag}.`
3929
3928
  );
3930
3929
  }
3931
3930
  let decimals = resolved.decimals;
@@ -3934,20 +3933,20 @@ function buildAssetIdentifier(cmdName, labels, rawCurrency, rawDecimals, rawSymb
3934
3933
  throw new Error(`${cmdName}: ${labels.currencyFlag} is a raw CAIP-19 string; ${labels.decimalsFlag} (0-18) is required to convert amounts to base units.`);
3935
3934
  }
3936
3935
  const parsed = Number(rawDecimals);
3937
- if (!Number.isInteger(parsed) || parsed < 0 || parsed > 18) {
3936
+ if (!Number.isInteger(parsed) || parsed < import_sdk10.ASSET_DECIMALS_MIN || parsed > import_sdk10.ASSET_DECIMALS_MAX) {
3938
3937
  throw new Error(`${cmdName}: ${labels.decimalsFlag} must be an integer in [0, 18] (got '${rawDecimals}').`);
3939
3938
  }
3940
3939
  decimals = parsed;
3941
3940
  } else if (rawDecimals !== void 0) {
3942
3941
  const parsed = Number(rawDecimals);
3943
- if (!Number.isInteger(parsed) || parsed < 0 || parsed > 18) {
3942
+ if (!Number.isInteger(parsed) || parsed < import_sdk10.ASSET_DECIMALS_MIN || parsed > import_sdk10.ASSET_DECIMALS_MAX) {
3944
3943
  throw new Error(`${cmdName}: ${labels.decimalsFlag} must be an integer in [0, 18] (got '${rawDecimals}').`);
3945
3944
  }
3946
3945
  decimals = parsed;
3947
3946
  }
3948
3947
  let symbol = resolved.symbol;
3949
3948
  if (rawSymbol !== void 0) {
3950
- if (rawSymbol.length === 0 || rawSymbol.length > 16) {
3949
+ if (rawSymbol.length < import_sdk10.ASSET_SYMBOL_MIN_LEN || rawSymbol.length > import_sdk10.ASSET_SYMBOL_MAX_LEN) {
3951
3950
  throw new Error(`${cmdName}: ${labels.symbolFlag} must be 1-16 chars (got length ${rawSymbol.length}).`);
3952
3951
  }
3953
3952
  symbol = rawSymbol;
@@ -3961,9 +3960,10 @@ var DELEGATION_CURRENCY_FLAGS = {
3961
3960
  };
3962
3961
 
3963
3962
  // src/commands/delegations.ts
3963
+ var import_sdk11 = require("@heyanon-arp/sdk");
3964
3964
  var import_chalk9 = __toESM(require("chalk"));
3965
3965
  init_api();
3966
- var ALLOWED_STATES = /* @__PURE__ */ new Set(["offered", "accepted", "declined", "canceled"]);
3966
+ var ALLOWED_STATES = /* @__PURE__ */ new Set([import_sdk11.DelegationStates.OFFERED, import_sdk11.DelegationStates.ACCEPTED, import_sdk11.DelegationStates.DECLINED, import_sdk11.DelegationStates.CANCELED]);
3967
3967
  function registerDelegationsCommand(root) {
3968
3968
  root.command("delegations").description("List delegations for a relationship (one row per delegationId, oldest-first)").argument("<relationship-id>", "Relationship UUID").option("--server <url>", "Override ARP server base URL").option("--state <s>", "Filter by exact state (offered|accepted|declined|canceled)").option("--after <id>", "Cursor: pass the previous page's last `id` to fetch the next page").option("--limit <n>", "Max rows to return (1..100)", "20").option("--from-did <did>", "Signer DID \u2014 required only if multiple agents are registered against this server").option(
3969
3969
  "--verbose",
@@ -4022,7 +4022,7 @@ function formatDelegationLine(d, selfDid, opts = {}) {
4022
4022
  const amount = formatAmount(d);
4023
4023
  const title = d.title ? import_chalk9.default.dim(`"${truncate2(d.title, 40)}"`) : import_chalk9.default.dim("(no title)");
4024
4024
  let declineSuffix = "";
4025
- if (d.state === "declined" && d.declineReason) {
4025
+ if (d.state === import_sdk11.DelegationStates.DECLINED && d.declineReason) {
4026
4026
  const detail = d.declineReasonDetail ? `: ${truncate2(d.declineReasonDetail, 40)}` : "";
4027
4027
  declineSuffix = ` ${import_chalk9.default.dim(`(reason: ${d.declineReason}${detail})`)}`;
4028
4028
  }
@@ -4037,34 +4037,34 @@ function colorState(s) {
4037
4037
  // returns `undefined` and `formatDelegationLine` crashes with
4038
4038
  // `TypeError: Cannot read properties of undefined (reading
4039
4039
  // 'padEnd')` on any listing containing such a row.
4040
- case "offered":
4040
+ case import_sdk11.DelegationStates.OFFERED:
4041
4041
  return import_chalk9.default.yellow("offered");
4042
- case "pending_lock_finalization":
4042
+ case import_sdk11.DelegationStates.PENDING_LOCK_FINALIZATION:
4043
4043
  return import_chalk9.default.yellow("pending_lock");
4044
- case "accepted":
4044
+ case import_sdk11.DelegationStates.ACCEPTED:
4045
4045
  return import_chalk9.default.green("accepted");
4046
4046
  // The on-chain escrow lock is confirmed. Distinct branch so a
4047
4047
  // LOCKED row renders without hitting the defensive fallback
4048
4048
  // (which would otherwise echo the raw state).
4049
- case "locked":
4049
+ case import_sdk11.DelegationStates.LOCKED:
4050
4050
  return import_chalk9.default.green("locked");
4051
- case "declined":
4051
+ case import_sdk11.DelegationStates.DECLINED:
4052
4052
  return import_chalk9.default.red("declined");
4053
- case "canceled":
4053
+ case import_sdk11.DelegationStates.CANCELED:
4054
4054
  return import_chalk9.default.dim("canceled");
4055
4055
  // Terminal escrow outcomes: completed = payee paid;
4056
4056
  // failed = create_lock never landed; refunded = funds returned
4057
4057
  // to the buyer (work expired / dispute closed);
4058
4058
  // dispute_resolved = operator ruled.
4059
- case "completed":
4059
+ case import_sdk11.DelegationStates.COMPLETED:
4060
4060
  return import_chalk9.default.green("completed");
4061
- case "failed":
4061
+ case import_sdk11.DelegationStates.FAILED:
4062
4062
  return import_chalk9.default.red("failed");
4063
- case "refunded":
4063
+ case import_sdk11.DelegationStates.REFUNDED:
4064
4064
  return import_chalk9.default.red("refunded");
4065
- case "disputing":
4065
+ case import_sdk11.DelegationStates.DISPUTING:
4066
4066
  return import_chalk9.default.yellow("disputing");
4067
- case "dispute_resolved":
4067
+ case import_sdk11.DelegationStates.DISPUTE_RESOLVED:
4068
4068
  return import_chalk9.default.red("dispute_resolved");
4069
4069
  default: {
4070
4070
  const _exhaustive = s;
@@ -4110,7 +4110,7 @@ function parseLimit2(raw) {
4110
4110
  }
4111
4111
 
4112
4112
  // src/commands/did-doc.ts
4113
- var import_sdk8 = require("@heyanon-arp/sdk");
4113
+ var import_sdk12 = require("@heyanon-arp/sdk");
4114
4114
  init_api();
4115
4115
  function registerDidDocCommand(root) {
4116
4116
  root.command("did-doc").description(
@@ -4119,7 +4119,7 @@ function registerDidDocCommand(root) {
4119
4119
  "--field <path>",
4120
4120
  "Extract a single value via a dotted path. Supports object property access (`id`), numeric array indexes (`verificationMethod.0`), and `#fragment` array selectors that match a verification-method or service entry by its `id` suffix (`verificationMethod.#settlement.publicKeyMultibase`). Two well-known shortcuts skip the path entirely AND strip the multibase prefix: `settlementPublicKey` / `identityPublicKey` emit the raw base58 pubkey ready for flags like `--recipient-pubkey`. Scalar values (string / number / boolean) emit raw to stdout for shell composition (`KEY=$(heyarp did-doc ... --field ...)`); objects / arrays emit as pretty-printed JSON. Mutually exclusive with `--json` since `--field` already controls the shape."
4121
4121
  ).action(async (did, opts) => {
4122
- if (!(0, import_sdk8.isValidDid)(did)) {
4122
+ if (!(0, import_sdk12.isValidDid)(did)) {
4123
4123
  throw new Error(`'${did}' is not a syntactically valid did:arp identifier`);
4124
4124
  }
4125
4125
  if (opts.json && opts.field !== void 0) {
@@ -4231,7 +4231,7 @@ function describeShape(value) {
4231
4231
  }
4232
4232
 
4233
4233
  // src/commands/doctor.ts
4234
- var import_sdk9 = require("@heyanon-arp/sdk");
4234
+ var import_sdk13 = require("@heyanon-arp/sdk");
4235
4235
  var import_chalk10 = __toESM(require("chalk"));
4236
4236
  init_api();
4237
4237
  function registerDoctorCommand(root) {
@@ -4241,7 +4241,7 @@ function registerDoctorCommand(root) {
4241
4241
  }
4242
4242
  var LISTENING_THRESHOLD_SECONDS = 15 * 60;
4243
4243
  async function runDoctor(did, opts) {
4244
- if (!(0, import_sdk9.isValidDid)(did)) {
4244
+ if (!(0, import_sdk13.isValidDid)(did)) {
4245
4245
  throw new Error(`'${did}' is not a syntactically valid did:arp identifier`);
4246
4246
  }
4247
4247
  const api = new ArpApiClient(opts.server);
@@ -4394,14 +4394,14 @@ function formatHints(event) {
4394
4394
  }
4395
4395
 
4396
4396
  // src/commands/escrow.ts
4397
- var import_sdk11 = require("@heyanon-arp/sdk");
4397
+ var import_sdk15 = require("@heyanon-arp/sdk");
4398
4398
  var import_utils2 = require("@noble/hashes/utils");
4399
4399
  var import_chalk12 = __toESM(require("chalk"));
4400
4400
  init_api();
4401
4401
 
4402
4402
  // src/commands/escrow-actions.ts
4403
4403
  var import_node_crypto = require("crypto");
4404
- var import_sdk10 = require("@heyanon-arp/sdk");
4404
+ var import_sdk14 = require("@heyanon-arp/sdk");
4405
4405
  var import_web33 = require("@solana/web3.js");
4406
4406
  init_api();
4407
4407
  var FEE_BUFFER_LAMPORTS = 10000000n;
@@ -4415,7 +4415,7 @@ async function setup(cmd, delegationIdArg, opts) {
4415
4415
  const delegationId = normaliseDelegationId(delegationIdArg);
4416
4416
  const fetched = await fetchLockAccount(conn, programId, delegationId);
4417
4417
  if (!fetched) {
4418
- const lockPda = deriveLockPda(programId, (0, import_sdk10.deriveLockId)(delegationId)).toBase58();
4418
+ const lockPda = deriveLockPda(programId, (0, import_sdk14.deriveLockId)(delegationId)).toBase58();
4419
4419
  throw new Error(
4420
4420
  `${cmd}: no on-chain Lock for delegation ${delegationId} (PDA ${lockPda} not found on ${rpcUrl}). Either the buyer hasn't funded yet (delegation fund \u2192 server relays create_lock), or you're pointing at the wrong cluster/program.`
4421
4421
  );
@@ -4440,7 +4440,7 @@ function nowSecs() {
4440
4440
  function deadlinePassed(ctx) {
4441
4441
  return ctx.lock.expiry > 0n && nowSecs() >= ctx.lock.expiry;
4442
4442
  }
4443
- var SYSTEM_PROGRAM_B58 = "11111111111111111111111111111111";
4443
+ var SYSTEM_PROGRAM_B58 = import_sdk14.SYSTEM_PROGRAM_ID_BASE58;
4444
4444
  function effectiveFeeRecipient(lock) {
4445
4445
  if (lock.feeRecipientAtLock !== SYSTEM_PROGRAM_B58) return new import_web33.PublicKey(lock.feeRecipientAtLock);
4446
4446
  if (lock.treasuryAtLock && lock.treasuryAtLock !== SYSTEM_PROGRAM_B58) return new import_web33.PublicKey(lock.treasuryAtLock);
@@ -4502,7 +4502,7 @@ function emit(ctx, action, signature, extra = {}) {
4502
4502
  }
4503
4503
  async function acceptHandler(delegationId, opts) {
4504
4504
  const ctx = await setup("escrow accept", delegationId, opts);
4505
- requireState(ctx, ["created"], "Accept is only possible before any other transition; if it shows in_progress you already accepted.");
4505
+ requireState(ctx, [import_sdk14.LockStates.CREATED], "Accept is only possible before any other transition; if it shows in_progress you already accepted.");
4506
4506
  requireSigner(ctx, "payee", ctx.lock.payee);
4507
4507
  const stake = ctx.lock.workerStakeAtLock;
4508
4508
  await preflightLamports(ctx, stake, `the worker stake (${stake} lamports, returned when the lock settles in your favour)`);
@@ -4512,7 +4512,7 @@ async function acceptHandler(delegationId, opts) {
4512
4512
  }
4513
4513
  async function submitWorkHandler(delegationId, opts) {
4514
4514
  const ctx = await setup("escrow submit-work", delegationId, opts);
4515
- requireState(ctx, ["in_progress"], "Submit needs an accepted lock ('created' \u2192 run `heyarp escrow accept` first; terminal \u2192 the cycle already ended).");
4515
+ requireState(ctx, [import_sdk14.LockStates.IN_PROGRESS], "Submit needs an accepted lock ('created' \u2192 run `heyarp escrow accept` first; terminal \u2192 the cycle already ended).");
4516
4516
  requireSigner(ctx, "payee", ctx.lock.payee);
4517
4517
  if (deadlinePassed(ctx)) {
4518
4518
  throw new Error(
@@ -4523,7 +4523,7 @@ async function submitWorkHandler(delegationId, opts) {
4523
4523
  let reviewDeadline = null;
4524
4524
  try {
4525
4525
  const fresh = await fetchLockAccount(ctx.conn, ctx.programId, ctx.delegationId);
4526
- if (fresh && fresh.lock.state === "submitted" && fresh.lock.expiry > 0n) {
4526
+ if (fresh && fresh.lock.state === import_sdk14.LockStates.SUBMITTED && fresh.lock.expiry > 0n) {
4527
4527
  reviewDeadline = new Date(Number(fresh.lock.expiry) * 1e3).toISOString();
4528
4528
  }
4529
4529
  } catch {
@@ -4536,22 +4536,25 @@ async function submitWorkHandler(delegationId, opts) {
4536
4536
  }
4537
4537
  async function claimHandler(delegationId, opts) {
4538
4538
  const ctx = await setup("escrow claim", delegationId, opts);
4539
- requireState(ctx, ["submitted"], "Claim needs submitted work ('in_progress' \u2192 the worker must `escrow submit-work` first; 'paid' \u2192 already claimed).");
4539
+ requireState(ctx, [import_sdk14.LockStates.SUBMITTED], "Claim needs submitted work ('in_progress' \u2192 the worker must `escrow submit-work` first; 'paid' \u2192 already claimed).");
4540
4540
  const me = ctx.keypair.publicKey.toBase58();
4541
4541
  let role;
4542
4542
  if (me === ctx.lock.payer) {
4543
- role = "buyer_approved";
4543
+ role = import_sdk14.EscrowReleaseMethods.BUYER_APPROVED;
4544
4544
  } else if (me === ctx.lock.payee) {
4545
4545
  if (!deadlinePassed(ctx)) {
4546
4546
  throw new Error(
4547
4547
  `escrow claim: the review window is still open (until ${expiryIso(ctx)}). As the worker you can self-claim only AFTER it lapses; before that the buyer must approve (or dispute).`
4548
4548
  );
4549
4549
  }
4550
- role = "review_timeout";
4550
+ role = import_sdk14.EscrowReleaseMethods.REVIEW_TIMEOUT;
4551
4551
  } else {
4552
4552
  throw new Error(`escrow claim: your settlement key ${me} is neither the payer (${ctx.lock.payer}) nor the payee (${ctx.lock.payee}) of this lock.`);
4553
4553
  }
4554
- progress(ctx.opts.json, role === "buyer_approved" ? "approving payment as the buyer \u2014 this RELEASES the escrow and is irreversible" : "self-claiming after review timeout");
4554
+ progress(
4555
+ ctx.opts.json,
4556
+ role === import_sdk14.EscrowReleaseMethods.BUYER_APPROVED ? "approving payment as the buyer \u2014 this RELEASES the escrow and is irreversible" : "self-claiming after review timeout"
4557
+ );
4555
4558
  const sig = await sendIx(
4556
4559
  ctx,
4557
4560
  buildClaimWorkPaymentIx({
@@ -4568,7 +4571,7 @@ async function claimHandler(delegationId, opts) {
4568
4571
  }
4569
4572
  async function cancelHandler(delegationId, opts) {
4570
4573
  const ctx = await setup("escrow cancel", delegationId, opts);
4571
- requireState(ctx, ["created"], "Cancel is the pre-accept exit only \u2014 once the worker accepted ('in_progress') the windows model governs.");
4574
+ requireState(ctx, [import_sdk14.LockStates.CREATED], "Cancel is the pre-accept exit only \u2014 once the worker accepted ('in_progress') the windows model governs.");
4572
4575
  requireSigner(ctx, "payer", ctx.lock.payer);
4573
4576
  const sig = await sendIx(
4574
4577
  ctx,
@@ -4583,7 +4586,7 @@ async function cancelHandler(delegationId, opts) {
4583
4586
  }
4584
4587
  async function claimExpiredHandler(delegationId, opts) {
4585
4588
  const ctx = await setup("escrow claim-expired", delegationId, opts);
4586
- requireState(ctx, ["in_progress"], "Claim-expired recovers an accepted-but-never-submitted lock; 'submitted' work goes through claim/dispute instead.");
4589
+ requireState(ctx, [import_sdk14.LockStates.IN_PROGRESS], "Claim-expired recovers an accepted-but-never-submitted lock; 'submitted' work goes through claim/dispute instead.");
4587
4590
  requireSigner(ctx, "payer", ctx.lock.payer);
4588
4591
  if (!deadlinePassed(ctx)) {
4589
4592
  throw new Error(`escrow claim-expired: the work window is still open (until ${expiryIso(ctx)}). The worker can still submit; the chain will reject this claim until then.`);
@@ -4601,7 +4604,7 @@ async function claimExpiredHandler(delegationId, opts) {
4601
4604
  }
4602
4605
  async function disputeOpenHandler(delegationId, opts) {
4603
4606
  const ctx = await setup("escrow dispute open", delegationId, opts);
4604
- requireState(ctx, ["submitted"], "Disputes open against SUBMITTED work, inside the review window.");
4607
+ requireState(ctx, [import_sdk14.LockStates.SUBMITTED], "Disputes open against SUBMITTED work, inside the review window.");
4605
4608
  requireSigner(ctx, "payer", ctx.lock.payer);
4606
4609
  if (deadlinePassed(ctx)) {
4607
4610
  throw new Error(
@@ -4616,7 +4619,7 @@ async function disputeOpenHandler(delegationId, opts) {
4616
4619
  }
4617
4620
  async function disputeCloseHandler(delegationId, opts) {
4618
4621
  const ctx = await setup("escrow dispute close", delegationId, opts);
4619
- requireState(ctx, ["disputing"], "Close only applies to an OPEN dispute that the operator never resolved.");
4622
+ requireState(ctx, [import_sdk14.LockStates.DISPUTING], "Close only applies to an OPEN dispute that the operator never resolved.");
4620
4623
  const me = ctx.keypair.publicKey.toBase58();
4621
4624
  if (me !== ctx.lock.payer && me !== ctx.lock.payee) {
4622
4625
  throw new Error(`escrow dispute close: your settlement key ${me} is neither party of this lock.`);
@@ -4641,7 +4644,7 @@ async function disputeCloseHandler(delegationId, opts) {
4641
4644
  }
4642
4645
  async function disputeResolveHandler(delegationId, opts) {
4643
4646
  const ctx = await setup("escrow dispute resolve", delegationId, opts);
4644
- requireState(ctx, ["disputing"], "Resolve only applies to an OPEN dispute, inside the dispute window.");
4647
+ requireState(ctx, [import_sdk14.LockStates.DISPUTING], "Resolve only applies to an OPEN dispute, inside the dispute window.");
4645
4648
  if (deadlinePassed(ctx)) {
4646
4649
  throw new Error(
4647
4650
  `escrow dispute resolve: the dispute window lapsed at ${expiryIso(ctx)} \u2014 the chain rejects late rulings (LockExpired); either party can now \`escrow dispute close\` instead.`
@@ -4685,7 +4688,7 @@ async function showHandler(delegationId, opts) {
4685
4688
  jsonOut({
4686
4689
  delegation_id: normalised.slice("del_".length),
4687
4690
  lock_exists: false,
4688
- lock_pda: deriveLockPda(programId, (0, import_sdk10.deriveLockId)(normalised)).toBase58(),
4691
+ lock_pda: deriveLockPda(programId, (0, import_sdk14.deriveLockId)(normalised)).toBase58(),
4689
4692
  rpc_url: redactRpcUrl(rpcUrl)
4690
4693
  });
4691
4694
  return;
@@ -4762,7 +4765,7 @@ function registerEscrowCommands(root) {
4762
4765
  "Compute canonical condition_hash (32-byte hex) for `heyarp wallet create-lock --condition-hash <hex>` from the DELEGATION terms. Projects the flags to the canonical subset and hashes via SDK deriveDelegationConditionHash \u2014 no server fetch. The SAME terms MUST go into `delegation offer` or the server rejects the lock (ESC_LOCK_CONDITION_HASH_MISMATCH)."
4763
4766
  ).requiredOption("--delegation-id <id>", "Delegation UUID this lock binds (drives the on-chain lock_id + the condition_hash identity binding)").requiredOption("--scope <text>", "scope_summary \u2014 short prose describing the agreed work").option(
4764
4767
  "--currency <s>",
4765
- `Asset identifier bound into the hash: shorthand (${import_sdk11.WELL_KNOWN_ASSET_KEYS.join("|")}) OR raw CAIP-19 string. Optional but must match the offer's --currency.`
4768
+ `Asset identifier bound into the hash: shorthand (${import_sdk15.WELL_KNOWN_ASSET_KEYS.join("|")}) OR raw CAIP-19 string. Optional but must match the offer's --currency.`
4766
4769
  ).option("--currency-decimals <n>", "Decimal places (0-18). Required only when --currency is raw CAIP-19.").option("--currency-symbol <s>", 'Optional UI hint ("USDC", "SOL"). Max 16 chars.').option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Sender DID \u2014 required only if multiple agents are registered against this server").option("--json", "Machine-readable JSON: {delegation_id, condition_hash_hex, projected_subset}", false).action(async (opts) => {
4767
4770
  await runDeriveConditionHash(opts);
4768
4771
  });
@@ -4841,7 +4844,7 @@ async function runDeriveConditionHash(opts) {
4841
4844
  progress(opts.json, import_chalk12.default.dim(`Server: ${api.serverUrl}`));
4842
4845
  progress(opts.json, import_chalk12.default.dim(`Signer: ${sender.did}`));
4843
4846
  const subset = projectDelegationForHash(cmdName, opts, delegationId);
4844
- const hashBytes = (0, import_sdk11.deriveDelegationConditionHash)(subset);
4847
+ const hashBytes = (0, import_sdk15.deriveDelegationConditionHash)(subset);
4845
4848
  const hex = (0, import_utils2.bytesToHex)(hashBytes);
4846
4849
  if (opts.json) {
4847
4850
  jsonOut({
@@ -4923,6 +4926,7 @@ async function runRecoverSequence(opts) {
4923
4926
  }
4924
4927
 
4925
4928
  // src/commands/events.ts
4929
+ var import_sdk16 = require("@heyanon-arp/sdk");
4926
4930
  var import_chalk13 = __toESM(require("chalk"));
4927
4931
  init_api();
4928
4932
  function registerEventsCommand(root) {
@@ -4973,8 +4977,8 @@ async function runEvents(relationshipId, opts) {
4973
4977
  }
4974
4978
  const query = { limit };
4975
4979
  if (since !== void 0) query.since = since;
4976
- if (opts.successOnly) query.readModelStatus = "materialized";
4977
- else if (opts.rejectedOnly) query.readModelStatus = "rejected";
4980
+ if (opts.successOnly) query.readModelStatus = import_sdk16.ReadModelStatuses.MATERIALIZED;
4981
+ else if (opts.rejectedOnly) query.readModelStatus = import_sdk16.ReadModelStatuses.REJECTED;
4978
4982
  const signer = makeSigner(sender);
4979
4983
  const events = await api.listEvents(relationshipId, signer, query);
4980
4984
  if (opts.json) {
@@ -5009,8 +5013,8 @@ function formatEventLine(ev, selfDid, opts = {}) {
5009
5013
  const hash = opts.fullIds ? ev.serverEventHash : hashHead(ev.serverEventHash);
5010
5014
  const extra = extraDetail(ev);
5011
5015
  const tail = extra ? ` ${import_chalk13.default.dim(`(${extra})`)}` : "";
5012
- const status = ev.readModelStatus ?? "materialized";
5013
- const statusGlyph = status === "rejected" ? `${import_chalk13.default.red("\u2717")} ` : " ";
5016
+ const status = ev.readModelStatus ?? import_sdk16.ReadModelStatuses.MATERIALIZED;
5017
+ const statusGlyph = status === import_sdk16.ReadModelStatuses.REJECTED ? `${import_chalk13.default.red("\u2717")} ` : " ";
5014
5018
  return `${statusGlyph}${idx} ${import_chalk13.default.cyan(eventId)} ${type} ${direction} ${import_chalk13.default.cyan(hash)}${tail}`;
5015
5019
  }
5016
5020
  function eventIdHead(eventId) {
@@ -6185,7 +6189,7 @@ function parseLimit4(raw) {
6185
6189
  // src/commands/keys.ts
6186
6190
  var import_node_crypto2 = require("crypto");
6187
6191
  var import_node_fs7 = require("fs");
6188
- var import_sdk12 = require("@heyanon-arp/sdk");
6192
+ var import_sdk17 = require("@heyanon-arp/sdk");
6189
6193
  var import_chalk17 = __toESM(require("chalk"));
6190
6194
  function writeSecretFile(path, body) {
6191
6195
  const tmp = `${path}.tmp.${(0, import_node_crypto2.randomBytes)(8).toString("hex")}`;
@@ -6213,19 +6217,19 @@ function writeSecretFile(path, body) {
6213
6217
  function registerKeysCommand(root) {
6214
6218
  const keys = root.command("keys").description("Local key utilities");
6215
6219
  keys.command("gen").description("Generate a fresh identity + settlement keypair (no save by default)").action(() => {
6216
- const identity = (0, import_sdk12.generateKeyPair)();
6217
- const settlement = (0, import_sdk12.generateKeyPair)();
6220
+ const identity = (0, import_sdk17.generateKeyPair)();
6221
+ const settlement = (0, import_sdk17.generateKeyPair)();
6218
6222
  const out = [
6219
6223
  import_chalk17.default.bold("Identity key (Ed25519)"),
6220
- ` public (base58btc): ${import_chalk17.default.cyan((0, import_sdk12.base58btcEncode)(identity.publicKey))}`,
6224
+ ` public (base58btc): ${import_chalk17.default.cyan((0, import_sdk17.base58btcEncode)(identity.publicKey))}`,
6221
6225
  ` secret (base64) : ${import_chalk17.default.yellow(Buffer.from(identity.secretKey).toString("base64"))}`,
6222
6226
  "",
6223
6227
  import_chalk17.default.bold("Settlement key (Ed25519)"),
6224
- ` public (base58btc): ${import_chalk17.default.cyan((0, import_sdk12.base58btcEncode)(settlement.publicKey))}`,
6228
+ ` public (base58btc): ${import_chalk17.default.cyan((0, import_sdk17.base58btcEncode)(settlement.publicKey))}`,
6225
6229
  ` secret (base64) : ${import_chalk17.default.yellow(Buffer.from(settlement.secretKey).toString("base64"))}`,
6226
6230
  "",
6227
6231
  import_chalk17.default.bold("Resulting DID"),
6228
- ` ${import_chalk17.default.cyan((0, import_sdk12.formatDid)(identity.publicKey))}`
6232
+ ` ${import_chalk17.default.cyan((0, import_sdk17.formatDid)(identity.publicKey))}`
6229
6233
  ];
6230
6234
  console.log(out.join("\n"));
6231
6235
  });
@@ -6244,10 +6248,10 @@ function registerKeysCommand(root) {
6244
6248
  });
6245
6249
  keys.command("whoami").description("Print the DID derived from a base64-encoded identity secret key").argument("<secret-key-b64>", "32-byte Ed25519 seed, base64").action((secretKeyB64) => {
6246
6250
  const seed = decodeSeed(secretKeyB64);
6247
- const pub = (0, import_sdk12.getPublicKey)(seed);
6248
- const did = (0, import_sdk12.formatDid)(pub);
6251
+ const pub = (0, import_sdk17.getPublicKey)(seed);
6252
+ const did = (0, import_sdk17.formatDid)(pub);
6249
6253
  console.log(`${import_chalk17.default.bold("DID")}: ${import_chalk17.default.cyan(did)}`);
6250
- console.log(`${import_chalk17.default.bold("Identity public key (base58btc)")}: ${import_chalk17.default.cyan((0, import_sdk12.base58btcEncode)(pub))}`);
6254
+ console.log(`${import_chalk17.default.bold("Identity public key (base58btc)")}: ${import_chalk17.default.cyan((0, import_sdk17.base58btcEncode)(pub))}`);
6251
6255
  });
6252
6256
  }
6253
6257
  function decodeSeed(b64) {
@@ -6266,8 +6270,12 @@ function decodeSeed(b64) {
6266
6270
  // src/commands/list.ts
6267
6271
  var import_chalk18 = __toESM(require("chalk"));
6268
6272
  function registerListCommand(root) {
6269
- root.command("list").description("List agents registered locally (~/.heyarp/agents.json)").action(() => {
6273
+ root.command("list").description("List agents registered locally (~/.heyarp/agents.json)").option("--json", "Machine-readable mode \u2014 emit the local agents as a single JSON array on stdout ({serverUrl, did, name, tags, registeredAt}[]).", false).action((opts) => {
6270
6274
  const rows = listAgents();
6275
+ if (opts.json) {
6276
+ jsonOut(rows.map(({ serverUrl, agent }) => ({ serverUrl, did: agent.did, name: agent.name ?? null, tags: agent.tags ?? [], registeredAt: agent.registeredAt })));
6277
+ return;
6278
+ }
6271
6279
  if (rows.length === 0) {
6272
6280
  console.log(import_chalk18.default.dim(`No local agents. State file: ${stateFilePath()}`));
6273
6281
  return;
@@ -6406,12 +6414,12 @@ function registerLogoutCommand(root) {
6406
6414
  }
6407
6415
 
6408
6416
  // src/commands/profile.ts
6409
- var import_sdk13 = require("@heyanon-arp/sdk");
6417
+ var import_sdk18 = require("@heyanon-arp/sdk");
6410
6418
  var import_chalk21 = __toESM(require("chalk"));
6411
6419
  init_api();
6412
6420
  function registerProfileCommand(root) {
6413
6421
  root.command("profile").description("Composed agent profile: public fields + reputation + liveness, in one read.").argument("<did>", "did:arp:<base58btc> identifier").option("--server <url>", "Override ARP server base URL").option("--json", "Emit the profile as JSON on stdout.", false).action(async (did, opts) => {
6414
- if (!(0, import_sdk13.isValidDid)(did)) {
6422
+ if (!(0, import_sdk18.isValidDid)(did)) {
6415
6423
  throw new Error(`'${did}' is not a syntactically valid did:arp identifier`);
6416
6424
  }
6417
6425
  const api = new ArpApiClient(opts.server);
@@ -6439,7 +6447,7 @@ function registerProfileCommand(root) {
6439
6447
  }
6440
6448
 
6441
6449
  // src/commands/receipt.ts
6442
- var import_sdk14 = require("@heyanon-arp/sdk");
6450
+ var import_sdk19 = require("@heyanon-arp/sdk");
6443
6451
  var import_shield2 = require("@heyanon-arp/shield");
6444
6452
  var import_chalk22 = __toESM(require("chalk"));
6445
6453
  init_api();
@@ -6447,35 +6455,6 @@ function registerReceiptCommands(root) {
6447
6455
  const cmd = root.command("receipt").description("Receipt envelopes \u2014 the payee proposes a delivery record (payment consent is on-chain via claim_work_payment)");
6448
6456
  registerPropose(cmd);
6449
6457
  }
6450
- var POST_COMMIT_ERROR_CODES2 = /* @__PURE__ */ new Set([
6451
- "RECEIPT_ALREADY_EXISTS",
6452
- "RECEIPT_DELEGATION_NOT_FOUND",
6453
- "RECEIPT_DELEGATION_NOT_ACTIVE",
6454
- "RECEIPT_RELATIONSHIP_MISMATCH",
6455
- "RECEIPT_ISSUER_IS_CALLER",
6456
- "RECEIPT_NOT_FOUND",
6457
- "RECEIPT_INVALID_STATE",
6458
- // The receipt-propose handler's LOCKED gate emits
6459
- // `DELEGATION_PENDING_LOCK` (409) when the delegation is funded but
6460
- // the on-chain lock isn't confirmed yet (state
6461
- // `pending_lock_finalization`). It fires from the body handler AFTER
6462
- // the event row is committed — same lifecycle as
6463
- // `RECEIPT_DELEGATION_NOT_ACTIVE` — so the CLI must advance
6464
- // `lastSenderSequence`, otherwise a retry once the lock confirms
6465
- // reuses the consumed sequence and trips `ENV_SEQUENCE_BACKWARDS`.
6466
- "DELEGATION_PENDING_LOCK",
6467
- // response_hash / request_hash / deliverable_hash content
6468
- // verification. Server commits the receipt envelope row BEFORE
6469
- // running the canonical-hash lookup, so a rejection here still
6470
- // consumes the sender sequence — must be in the allowlist or
6471
- // a retry after fixing the hashes would trip
6472
- // `ENV_SEQUENCE_BACKWARDS`.
6473
- "RECEIPT_RESPONSE_HASH_NOT_FOUND",
6474
- "RECEIPT_REQUEST_HASH_NOT_FOUND",
6475
- "RECEIPT_DELIVERABLE_HASH_MISMATCH"
6476
- ]);
6477
- var VERDICT_VALUES = ["accepted", "accepted_with_notes", "rejected"];
6478
- var SHA256_RE = /^sha256:[0-9a-f]{64}$/;
6479
6458
  function registerPropose(parent) {
6480
6459
  parent.command("propose").description("Send a receipt envelope as the PAYEE. Row lands PROPOSED; the buyer approves payment on-chain via claim_work_payment.").argument("<recipient-did>", "Recipient agent DID (= caller / offerer of the parent delegation)").argument("<delegation-id>", "Parent delegation id (UUID, must be ACCEPTED)").argument("[request-hash]", "sha256:<64 hex> \u2014 SHA-256 of the work_request payload being settled. Omit when --auto-hashes is set.").argument("[response-hash]", "sha256:<64 hex> \u2014 SHA-256 of the work_response payload being settled. Omit when --auto-hashes is set.").option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Sender DID \u2014 required only if multiple agents are registered against this server").option("--verdict <s>", "verdict_proposed (accepted | accepted_with_notes | rejected)", "accepted").option("--notes-hash <sha256>", "Optional sha256:<64 hex> notes hash").option("--deliverable-hash <sha256>", "Optional sha256:<64 hex> deliverable hash").option("--input-tokens <n>", "Optional usage.input_tokens").option("--output-tokens <n>", "Optional usage.output_tokens").option("--latency-ms <n>", "Optional usage.latency_ms").option("--model <name>", "Optional usage.model").option("--computed-amount <s>", "Optional usage.computed_amount").option("--ttl <seconds>", "Envelope TTL in seconds", "3600").option(
6481
6460
  "--auto-hashes",
@@ -6675,7 +6654,7 @@ async function computeWorkLogHashes(api, sender, relationshipId, delegationId, r
6675
6654
  `receipt propose --auto-hashes: no work-log row found for (relationshipId=${relationshipId}, delegationId=${delegationId}, requestId=${requestId}). Did the work_request envelope land yet?`
6676
6655
  );
6677
6656
  }
6678
- if (workLog.state !== "responded") {
6657
+ if (workLog.state !== import_sdk19.WorkLogStates.RESPONDED) {
6679
6658
  throw new Error(
6680
6659
  `receipt propose --auto-hashes: work-log row ${requestId} is in state '${workLog.state}', not 'responded'. The payee must \`work respond\` before a receipt can be proposed.`
6681
6660
  );
@@ -6691,27 +6670,27 @@ async function computeWorkLogHashes(api, sender, relationshipId, delegationId, r
6691
6670
  };
6692
6671
  const responseBody = workLog.responseOutput !== void 0 ? { type: "work_response", content: { delegation_id: workLog.delegationId, request_id: workLog.requestId, output: workLog.responseOutput } } : { type: "work_response", content: { delegation_id: workLog.delegationId, request_id: workLog.requestId, error: workLog.responseError } };
6693
6672
  return {
6694
- requestHash: (0, import_sdk14.canonicalSha256Hex)(requestBody),
6695
- responseHash: (0, import_sdk14.canonicalSha256Hex)(responseBody)
6673
+ requestHash: (0, import_sdk19.canonicalSha256Hex)(requestBody),
6674
+ responseHash: (0, import_sdk19.canonicalSha256Hex)(responseBody)
6696
6675
  };
6697
6676
  }
6698
6677
  async function sendReceiptEnvelope(args) {
6699
6678
  const nextSequence = (args.sender.lastSenderSequence ?? 0) + 1;
6700
6679
  const protectedBlock = {
6701
- protocol_version: "arp/0.1",
6702
- purpose: import_sdk14.Purpose.ENVELOPE,
6703
- message_id: (0, import_sdk14.uuidV4)(),
6680
+ protocol_version: import_sdk19.CURRENT_PROTOCOL_VERSION,
6681
+ purpose: import_sdk19.Purpose.ENVELOPE,
6682
+ message_id: (0, import_sdk19.uuidV4)(),
6704
6683
  sender_did: args.sender.did,
6705
6684
  recipient_did: args.recipientDid,
6706
6685
  relationship_id: null,
6707
6686
  sender_sequence: nextSequence,
6708
- sender_nonce: (0, import_sdk14.senderNonce)(),
6709
- timestamp: (0, import_sdk14.rfc3339)(),
6710
- expires_at: (0, import_sdk14.expiresAt)(args.ttlSeconds),
6687
+ sender_nonce: (0, import_sdk19.senderNonce)(),
6688
+ timestamp: (0, import_sdk19.rfc3339)(),
6689
+ expires_at: (0, import_sdk19.expiresAt)(args.ttlSeconds),
6711
6690
  delivery_id: null
6712
6691
  };
6713
6692
  const signer = makeSigner(args.sender);
6714
- const envelope = (0, import_sdk14.signEnvelope)({
6693
+ const envelope = (0, import_sdk19.signEnvelope)({
6715
6694
  protected: protectedBlock,
6716
6695
  body: args.body,
6717
6696
  identitySecretKey: signer.identitySecretKey,
@@ -6726,7 +6705,7 @@ async function sendReceiptEnvelope(args) {
6726
6705
  updateAgentLocal(args.server, args.sender.did, { lastSenderSequence: nextSequence });
6727
6706
  return result;
6728
6707
  } catch (err) {
6729
- if (err instanceof ApiError && POST_COMMIT_ERROR_CODES2.has(err.payload.code)) {
6708
+ if (err instanceof ApiError && (0, import_sdk19.isPostCommitErrorCode)(err.payload.code)) {
6730
6709
  updateAgentLocal(args.server, args.sender.did, { lastSenderSequence: nextSequence });
6731
6710
  }
6732
6711
  throw err;
@@ -6741,9 +6720,9 @@ function printIngestResult2(result) {
6741
6720
  console.log(`${import_chalk22.default.bold("Server event hash")}: ${import_chalk22.default.cyan(result.serverEventHash)}`);
6742
6721
  }
6743
6722
  function parseVerdict(cmdName, raw) {
6744
- if (raw === void 0 || raw === "") return "accepted";
6745
- if (!VERDICT_VALUES.includes(raw)) {
6746
- throw new Error(`${cmdName}: --verdict must be one of ${VERDICT_VALUES.join(" | ")} (got '${raw}')`);
6723
+ if (raw === void 0 || raw === "") return import_sdk19.ReceiptVerdicts.ACCEPTED;
6724
+ if (!import_sdk19.RECEIPT_VERDICTS.includes(raw)) {
6725
+ throw new Error(`${cmdName}: --verdict must be one of ${import_sdk19.RECEIPT_VERDICTS.join(" | ")} (got '${raw}')`);
6747
6726
  }
6748
6727
  return raw;
6749
6728
  }
@@ -6780,7 +6759,7 @@ function requireUuid3(cmdName, raw, label) {
6780
6759
  requireUuid(cmdName, raw, label);
6781
6760
  }
6782
6761
  function requireSha256(cmdName, raw, label) {
6783
- if (!SHA256_RE.test(raw)) {
6762
+ if (!(0, import_sdk19.isSha256Hex)(raw)) {
6784
6763
  throw new Error(`${cmdName}: ${label} must match 'sha256:<64 lowercase hex>' (got '${raw}')`);
6785
6764
  }
6786
6765
  }
@@ -6791,6 +6770,7 @@ function requireDid2(cmdName, did, label) {
6791
6770
  }
6792
6771
 
6793
6772
  // src/commands/receipts.ts
6773
+ var import_sdk20 = require("@heyanon-arp/sdk");
6794
6774
  var import_chalk23 = __toESM(require("chalk"));
6795
6775
  init_api();
6796
6776
  function registerReceiptsCommand(root) {
@@ -6862,11 +6842,11 @@ function formatReceiptLine(r, selfDid, opts = {}) {
6862
6842
  }
6863
6843
  function formatVerdict(r) {
6864
6844
  switch (r.verdictProposed) {
6865
- case "accepted":
6845
+ case import_sdk20.ReceiptVerdicts.ACCEPTED:
6866
6846
  return import_chalk23.default.green("accepted");
6867
- case "accepted_with_notes":
6847
+ case import_sdk20.ReceiptVerdicts.ACCEPTED_WITH_NOTES:
6868
6848
  return import_chalk23.default.yellow("accepted_with_notes");
6869
- case "rejected":
6849
+ case import_sdk20.ReceiptVerdicts.REJECTED:
6870
6850
  return import_chalk23.default.red("rejected");
6871
6851
  }
6872
6852
  }
@@ -6893,7 +6873,7 @@ function parseLimit5(raw) {
6893
6873
 
6894
6874
  // src/commands/recover.ts
6895
6875
  var import_node_fs8 = require("fs");
6896
- var import_sdk15 = require("@heyanon-arp/sdk");
6876
+ var import_sdk21 = require("@heyanon-arp/sdk");
6897
6877
  var import_chalk24 = __toESM(require("chalk"));
6898
6878
  init_api();
6899
6879
  function registerRecoverCommand(root) {
@@ -6914,18 +6894,18 @@ async function runRecover(opts) {
6914
6894
  }
6915
6895
  const bundle = validateKeyBundle(raw);
6916
6896
  const identitySecret = new Uint8Array(Buffer.from(bundle.identitySecretKeyB64, "base64"));
6917
- const identityPub = (0, import_sdk15.base58btcDecode)(bundle.identityPublicKeyB58);
6918
- const derivedDid = (0, import_sdk15.formatDid)(identityPub);
6897
+ const identityPub = (0, import_sdk21.base58btcDecode)(bundle.identityPublicKeyB58);
6898
+ const derivedDid = (0, import_sdk21.formatDid)(identityPub);
6919
6899
  if (derivedDid !== bundle.did) {
6920
6900
  throw new Error(`recover: key file is inconsistent \u2014 its DID (${bundle.did}) does not match its identity public key (${derivedDid})`);
6921
6901
  }
6922
6902
  const probe2 = new TextEncoder().encode("heyarp-recover-keycheck");
6923
- if (!(0, import_sdk15.verify)((0, import_sdk15.sign)(probe2, identitySecret), probe2, identityPub)) {
6903
+ if (!(0, import_sdk21.verify)((0, import_sdk21.sign)(probe2, identitySecret), probe2, identityPub)) {
6924
6904
  throw new Error("recover: the identity secret key does not match the public key in this bundle");
6925
6905
  }
6926
6906
  const settlementSecret = new Uint8Array(Buffer.from(bundle.settlementSecretKeyB64, "base64"));
6927
- const settlementPub = (0, import_sdk15.base58btcDecode)(bundle.settlementPublicKeyB58);
6928
- if (!(0, import_sdk15.verify)((0, import_sdk15.sign)(probe2, settlementSecret), probe2, settlementPub)) {
6907
+ const settlementPub = (0, import_sdk21.base58btcDecode)(bundle.settlementPublicKeyB58);
6908
+ if (!(0, import_sdk21.verify)((0, import_sdk21.sign)(probe2, settlementSecret), probe2, settlementPub)) {
6929
6909
  throw new Error("recover: the settlement secret key does not match the settlement public key in this bundle");
6930
6910
  }
6931
6911
  if (bundle.ownerWallet && bundle.ownerWallet !== credential.wallet) {
@@ -6981,7 +6961,7 @@ async function runRecover(opts) {
6981
6961
  // src/commands/register.ts
6982
6962
  var import_node_crypto4 = require("crypto");
6983
6963
  var import_node_fs9 = require("fs");
6984
- var import_sdk16 = require("@heyanon-arp/sdk");
6964
+ var import_sdk22 = require("@heyanon-arp/sdk");
6985
6965
  var import_chalk25 = __toESM(require("chalk"));
6986
6966
  var import_prompts2 = __toESM(require("prompts"));
6987
6967
  init_api();
@@ -7043,37 +7023,37 @@ async function runRegister(opts, deps = defaultRegisterDeps) {
7043
7023
  warnIfOrphanHomesPresent();
7044
7024
  }
7045
7025
  const keys = opts.fromKeys ? loadKeysFromFile(opts.fromKeys) : freshKeys();
7046
- const did = (0, import_sdk16.formatDid)(keys.identityPublicKey);
7026
+ const did = (0, import_sdk22.formatDid)(keys.identityPublicKey);
7047
7027
  if (!opts.json) console.log(import_chalk25.default.dim(`DID will be: ${did}`));
7048
7028
  const answers = await mergeAnswers(opts);
7049
7029
  const scryptSalt = (0, import_node_crypto4.randomBytes)(16);
7050
7030
  if (!opts.json) console.log(import_chalk25.default.dim("Deriving scrypt key, this may take a moment..."));
7051
- const scryptKey = (0, import_sdk16.deriveScryptKey)(answers.password, new Uint8Array(scryptSalt));
7031
+ const scryptKey = (0, import_sdk22.deriveScryptKey)(answers.password, new Uint8Array(scryptSalt));
7052
7032
  const challenge = await api.issueChallenge("register");
7053
7033
  const challengeBytes = base64UrlNoPadDecode(challenge.challengeB64);
7054
7034
  if (challengeBytes.length !== 32) {
7055
7035
  throw new Error(`Server returned a ${challengeBytes.length}-byte challenge; expected 32`);
7056
7036
  }
7057
- const challengeSig = (0, import_sdk16.signChallenge)(challengeBytes, keys.identitySecretKey);
7058
- const identityPublicKeyB58 = (0, import_sdk16.base58btcEncode)(keys.identityPublicKey);
7037
+ const challengeSig = (0, import_sdk22.signChallenge)(challengeBytes, keys.identitySecretKey);
7038
+ const identityPublicKeyB58 = (0, import_sdk22.base58btcEncode)(keys.identityPublicKey);
7059
7039
  await api.submitChallengeResponse({
7060
7040
  challengeId: challenge.challengeId,
7061
7041
  identityPublicKey: identityPublicKeyB58,
7062
7042
  signature: Buffer.from(challengeSig).toString("base64")
7063
7043
  });
7064
- const settlementPublicKeyB58 = (0, import_sdk16.base58btcEncode)(keys.settlementPublicKey);
7065
- const scryptSaltId = (0, import_sdk16.uuidV4)();
7044
+ const settlementPublicKeyB58 = (0, import_sdk22.base58btcEncode)(keys.settlementPublicKey);
7045
+ const scryptSaltId = (0, import_sdk22.uuidV4)();
7066
7046
  const payload = {
7067
- purpose: "ARP-KEY-LINK-v1",
7047
+ purpose: import_sdk22.Purpose.KEY_LINK,
7068
7048
  agent_did: did,
7069
7049
  identity_public_key: identityPublicKeyB58,
7070
7050
  settlement_public_key: settlementPublicKeyB58,
7071
- owner_signing_method: "scrypt_password_proof",
7051
+ owner_signing_method: import_sdk22.OWNER_SIGNING_METHODS[0],
7072
7052
  link_method: "manual",
7073
- created_at: (0, import_sdk16.rfc3339)(),
7074
- nonce: (0, import_sdk16.senderNonce)()
7053
+ created_at: (0, import_sdk22.rfc3339)(),
7054
+ nonce: (0, import_sdk22.senderNonce)()
7075
7055
  };
7076
- const attestation = (0, import_sdk16.signKeyLinkAttestation)({ payload, scryptKey, scryptSaltId });
7056
+ const attestation = (0, import_sdk22.signKeyLinkAttestation)({ payload, scryptKey, scryptSaltId });
7077
7057
  const body = {
7078
7058
  challengeId: challenge.challengeId,
7079
7059
  identityPublicKey: identityPublicKeyB58,
@@ -7116,7 +7096,7 @@ async function runRegister(opts, deps = defaultRegisterDeps) {
7116
7096
  try {
7117
7097
  result = await api.register(body, credential.token);
7118
7098
  } catch (err) {
7119
- if (err instanceof ApiError && (err.payload.code === "AUTH_TOKEN_INVALID" || err.payload.code === "AUTH_TOKEN_REQUIRED")) {
7099
+ if (err instanceof ApiError && (err.payload.code === import_sdk22.CliAuthTokenErrorCodes.INVALID || err.payload.code === import_sdk22.CliAuthTokenErrorCodes.REQUIRED)) {
7120
7100
  throw new Error(
7121
7101
  `register: the stored login token for ${api.serverUrl} was rejected (${err.payload.message}) \u2014 run 'heyarp login' again. Your keys are saved locally; re-running register with --from-keys is safe.`
7122
7102
  );
@@ -7272,8 +7252,8 @@ function parseTagsCsv(raw) {
7272
7252
  return raw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
7273
7253
  }
7274
7254
  function freshKeys() {
7275
- const identity = (0, import_sdk16.generateKeyPair)();
7276
- const settlement = (0, import_sdk16.generateKeyPair)();
7255
+ const identity = (0, import_sdk22.generateKeyPair)();
7256
+ const settlement = (0, import_sdk22.generateKeyPair)();
7277
7257
  return {
7278
7258
  identityPublicKey: identity.publicKey,
7279
7259
  identitySecretKey: identity.secretKey,
@@ -7299,9 +7279,9 @@ function loadKeysFromFile(path) {
7299
7279
  if (identitySecret.length !== 32) throw new Error("--from-keys: identitySecretKeyB64 is not a 32-byte Ed25519 seed");
7300
7280
  if (settlementSecret.length !== 32) throw new Error("--from-keys: settlementSecretKeyB64 is not a 32-byte Ed25519 seed");
7301
7281
  return {
7302
- identityPublicKey: (0, import_sdk16.getPublicKey)(identitySecret),
7282
+ identityPublicKey: (0, import_sdk22.getPublicKey)(identitySecret),
7303
7283
  identitySecretKey: identitySecret,
7304
- settlementPublicKey: (0, import_sdk16.getPublicKey)(settlementSecret),
7284
+ settlementPublicKey: (0, import_sdk22.getPublicKey)(settlementSecret),
7305
7285
  settlementSecretKey: settlementSecret
7306
7286
  };
7307
7287
  }
@@ -7317,17 +7297,27 @@ function assertJsonRequiresYes(opts) {
7317
7297
  }
7318
7298
 
7319
7299
  // src/commands/relationships.ts
7300
+ var import_sdk23 = require("@heyanon-arp/sdk");
7320
7301
  var import_chalk26 = __toESM(require("chalk"));
7321
7302
  init_api();
7322
- var ALLOWED_STATES2 = /* @__PURE__ */ new Set(["pending", "active", "paused", "closed"]);
7303
+ var ALLOWED_STATES2 = new Set(import_sdk23.RELATIONSHIP_STATE_NAMES);
7323
7304
  function registerRelationshipsCommand(root) {
7324
7305
  root.command("relationships").description(
7325
7306
  "List relationships <did> belongs to (live + closed pairs, ordered by lastEventAt desc). DID can be passed positionally OR via --from-did; if both are omitted the resolver picks the sole local agent for the server (see `heyarp config set server` + multi-DID disambiguation rules)."
7326
- ).argument("[did]", "Agent DID (did:arp:...) \u2014 must have local state. Optional when --from-did is given OR exactly one agent is registered for the resolved server.").option("--server <url>", "Override ARP server base URL").option("--state <s>", "Filter by relationship state (pending|active|paused|closed)").option("--limit <n>", "Max relationships to return (1..100)", "20").option("--verbose", "Print the full JSON for each relationship in addition to the table", false).option("--from-did <did>", "Alias for the positional <did> argument \u2014 accepted for consistency with other signed commands. Must match the positional if both are given.").action(async (did, opts) => {
7307
+ ).argument("[did]", "Agent DID (did:arp:...) \u2014 must have local state. Optional when --from-did is given OR exactly one agent is registered for the resolved server.").option("--server <url>", "Override ARP server base URL").option("--state <s>", "Filter by relationship state (pending|active|paused|closed)").option("--limit <n>", "Max relationships to return (1..100)", "20").option("--verbose", "Print the full JSON for each relationship in addition to the table. Mutually exclusive with --json.", false).option(
7308
+ "--json",
7309
+ "Machine-readable mode \u2014 emit the relationships as a single JSON array on stdout (RelationshipPublic[]). Prelude + table move to stderr. Mutually exclusive with --verbose.",
7310
+ false
7311
+ ).option("--from-did <did>", "Alias for the positional <did> argument \u2014 accepted for consistency with other signed commands. Must match the positional if both are given.").action(async (did, opts) => {
7327
7312
  await runRelationships(did, opts);
7328
7313
  });
7329
7314
  }
7330
7315
  async function runRelationships(positionalDid, opts) {
7316
+ if (opts.verbose && opts.json) {
7317
+ throw new Error(
7318
+ "relationships: --verbose and --json are mutually exclusive. --json emits the rows as a clean array; --verbose adds per-row dumps that would break `--json | jq`."
7319
+ );
7320
+ }
7331
7321
  const limit = parseLimit6(opts.limit);
7332
7322
  const state = parseState2(opts.state);
7333
7323
  if (positionalDid !== void 0 && opts.fromDid !== void 0 && positionalDid !== opts.fromDid) {
@@ -7337,12 +7327,16 @@ async function runRelationships(positionalDid, opts) {
7337
7327
  const local = explicitDid !== void 0 ? loadAgentOrThrow(opts.server, explicitDid) : resolveSenderAgent("relationships", opts.server, void 0);
7338
7328
  const did = local.did;
7339
7329
  const api = new ArpApiClient(opts.server);
7340
- console.log(import_chalk26.default.dim(`Server: ${api.serverUrl}`));
7341
- console.log(import_chalk26.default.dim(`Signer: ${local.did}`));
7330
+ progress(opts.json, import_chalk26.default.dim(`Server: ${api.serverUrl}`));
7331
+ progress(opts.json, import_chalk26.default.dim(`Signer: ${local.did}`));
7342
7332
  const query = { limit };
7343
7333
  if (state) query.state = state;
7344
7334
  const signer = makeSigner(local);
7345
7335
  const rows = await api.listRelationships(did, signer, query);
7336
+ if (opts.json) {
7337
+ jsonOut(rows);
7338
+ return;
7339
+ }
7346
7340
  if (rows.length === 0) {
7347
7341
  console.log(import_chalk26.default.dim("\n(no relationships)"));
7348
7342
  return;
@@ -7387,12 +7381,12 @@ function parseLimit6(raw) {
7387
7381
  }
7388
7382
 
7389
7383
  // src/commands/reputation.ts
7390
- var import_sdk17 = require("@heyanon-arp/sdk");
7384
+ var import_sdk24 = require("@heyanon-arp/sdk");
7391
7385
  var import_chalk27 = __toESM(require("chalk"));
7392
7386
  init_api();
7393
7387
  function registerReputationCommand(root) {
7394
7388
  root.command("reputation").description("Show an agent's informational reputation (composite + component scores + raw counters). Public, no auth. NOT a money/eligibility gate.").argument("<did>", "did:arp:<base58btc> identifier").option("--server <url>", "Override ARP server base URL").option("--json", "Emit the reputation as JSON on stdout.", false).action(async (did, opts) => {
7395
- if (!(0, import_sdk17.isValidDid)(did)) {
7389
+ if (!(0, import_sdk24.isValidDid)(did)) {
7396
7390
  throw new Error(`'${did}' is not a syntactically valid did:arp identifier`);
7397
7391
  }
7398
7392
  const api = new ArpApiClient(opts.server);
@@ -7434,11 +7428,11 @@ function bar(score) {
7434
7428
  }
7435
7429
 
7436
7430
  // src/commands/send-handshake.ts
7437
- var import_sdk18 = require("@heyanon-arp/sdk");
7431
+ var import_sdk25 = require("@heyanon-arp/sdk");
7438
7432
  var import_chalk28 = __toESM(require("chalk"));
7439
7433
  init_api();
7440
7434
  function registerSendHandshakeCommand(root) {
7441
- root.command("send-handshake").description("Send a handshake envelope to <recipient-did>. Server creates the relationship row on first contact.").argument("<recipient-did>", "Recipient agent DID (did:arp:...)").option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Sender DID \u2014 required only if multiple agents are registered against this server").option("--greeting <s>", "Optional greeting text included in body.content").option("--intent <s>", "Optional intent text included in body.content").option("--ttl <seconds>", "Envelope TTL in seconds (max 86400 = 24h)", "3600").option("--verbose", "Print the full envelope before sending and the full server response. Mutually exclusive with --json.", false).option(
7435
+ root.command("send-handshake").description("Send a handshake envelope to <recipient-did>. Server creates the relationship row on first contact.").argument("<recipient-did>", "Recipient agent DID (did:arp:...)").option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Sender DID \u2014 required only if multiple agents are registered against this server").option("--greeting <s>", "Optional greeting text included in body.content").option("--intent <s>", "Optional intent text included in body.content").option("--ttl <seconds>", `Envelope TTL in seconds (max ${import_sdk25.MAX_ENVELOPE_TTL_SECONDS} = 24h)`, "3600").option("--verbose", "Print the full envelope before sending and the full server response. Mutually exclusive with --json.", false).option(
7442
7436
  "--json",
7443
7437
  "Machine-readable mode \u2014 emit a single JSON object on stdout ({ok, eventId, relationshipId, relationshipEventIndex, serverTimestamp, signedMessageHash, serverEventHash, prevServerEventHash, senderSequence}). The Server/Sender/Recipient prelude moves to stderr so `--json | jq` stays byte-pure. Mutually exclusive with --verbose.",
7444
7438
  false
@@ -7447,8 +7441,8 @@ function registerSendHandshakeCommand(root) {
7447
7441
  });
7448
7442
  }
7449
7443
  async function runSendHandshake(recipientDid, opts) {
7450
- if (!isDid(recipientDid)) {
7451
- throw new Error(`send-handshake: <recipient-did> must look like 'did:arp:...' (got '${recipientDid}')`);
7444
+ if (!(0, import_sdk25.isValidDid)(recipientDid)) {
7445
+ throw new Error(`send-handshake: <recipient-did> must be a valid did:arp identifier (base58btc-encoded 32-byte Ed25519 pubkey) (got '${recipientDid}')`);
7452
7446
  }
7453
7447
  if (opts.verbose && opts.json) {
7454
7448
  throw new Error(
@@ -7467,20 +7461,20 @@ async function runSendHandshake(recipientDid, opts) {
7467
7461
  const body = { type: "handshake", content };
7468
7462
  const nextSequence = (sender.lastSenderSequence ?? 0) + 1;
7469
7463
  const protectedBlock = {
7470
- protocol_version: "arp/0.1",
7471
- purpose: import_sdk18.Purpose.ENVELOPE,
7472
- message_id: (0, import_sdk18.uuidV4)(),
7464
+ protocol_version: import_sdk25.CURRENT_PROTOCOL_VERSION,
7465
+ purpose: import_sdk25.Purpose.ENVELOPE,
7466
+ message_id: (0, import_sdk25.uuidV4)(),
7473
7467
  sender_did: sender.did,
7474
7468
  recipient_did: recipientDid,
7475
7469
  relationship_id: null,
7476
7470
  sender_sequence: nextSequence,
7477
- sender_nonce: (0, import_sdk18.senderNonce)(),
7478
- timestamp: (0, import_sdk18.rfc3339)(),
7479
- expires_at: (0, import_sdk18.expiresAt)(ttlSeconds),
7471
+ sender_nonce: (0, import_sdk25.senderNonce)(),
7472
+ timestamp: (0, import_sdk25.rfc3339)(),
7473
+ expires_at: (0, import_sdk25.expiresAt)(ttlSeconds),
7480
7474
  delivery_id: null
7481
7475
  };
7482
7476
  const signer = makeSigner(sender);
7483
- const envelope = (0, import_sdk18.signEnvelope)({
7477
+ const envelope = (0, import_sdk25.signEnvelope)({
7484
7478
  protected: protectedBlock,
7485
7479
  body,
7486
7480
  identitySecretKey: signer.identitySecretKey
@@ -7494,6 +7488,7 @@ async function runSendHandshake(recipientDid, opts) {
7494
7488
  if (opts.json) {
7495
7489
  jsonOut({
7496
7490
  ok: true,
7491
+ action: "handshake",
7497
7492
  eventId: result.eventId,
7498
7493
  relationshipId: result.relationshipId,
7499
7494
  relationshipEventIndex: result.relationshipEventIndex,
@@ -7532,15 +7527,12 @@ function parseTtl3(raw) {
7532
7527
  }
7533
7528
  return n;
7534
7529
  }
7535
- function isDid(s) {
7536
- return typeof s === "string" && s.startsWith("did:arp:") && s.length > "did:arp:".length;
7537
- }
7538
7530
 
7539
7531
  // src/commands/send-handshake-response.ts
7540
- var import_sdk19 = require("@heyanon-arp/sdk");
7532
+ var import_sdk26 = require("@heyanon-arp/sdk");
7541
7533
  var import_chalk29 = __toESM(require("chalk"));
7542
7534
  init_api();
7543
- var ALLOWED_DECISIONS = /* @__PURE__ */ new Set(["accept", "decline"]);
7535
+ var ALLOWED_DECISIONS = new Set(import_sdk26.HANDSHAKE_DECISIONS);
7544
7536
  function registerSendHandshakeResponseCommand(root) {
7545
7537
  root.command("send-handshake-response").description("Accept or decline an inbound handshake from <recipient-did>. Server reuses the existing relationship row.").argument("<recipient-did>", "Recipient agent DID (did:arp:...) \u2014 the original handshake sender").option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Sender DID \u2014 required only if multiple agents are registered against this server").option("--decision <s>", "REQUIRED: accept or decline").option("--notes <s>", "Optional notes included in body.content").option(
7546
7538
  "--reason <code>",
@@ -7548,8 +7540,8 @@ function registerSendHandshakeResponseCommand(root) {
7548
7540
  // We don't use commander's requiredOption because it
7549
7541
  // would fire for the accept path too; validate manually
7550
7542
  // after decision is parsed.
7551
- `When --decision=decline: required reason code (one of: ${import_sdk19.DECLINE_REASONS.join(", ")}). Carried in body.content.reason.`
7552
- ).option("--reason-detail <s>", "Optional free-text elaboration alongside --reason (max 512 chars).").option("--ttl <seconds>", "Envelope TTL in seconds (max 86400 = 24h)", "3600").option("--verbose", "Print the full envelope before sending and the full server response. Mutually exclusive with --json.", false).option(
7543
+ `When --decision=decline: required reason code (one of: ${import_sdk26.DECLINE_REASONS.join(", ")}). Carried in body.content.reason.`
7544
+ ).option("--reason-detail <s>", "Optional free-text elaboration alongside --reason (max 512 chars).").option("--ttl <seconds>", `Envelope TTL in seconds (max ${import_sdk26.MAX_ENVELOPE_TTL_SECONDS} = 24h)`, "3600").option("--verbose", "Print the full envelope before sending and the full server response. Mutually exclusive with --json.", false).option(
7553
7545
  "--json",
7554
7546
  "Machine-readable mode \u2014 emit a single JSON object on stdout ({ok, decision, eventId, relationshipId, relationshipEventIndex, serverTimestamp, serverEventHash, prevServerEventHash}; idempotent short-circuit adds {idempotent:true}). Prelude + banners move off stdout; on failure stderr carries `{code, message}`. Mutually exclusive with --verbose.",
7555
7547
  false
@@ -7567,13 +7559,13 @@ async function runSendHandshakeResponse(recipientDid, opts) {
7567
7559
  "send-handshake-response: --verbose and --json are mutually exclusive. --json emits the structured server response; --verbose adds envelope + response dumps that would break `--json | jq`."
7568
7560
  );
7569
7561
  }
7570
- if (!isDid2(recipientDid)) {
7571
- throw new Error(`send-handshake-response: <recipient-did> must look like 'did:arp:...' (got '${recipientDid}')`);
7562
+ if (!(0, import_sdk26.isValidDid)(recipientDid)) {
7563
+ throw new Error(`send-handshake-response: <recipient-did> must be a valid did:arp identifier (base58btc-encoded 32-byte Ed25519 pubkey) (got '${recipientDid}')`);
7572
7564
  }
7573
7565
  const decision = parseDecision(opts.decision);
7574
7566
  const ttlSeconds = parseTtl4(opts.ttl);
7575
7567
  let declinePayload = null;
7576
- if (decision === "decline") {
7568
+ if (decision === import_sdk26.HandshakeDecisions.DECLINE) {
7577
7569
  const reason = parseDeclineReason("send-handshake-response", opts.reason);
7578
7570
  const detail = parseReasonDetail("send-handshake-response", opts.reasonDetail);
7579
7571
  declinePayload = detail ? { reason, reasonDetail: detail } : { reason };
@@ -7599,6 +7591,7 @@ async function runSendHandshakeResponse(recipientDid, opts) {
7599
7591
  if (opts.json) {
7600
7592
  jsonOut({
7601
7593
  ok: true,
7594
+ action: "handshake_response",
7602
7595
  idempotent: true,
7603
7596
  decision,
7604
7597
  eventId: previousResponseFromMe.eventId,
@@ -7641,19 +7634,19 @@ async function runSendHandshakeResponse(recipientDid, opts) {
7641
7634
  const body = { type: "handshake_response", content };
7642
7635
  const nextSequence = (sender.lastSenderSequence ?? 0) + 1;
7643
7636
  const protectedBlock = {
7644
- protocol_version: "arp/0.1",
7645
- purpose: import_sdk19.Purpose.ENVELOPE,
7646
- message_id: (0, import_sdk19.uuidV4)(),
7637
+ protocol_version: import_sdk26.CURRENT_PROTOCOL_VERSION,
7638
+ purpose: import_sdk26.Purpose.ENVELOPE,
7639
+ message_id: (0, import_sdk26.uuidV4)(),
7647
7640
  sender_did: sender.did,
7648
7641
  recipient_did: recipientDid,
7649
7642
  relationship_id: null,
7650
7643
  sender_sequence: nextSequence,
7651
- sender_nonce: (0, import_sdk19.senderNonce)(),
7652
- timestamp: (0, import_sdk19.rfc3339)(),
7653
- expires_at: (0, import_sdk19.expiresAt)(ttlSeconds),
7644
+ sender_nonce: (0, import_sdk26.senderNonce)(),
7645
+ timestamp: (0, import_sdk26.rfc3339)(),
7646
+ expires_at: (0, import_sdk26.expiresAt)(ttlSeconds),
7654
7647
  delivery_id: null
7655
7648
  };
7656
- const envelope = (0, import_sdk19.signEnvelope)({
7649
+ const envelope = (0, import_sdk26.signEnvelope)({
7657
7650
  protected: protectedBlock,
7658
7651
  body,
7659
7652
  identitySecretKey: signer.identitySecretKey
@@ -7667,6 +7660,7 @@ async function runSendHandshakeResponse(recipientDid, opts) {
7667
7660
  if (opts.json) {
7668
7661
  jsonOut({
7669
7662
  ok: true,
7663
+ action: "handshake_response",
7670
7664
  decision,
7671
7665
  eventId: result.eventId,
7672
7666
  relationshipId: result.relationshipId,
@@ -7715,13 +7709,10 @@ function parseTtl4(raw) {
7715
7709
  }
7716
7710
  return n;
7717
7711
  }
7718
- function isDid2(s) {
7719
- return typeof s === "string" && s.startsWith("did:arp:") && s.length > "did:arp:".length;
7720
- }
7721
7712
  function classifyIdempotencyOutcome(decision, existing) {
7722
7713
  if (!existing) return { kind: "proceed" };
7723
- if (existing.state === "active") {
7724
- if (decision === "decline") {
7714
+ if (existing.state === import_sdk26.RelationshipStates.ACTIVE) {
7715
+ if (decision === import_sdk26.HandshakeDecisions.DECLINE) {
7725
7716
  return {
7726
7717
  kind: "error",
7727
7718
  message: `send-handshake-response: relationship ${existing.relationshipId} is already 'active' from a previous ACCEPT. A decline cannot reverse that \u2014 the active relationship is now under FSM control. If you want to terminate, close the relationship explicitly; pass --force to ignore this check and send the decline (which the server will reject with DOM_INVALID_TRANSITION).`
@@ -7729,7 +7720,7 @@ function classifyIdempotencyOutcome(decision, existing) {
7729
7720
  }
7730
7721
  return { kind: "short-circuit" };
7731
7722
  }
7732
- if (existing.state === "closed") {
7723
+ if (existing.state === import_sdk26.RelationshipStates.CLOSED) {
7733
7724
  return {
7734
7725
  kind: "error",
7735
7726
  message: `send-handshake-response: relationship ${existing.relationshipId} is CLOSED. Cannot respond to handshake on a terminated relationship \u2014 start a fresh handshake instead.`
@@ -7919,7 +7910,7 @@ function registerWhoamiCommand(root) {
7919
7910
  }
7920
7911
 
7921
7912
  // src/commands/work.ts
7922
- var import_sdk20 = require("@heyanon-arp/sdk");
7913
+ var import_sdk27 = require("@heyanon-arp/sdk");
7923
7914
  var import_chalk32 = __toESM(require("chalk"));
7924
7915
  init_api();
7925
7916
  function registerWorkCommands(root) {
@@ -7927,25 +7918,6 @@ function registerWorkCommands(root) {
7927
7918
  registerRequest(cmd);
7928
7919
  registerRespond(cmd);
7929
7920
  }
7930
- var POST_COMMIT_ERROR_CODES3 = /* @__PURE__ */ new Set([
7931
- "WORK_DELEGATION_NOT_FOUND",
7932
- "WORK_DELEGATION_NOT_ACTIVE",
7933
- "WORK_RELATIONSHIP_MISMATCH",
7934
- "WORK_REQUESTER_NOT_OFFERER",
7935
- "WORK_REQUEST_ALREADY_EXISTS",
7936
- "WORK_REQUEST_NOT_FOUND",
7937
- "WORK_RESPONDER_IS_CALLER",
7938
- "WORK_INVALID_STATE",
7939
- // The work handler's LOCKED gate emits
7940
- // `DELEGATION_PENDING_LOCK` (409) when the delegation is funded but
7941
- // the on-chain lock isn't confirmed yet (state
7942
- // `pending_lock_finalization`). It fires from the body handler AFTER
7943
- // the event row is committed — same lifecycle as
7944
- // `WORK_DELEGATION_NOT_ACTIVE` — so the CLI must advance
7945
- // `lastSenderSequence`, otherwise a retry once the lock confirms
7946
- // reuses the consumed sequence and trips `ENV_SEQUENCE_BACKWARDS`.
7947
- "DELEGATION_PENDING_LOCK"
7948
- ]);
7949
7921
  function registerRequest(parent) {
7950
7922
  parent.command("request").description("Send a work_request to <recipient-did> under <delegation-id> (must be ACCEPTED).").argument("<recipient-did>", "Recipient agent DID (the payee \u2014 the OTHER side of the delegation pair)").argument("<delegation-id>", "Parent delegation id (UUID, must be ACCEPTED)").option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Sender DID \u2014 required only if multiple agents are registered against this server").option("--request-id <id>", "Override the auto-generated request id (must be unique within the delegation)").option(
7951
7923
  "--params <json>",
@@ -7953,11 +7925,20 @@ function registerRequest(parent) {
7953
7925
  ).option(
7954
7926
  "--params-file <path>",
7955
7927
  "Read the JSON payload from a file. Mutually exclusive with --params. Use this for any payload large or quote-heavy enough that `--params '{...}'` would fight your shell."
7956
- ).option("--ttl <seconds>", "Envelope TTL in seconds (max 86400 = 24h)", "3600").option("--verbose", "Print the full envelope before sending and the full server response", false).action(async (recipientDid, delegationId, opts) => {
7928
+ ).option("--ttl <seconds>", `Envelope TTL in seconds (max ${import_sdk27.MAX_ENVELOPE_TTL_SECONDS} = 24h)`, "3600").option("--verbose", "Print the full envelope before sending and the full server response. Mutually exclusive with --json.", false).option(
7929
+ "--json",
7930
+ 'Machine-readable mode \u2014 emit a single JSON object on stdout ({ok, action:"work_request", requestId, delegationId, eventId, relationshipId, relationshipEventIndex, serverTimestamp, serverEventHash}). Prelude + reply hints move to stderr; on failure stderr carries `{code, message}`. Mutually exclusive with --verbose.',
7931
+ false
7932
+ ).action(async (recipientDid, delegationId, opts) => {
7957
7933
  await runRequest(recipientDid, delegationId, opts);
7958
7934
  });
7959
7935
  }
7960
7936
  async function runRequest(recipientDid, delegationId, opts) {
7937
+ if (opts.verbose && opts.json) {
7938
+ throw new Error(
7939
+ "work request: --verbose and --json are mutually exclusive. --json emits the structured server response; --verbose adds dumps that would break `--json | jq`."
7940
+ );
7941
+ }
7961
7942
  requireDid3("work request", recipientDid, "<recipient-did>");
7962
7943
  delegationId = requireUuidNormalised3("work request", delegationId, "<delegation-id>");
7963
7944
  const ttlSeconds = parseTtl5("work request", opts.ttl);
@@ -7971,27 +7952,51 @@ async function runRequest(recipientDid, delegationId, opts) {
7971
7952
  params
7972
7953
  };
7973
7954
  const body = { type: "work_request", content };
7974
- console.log(import_chalk32.default.dim(`Server: ${api.serverUrl}`));
7975
- console.log(import_chalk32.default.dim(`Sender: ${sender.did}`));
7976
- console.log(import_chalk32.default.dim(`Recipient: ${recipientDid}`));
7977
- console.log(import_chalk32.default.dim(`Delegation: ${delegationId}`));
7978
- console.log(import_chalk32.default.dim(`Request id: ${requestId}`));
7955
+ progress(opts.json, import_chalk32.default.dim(`Server: ${api.serverUrl}`));
7956
+ progress(opts.json, import_chalk32.default.dim(`Sender: ${sender.did}`));
7957
+ progress(opts.json, import_chalk32.default.dim(`Recipient: ${recipientDid}`));
7958
+ progress(opts.json, import_chalk32.default.dim(`Delegation: ${delegationId}`));
7959
+ progress(opts.json, import_chalk32.default.dim(`Request id: ${requestId}`));
7979
7960
  const result = await sendWorkEnvelope({ api, sender, recipientDid, body, ttlSeconds, verbose: opts.verbose, server: opts.server });
7980
- printIngestResult3(result);
7981
- console.log(import_chalk32.default.dim(`
7961
+ if (opts.json) {
7962
+ jsonOut({
7963
+ ok: true,
7964
+ action: "work_request",
7965
+ requestId,
7966
+ delegationId,
7967
+ eventId: result.eventId,
7968
+ relationshipId: result.relationshipId,
7969
+ relationshipEventIndex: result.relationshipEventIndex,
7970
+ serverTimestamp: result.serverTimestamp,
7971
+ serverEventHash: result.serverEventHash,
7972
+ prevServerEventHash: result.prevServerEventHash ?? null
7973
+ });
7974
+ } else {
7975
+ printIngestResult3(result);
7976
+ console.log(import_chalk32.default.dim(`
7982
7977
  The payee can reply with:`));
7983
- console.log(import_chalk32.default.dim(` heyarp work respond ${result.relationshipId} ${delegationId} ${requestId} --output '<json>'`));
7984
- console.log(import_chalk32.default.dim(` heyarp work respond ${result.relationshipId} ${delegationId} ${requestId} --error CODE:message`));
7978
+ console.log(import_chalk32.default.dim(` heyarp work respond ${result.relationshipId} ${delegationId} ${requestId} --output '<json>'`));
7979
+ console.log(import_chalk32.default.dim(` heyarp work respond ${result.relationshipId} ${delegationId} ${requestId} --error CODE:message`));
7980
+ }
7985
7981
  }
7986
7982
  function registerRespond(parent) {
7987
7983
  parent.command("respond").description("Send a work_response under <relationship-id> for <delegation-id> / <request-id>. Payee-only.").argument("<relationship-id>", "Relationship UUID").argument("<delegation-id>", "Parent delegation id (UUID)").argument("<request-id>", "Request id supplied on the work_request").option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Sender DID \u2014 required only if multiple agents are registered against this server").option("--output <json>", "Success payload as a JSON object literal. Mutually exclusive with --error and --output-file.").option(
7988
7984
  "--output-file <path>",
7989
7985
  "Read the success payload from a file. Mutually exclusive with --output and --error. Use this for any payload large or quote-heavy enough to fight your shell."
7990
- ).option("--error <code:message>", "Failure payload as `CODE:message`. Mutually exclusive with --output / --output-file.").option("--ttl <seconds>", "Envelope TTL in seconds", "3600").option("--verbose", "Print the full envelope before sending and the full server response", false).action(async (relationshipId, delegationId, requestId, opts) => {
7986
+ ).option("--error <code:message>", "Failure payload as `CODE:message`. Mutually exclusive with --output / --output-file.").option("--ttl <seconds>", "Envelope TTL in seconds", "3600").option("--verbose", "Print the full envelope before sending and the full server response. Mutually exclusive with --json.", false).option(
7987
+ "--json",
7988
+ 'Machine-readable mode \u2014 emit a single JSON object on stdout ({ok, action:"work_response", requestId, delegationId, eventId, relationshipId, relationshipEventIndex, serverTimestamp, serverEventHash}). Prelude moves to stderr; on failure stderr carries `{code, message}`. Mutually exclusive with --verbose.',
7989
+ false
7990
+ ).action(async (relationshipId, delegationId, requestId, opts) => {
7991
7991
  await runRespond(relationshipId, delegationId, requestId, opts);
7992
7992
  });
7993
7993
  }
7994
7994
  async function runRespond(relationshipId, delegationId, requestId, opts) {
7995
+ if (opts.verbose && opts.json) {
7996
+ throw new Error(
7997
+ "work respond: --verbose and --json are mutually exclusive. --json emits the structured server response; --verbose adds dumps that would break `--json | jq`."
7998
+ );
7999
+ }
7995
8000
  relationshipId = requireUuidNormalised3("work respond", relationshipId, "<relationship-id>");
7996
8001
  delegationId = requireUuidNormalised3("work respond", delegationId, "<delegation-id>");
7997
8002
  const ttlSeconds = parseTtl5("work respond", opts.ttl);
@@ -8006,33 +8011,48 @@ async function runRespond(relationshipId, delegationId, requestId, opts) {
8006
8011
  ...responsePayload
8007
8012
  };
8008
8013
  const body = { type: "work_response", content };
8009
- console.log(import_chalk32.default.dim(`Server: ${api.serverUrl}`));
8010
- console.log(import_chalk32.default.dim(`Sender: ${sender.did}`));
8011
- console.log(import_chalk32.default.dim(`Recipient: ${recipientDid}`));
8012
- console.log(import_chalk32.default.dim(`Relationship: ${relationshipId}`));
8013
- console.log(import_chalk32.default.dim(`Delegation: ${delegationId}`));
8014
- console.log(import_chalk32.default.dim(`Request id: ${requestId}`));
8015
- console.log(import_chalk32.default.dim(`Outcome: ${responsePayload.output ? "success" : "error"}`));
8014
+ progress(opts.json, import_chalk32.default.dim(`Server: ${api.serverUrl}`));
8015
+ progress(opts.json, import_chalk32.default.dim(`Sender: ${sender.did}`));
8016
+ progress(opts.json, import_chalk32.default.dim(`Recipient: ${recipientDid}`));
8017
+ progress(opts.json, import_chalk32.default.dim(`Relationship: ${relationshipId}`));
8018
+ progress(opts.json, import_chalk32.default.dim(`Delegation: ${delegationId}`));
8019
+ progress(opts.json, import_chalk32.default.dim(`Request id: ${requestId}`));
8020
+ progress(opts.json, import_chalk32.default.dim(`Outcome: ${responsePayload.output ? "success" : "error"}`));
8016
8021
  const result = await sendWorkEnvelope({ api, sender, recipientDid, body, ttlSeconds, verbose: opts.verbose, server: opts.server });
8017
- printIngestResult3(result);
8022
+ if (opts.json) {
8023
+ jsonOut({
8024
+ ok: true,
8025
+ action: "work_response",
8026
+ requestId,
8027
+ delegationId,
8028
+ eventId: result.eventId,
8029
+ relationshipId: result.relationshipId,
8030
+ relationshipEventIndex: result.relationshipEventIndex,
8031
+ serverTimestamp: result.serverTimestamp,
8032
+ serverEventHash: result.serverEventHash,
8033
+ prevServerEventHash: result.prevServerEventHash ?? null
8034
+ });
8035
+ } else {
8036
+ printIngestResult3(result);
8037
+ }
8018
8038
  }
8019
8039
  async function sendWorkEnvelope(args) {
8020
8040
  const nextSequence = (args.sender.lastSenderSequence ?? 0) + 1;
8021
8041
  const protectedBlock = {
8022
- protocol_version: "arp/0.1",
8023
- purpose: import_sdk20.Purpose.ENVELOPE,
8024
- message_id: (0, import_sdk20.uuidV4)(),
8042
+ protocol_version: import_sdk27.CURRENT_PROTOCOL_VERSION,
8043
+ purpose: import_sdk27.Purpose.ENVELOPE,
8044
+ message_id: (0, import_sdk27.uuidV4)(),
8025
8045
  sender_did: args.sender.did,
8026
8046
  recipient_did: args.recipientDid,
8027
8047
  relationship_id: null,
8028
8048
  sender_sequence: nextSequence,
8029
- sender_nonce: (0, import_sdk20.senderNonce)(),
8030
- timestamp: (0, import_sdk20.rfc3339)(),
8031
- expires_at: (0, import_sdk20.expiresAt)(args.ttlSeconds),
8049
+ sender_nonce: (0, import_sdk27.senderNonce)(),
8050
+ timestamp: (0, import_sdk27.rfc3339)(),
8051
+ expires_at: (0, import_sdk27.expiresAt)(args.ttlSeconds),
8032
8052
  delivery_id: null
8033
8053
  };
8034
8054
  const signer = makeSigner(args.sender);
8035
- const envelope = (0, import_sdk20.signEnvelope)({
8055
+ const envelope = (0, import_sdk27.signEnvelope)({
8036
8056
  protected: protectedBlock,
8037
8057
  body: args.body,
8038
8058
  identitySecretKey: signer.identitySecretKey
@@ -8046,7 +8066,7 @@ async function sendWorkEnvelope(args) {
8046
8066
  updateAgentLocal(args.server, args.sender.did, { lastSenderSequence: nextSequence });
8047
8067
  return result;
8048
8068
  } catch (err) {
8049
- if (err instanceof ApiError && POST_COMMIT_ERROR_CODES3.has(err.payload.code)) {
8069
+ if (err instanceof ApiError && (0, import_sdk27.isPostCommitErrorCode)(err.payload.code)) {
8050
8070
  updateAgentLocal(args.server, args.sender.did, { lastSenderSequence: nextSequence });
8051
8071
  }
8052
8072
  throw err;
@@ -8058,7 +8078,7 @@ async function resolveResponseRecipient(cmdName, api, signer, args) {
8058
8078
  const page = await api.listWorkLogs(args.relationshipId, signer, { delegationId: args.delegationId, limit: 100, after });
8059
8079
  const row = page.find((w) => w.requestId === args.requestId);
8060
8080
  if (row) {
8061
- if (row.state !== "requested") {
8081
+ if (row.state !== import_sdk27.WorkLogStates.REQUESTED) {
8062
8082
  throw new Error(
8063
8083
  `${cmdName}: work_request ${args.requestId} for delegation ${args.delegationId} is already in state '${row.state}' \u2014 only requests in state 'requested' can be responded to.`
8064
8084
  );
@@ -8175,7 +8195,7 @@ function parseTtl5(cmdName, raw) {
8175
8195
  }
8176
8196
  function parseRequestId(cmdName, raw) {
8177
8197
  if (raw === void 0 || raw === "") {
8178
- return (0, import_sdk20.uuidV4)();
8198
+ return (0, import_sdk27.uuidV4)();
8179
8199
  }
8180
8200
  if (raw.length === 0) {
8181
8201
  throw new Error(`${cmdName}: --request-id must be a non-empty string`);
@@ -8196,9 +8216,10 @@ function requireDid3(cmdName, did, label) {
8196
8216
  }
8197
8217
 
8198
8218
  // src/commands/work-list.ts
8219
+ var import_sdk28 = require("@heyanon-arp/sdk");
8199
8220
  var import_chalk33 = __toESM(require("chalk"));
8200
8221
  init_api();
8201
- var ALLOWED_STATES3 = /* @__PURE__ */ new Set(["requested", "responded"]);
8222
+ var ALLOWED_STATES3 = new Set(import_sdk28.WORK_LOG_STATES);
8202
8223
  function registerWorkListCommand(root) {
8203
8224
  root.command("work-list").description("List work-log rows for a relationship (one row per (delegationId, requestId), oldest-first)").argument("<relationship-id>", "Relationship UUID").option("--server <url>", "Override ARP server base URL").option("--state <s>", "Filter by exact state (requested|responded)").option("--delegation-id <uuid>", "Narrow to work-logs operating under a specific delegation id").option("--after <id>", "Cursor: pass the previous page's last `id` to fetch the next page").option("--limit <n>", "Max rows to return (1..100)", "20").option("--from-did <did>", "Signer DID \u2014 required only if multiple agents are registered against this server").option(
8204
8225
  "--verbose",
@@ -8271,9 +8292,9 @@ function formatWorkLogLine(w, selfDid, opts = {}) {
8271
8292
  }
8272
8293
  function colorState2(s) {
8273
8294
  switch (s) {
8274
- case "requested":
8295
+ case import_sdk28.WorkLogStates.REQUESTED:
8275
8296
  return import_chalk33.default.yellow("requested");
8276
- case "responded":
8297
+ case import_sdk28.WorkLogStates.RESPONDED:
8277
8298
  return import_chalk33.default.green("responded");
8278
8299
  }
8279
8300
  }
@@ -8281,7 +8302,7 @@ function stateColumnWidth2() {
8281
8302
  return 9;
8282
8303
  }
8283
8304
  function formatOutcome(w) {
8284
- if (w.state === "requested") return import_chalk33.default.dim("(in flight)");
8305
+ if (w.state === import_sdk28.WorkLogStates.REQUESTED) return import_chalk33.default.dim("(in flight)");
8285
8306
  if (w.responseError) return import_chalk33.default.red(`error ${w.responseError.code}: ${truncate3(w.responseError.message, 32)}`);
8286
8307
  if (w.responseOutput) return import_chalk33.default.cyan("ok");
8287
8308
  return import_chalk33.default.dim("(empty response)");
@@ -8314,6 +8335,43 @@ function parseLimit7(raw) {
8314
8335
  return n;
8315
8336
  }
8316
8337
 
8338
+ // src/option-hint.ts
8339
+ function unknownOptionHint(program, err) {
8340
+ try {
8341
+ if (err.code !== "commander.unknownOption" || !err.message) return null;
8342
+ const m = /unknown option '([^']+)'/.exec(err.message);
8343
+ if (!m) return null;
8344
+ const raw = m[1];
8345
+ const flag = raw.split("=")[0];
8346
+ const owners = [];
8347
+ const walk = (cmd, path) => {
8348
+ for (const opt of cmd.options) {
8349
+ if (opt.long === flag || opt.short === flag) owners.push(path.join(" "));
8350
+ }
8351
+ for (const sub of cmd.commands) walk(sub, [...path, sub.name()]);
8352
+ };
8353
+ for (const sub of program.commands) walk(sub, [sub.name()]);
8354
+ if (owners.length === 0) return null;
8355
+ owners.sort((a, b) => a.split(" ").length - b.split(" ").length);
8356
+ const argv = process.argv.slice(2);
8357
+ const commandNames = /* @__PURE__ */ new Set();
8358
+ for (const c of program.commands) {
8359
+ commandNames.add(c.name());
8360
+ for (const a of c.aliases()) commandNames.add(a);
8361
+ }
8362
+ const flagIdx = argv.findIndex((t) => t === raw || t.split("=")[0] === flag);
8363
+ const cmdIdx = argv.findIndex((t) => commandNames.has(t));
8364
+ const placedBeforeCommand = cmdIdx === -1 || flagIdx !== -1 && flagIdx < cmdIdx;
8365
+ if (placedBeforeCommand) {
8366
+ return `'${flag}' is a command option, not a global one \u2014 put it AFTER the command (e.g. \`heyarp ${owners[0]} ${flag} \u2026\`), not before it.`;
8367
+ }
8368
+ const list = owners.slice(0, 4).join(", ") + (owners.length > 4 ? ", \u2026" : "");
8369
+ return `'${flag}' isn't valid for that command \u2014 it's an option of: ${list}.`;
8370
+ } catch {
8371
+ return null;
8372
+ }
8373
+ }
8374
+
8317
8375
  // src/cli.ts
8318
8376
  async function checkForUpdates() {
8319
8377
  if (package_default.private === true) return;
@@ -8328,7 +8386,7 @@ async function checkForUpdates() {
8328
8386
  async function main() {
8329
8387
  void checkForUpdates();
8330
8388
  const program = new import_commander.Command();
8331
- program.name("heyarp").description("ARP \u2014 Agent Relationship Protocol CLI (talks to apps/arp-server)").version(package_default.version).option("--trace", "Surface stack traces and error details on failure.", false);
8389
+ 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);
8332
8390
  program.exitOverride();
8333
8391
  program.configureOutput({
8334
8392
  writeErr: (str) => {
@@ -8336,6 +8394,7 @@ async function main() {
8336
8394
  process.stderr.write(str);
8337
8395
  }
8338
8396
  });
8397
+ program.addHelpText("after", () => optionPlacementHelpNote());
8339
8398
  program.addHelpText("after", () => onboardingHelpFooter());
8340
8399
  registerConfigCommand(program);
8341
8400
  registerGuideCommand(program);
@@ -8383,10 +8442,13 @@ async function main() {
8383
8442
  const json = process.argv.includes("--json");
8384
8443
  const verbose = process.argv.includes("--trace");
8385
8444
  const exitCode = typeof cerr.exitCode === "number" && cerr.exitCode !== 0 ? cerr.exitCode : 1;
8445
+ const hint = unknownOptionHint(program, cerr);
8386
8446
  if (isCommanderError && !json) {
8447
+ if (hint) process.stderr.write(`hint: ${hint}
8448
+ `);
8387
8449
  process.exit(exitCode);
8388
8450
  }
8389
- emitError(err, { json, verbose });
8451
+ emitError(err, { json, verbose, hint: hint ?? void 0 });
8390
8452
  process.exit(exitCode);
8391
8453
  }
8392
8454
  }