@heyanon-arp/cli 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +2331 -649
- package/dist/cli.js.map +1 -1
- package/examples/README.md +147 -0
- package/examples/worker-template.py +894 -0
- package/package.json +3 -2
package/dist/cli.js
CHANGED
|
@@ -333,6 +333,31 @@ var init_api = __esm({
|
|
|
333
333
|
async rotateIdentityKey(did, body, signer) {
|
|
334
334
|
return this.signedRequest("POST", `/v1/agents/${encodeURIComponent(did)}/rotate-identity-key`, body, signer);
|
|
335
335
|
}
|
|
336
|
+
// ─── Webhook config + secret ────────────────────────────────────
|
|
337
|
+
//
|
|
338
|
+
// All routes are `/me/...` — the server derives the agent from
|
|
339
|
+
// the signer DID, so the CLI never sends a `:did` path parameter.
|
|
340
|
+
// The signedRequest helper signs the canonical request (method +
|
|
341
|
+
// path + query + body) with the agent's identity key; that's the
|
|
342
|
+
// auth contract the SignedRequestGuard checks server-side.
|
|
343
|
+
async getMyWebhookConfig(signer) {
|
|
344
|
+
return this.signedRequest("GET", "/v1/agents/me/webhook-config", null, signer);
|
|
345
|
+
}
|
|
346
|
+
async setMyWebhookConfig(body, signer) {
|
|
347
|
+
return this.signedRequest("POST", "/v1/agents/me/webhook-config", body, signer);
|
|
348
|
+
}
|
|
349
|
+
async getMyWebhookSecretStatus(signer) {
|
|
350
|
+
return this.signedRequest("GET", "/v1/agents/me/webhook-secret", null, signer);
|
|
351
|
+
}
|
|
352
|
+
async initMyWebhookSecret(signer) {
|
|
353
|
+
return this.signedRequest("POST", "/v1/agents/me/webhook-secret/init", {}, signer);
|
|
354
|
+
}
|
|
355
|
+
async rotateStageMyWebhookSecret(signer) {
|
|
356
|
+
return this.signedRequest("POST", "/v1/agents/me/webhook-secret/rotate-stage", {}, signer);
|
|
357
|
+
}
|
|
358
|
+
async rotateCommitMyWebhookSecret(body, signer) {
|
|
359
|
+
return this.signedRequest("POST", "/v1/agents/me/webhook-secret/rotate-commit", body, signer);
|
|
360
|
+
}
|
|
336
361
|
/**
|
|
337
362
|
* Ingest a signed envelope. Endpoint is public (no
|
|
338
363
|
* `X-ARP-Signer-DID` headers) — authentication is the envelope's
|
|
@@ -431,9 +456,7 @@ var init_api = __esm({
|
|
|
431
456
|
* known but has never been party to an event.
|
|
432
457
|
*/
|
|
433
458
|
async getActivitySummary(did) {
|
|
434
|
-
return this.get(
|
|
435
|
-
`/v1/agents/${encodeURIComponent(did)}/activity-summary`
|
|
436
|
-
);
|
|
459
|
+
return this.get(`/v1/agents/${encodeURIComponent(did)}/activity-summary`);
|
|
437
460
|
}
|
|
438
461
|
/**
|
|
439
462
|
* Signed `GET /v1/agents/:did/sender-sequence`. Returns the highest
|
|
@@ -712,27 +735,17 @@ var import_simple_update_notifier = __toESM(require("simple-update-notifier"));
|
|
|
712
735
|
// package.json
|
|
713
736
|
var package_default = {
|
|
714
737
|
name: "@heyanon-arp/cli",
|
|
715
|
-
version: "0.0.
|
|
738
|
+
version: "0.0.5",
|
|
716
739
|
description: "Command-line client for the Agent Relationship Protocol \u2014 register agents, sign envelopes, run escrowed work cycles on Solana.",
|
|
717
740
|
license: "MIT",
|
|
718
|
-
keywords: [
|
|
719
|
-
"arp",
|
|
720
|
-
"agent-relationship-protocol",
|
|
721
|
-
"did",
|
|
722
|
-
"solana",
|
|
723
|
-
"escrow",
|
|
724
|
-
"ed25519",
|
|
725
|
-
"agents",
|
|
726
|
-
"a2a",
|
|
727
|
-
"cli"
|
|
728
|
-
],
|
|
741
|
+
keywords: ["arp", "agent-relationship-protocol", "did", "solana", "escrow", "ed25519", "agents", "a2a", "cli"],
|
|
729
742
|
bin: {
|
|
730
743
|
heyarp: "./dist/cli.js"
|
|
731
744
|
},
|
|
732
745
|
publishConfig: {
|
|
733
746
|
access: "public"
|
|
734
747
|
},
|
|
735
|
-
files: ["dist", "LICENSE", "README.md"],
|
|
748
|
+
files: ["dist", "examples", "LICENSE", "README.md"],
|
|
736
749
|
engines: {
|
|
737
750
|
node: ">=22"
|
|
738
751
|
},
|
|
@@ -765,9 +778,6 @@ var package_default = {
|
|
|
765
778
|
}
|
|
766
779
|
};
|
|
767
780
|
|
|
768
|
-
// src/cli.ts
|
|
769
|
-
init_api();
|
|
770
|
-
|
|
771
781
|
// src/commands/agents.ts
|
|
772
782
|
var import_chalk2 = __toESM(require("chalk"));
|
|
773
783
|
init_api();
|
|
@@ -789,15 +799,39 @@ function formatGenericError(err, verbose = false) {
|
|
|
789
799
|
return `${import_chalk.default.red("Error")} ${message}
|
|
790
800
|
${import_chalk.default.gray(err.stack)}`;
|
|
791
801
|
}
|
|
792
|
-
function
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
802
|
+
function toCliErrorJson(err, includeStack = false) {
|
|
803
|
+
const { ApiError: ApiError2 } = (init_api(), __toCommonJS(api_exports));
|
|
804
|
+
if (err instanceof ApiError2) {
|
|
805
|
+
const { code, message: message2, details } = err.payload;
|
|
806
|
+
const out2 = { code, message: message2 };
|
|
807
|
+
if (details !== void 0) out2.details = details;
|
|
808
|
+
return out2;
|
|
809
|
+
}
|
|
810
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
811
|
+
const out = { code: "CLI_ERROR", message };
|
|
812
|
+
if (includeStack && err instanceof Error && err.stack) {
|
|
813
|
+
out.details = { stack: err.stack };
|
|
814
|
+
}
|
|
815
|
+
return out;
|
|
816
|
+
}
|
|
817
|
+
function emitError(err, opts) {
|
|
818
|
+
if (opts.json) {
|
|
819
|
+
console.error(JSON.stringify(toCliErrorJson(err, opts.verbose)));
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
796
822
|
const { ApiError: ApiError2 } = (init_api(), __toCommonJS(api_exports));
|
|
797
823
|
if (err instanceof ApiError2) {
|
|
798
|
-
|
|
824
|
+
console.error(formatApiError(err.payload, opts.verbose));
|
|
825
|
+
} else {
|
|
826
|
+
console.error(formatGenericError(err, opts.verbose));
|
|
799
827
|
}
|
|
800
|
-
|
|
828
|
+
}
|
|
829
|
+
function emitActionError(err, command) {
|
|
830
|
+
let root = command;
|
|
831
|
+
while (root.parent) root = root.parent;
|
|
832
|
+
const verbose = !!root.opts().trace;
|
|
833
|
+
const json = !!command.opts().json;
|
|
834
|
+
emitError(err, { json, verbose });
|
|
801
835
|
}
|
|
802
836
|
function formatJson(value) {
|
|
803
837
|
return JSON.stringify(value, null, 2);
|
|
@@ -837,7 +871,18 @@ function supportsUnicodeFrame() {
|
|
|
837
871
|
return probe2.includes("UTF-8") || probe2.includes("UTF8");
|
|
838
872
|
}
|
|
839
873
|
function printJsonArray(rows) {
|
|
840
|
-
|
|
874
|
+
jsonOut(rows);
|
|
875
|
+
}
|
|
876
|
+
function jsonOut(value) {
|
|
877
|
+
console.log(formatJson(value));
|
|
878
|
+
}
|
|
879
|
+
function progress(jsonMode, ...args) {
|
|
880
|
+
if (jsonMode) return;
|
|
881
|
+
console.log(...args);
|
|
882
|
+
}
|
|
883
|
+
function warn(jsonMode, ...args) {
|
|
884
|
+
if (jsonMode) return;
|
|
885
|
+
console.error(...args);
|
|
841
886
|
}
|
|
842
887
|
function formatAgentsTable(rows) {
|
|
843
888
|
if (rows.length === 0) return import_chalk.default.dim("(no agents registered locally)");
|
|
@@ -876,11 +921,7 @@ function registerAgentsCommand(root) {
|
|
|
876
921
|
async function runAgents(opts) {
|
|
877
922
|
const limit = parseLimit(opts.limit);
|
|
878
923
|
const api = new ArpApiClient(opts.server);
|
|
879
|
-
|
|
880
|
-
console.error(import_chalk2.default.dim(`Server: ${api.serverUrl}`));
|
|
881
|
-
} else {
|
|
882
|
-
console.log(import_chalk2.default.dim(`Server: ${api.serverUrl}`));
|
|
883
|
-
}
|
|
924
|
+
progress(opts.json, import_chalk2.default.dim(`Server: ${api.serverUrl}`));
|
|
884
925
|
const query = { limit };
|
|
885
926
|
if (opts.tag && opts.tag.length > 0) query.tag = opts.tag.map((t) => t.trim().toLowerCase());
|
|
886
927
|
if (opts.query) query.q = opts.query;
|
|
@@ -1111,18 +1152,42 @@ function writeStateFile(state) {
|
|
|
1111
1152
|
(0, import_node_fs2.mkdirSync)(dir, { recursive: true, mode: 448 });
|
|
1112
1153
|
}
|
|
1113
1154
|
const body = JSON.stringify(state, null, 2);
|
|
1114
|
-
|
|
1155
|
+
const tmpPath = `${path}.tmp.${process.pid}`;
|
|
1156
|
+
const fd = (0, import_node_fs2.openSync)(tmpPath, "w", 384);
|
|
1157
|
+
try {
|
|
1158
|
+
(0, import_node_fs2.writeSync)(fd, body, 0, "utf8");
|
|
1159
|
+
(0, import_node_fs2.fsyncSync)(fd);
|
|
1160
|
+
} finally {
|
|
1161
|
+
(0, import_node_fs2.closeSync)(fd);
|
|
1162
|
+
}
|
|
1163
|
+
let chmodOk = true;
|
|
1164
|
+
try {
|
|
1165
|
+
(0, import_node_fs2.chmodSync)(tmpPath, 384);
|
|
1166
|
+
} catch {
|
|
1167
|
+
chmodOk = false;
|
|
1168
|
+
}
|
|
1169
|
+
try {
|
|
1170
|
+
(0, import_node_fs2.renameSync)(tmpPath, path);
|
|
1171
|
+
} catch (err) {
|
|
1172
|
+
try {
|
|
1173
|
+
(0, import_node_fs2.unlinkSync)(tmpPath);
|
|
1174
|
+
} catch {
|
|
1175
|
+
}
|
|
1176
|
+
throw err;
|
|
1177
|
+
}
|
|
1115
1178
|
try {
|
|
1116
1179
|
(0, import_node_fs2.chmodSync)(path, 384);
|
|
1117
1180
|
} catch {
|
|
1181
|
+
chmodOk = false;
|
|
1118
1182
|
}
|
|
1183
|
+
return { chmodOk };
|
|
1119
1184
|
}
|
|
1120
1185
|
function saveAgent(serverOverride, agent) {
|
|
1121
1186
|
const key = resolveServerUrl(serverOverride);
|
|
1122
1187
|
const state = readStateFile();
|
|
1123
1188
|
if (!state.servers[key]) state.servers[key] = { agents: {} };
|
|
1124
1189
|
state.servers[key].agents[agent.did] = agent;
|
|
1125
|
-
writeStateFile(state);
|
|
1190
|
+
return writeStateFile(state);
|
|
1126
1191
|
}
|
|
1127
1192
|
function loadAgent(serverOverride, did) {
|
|
1128
1193
|
const key = resolveServerUrl(serverOverride);
|
|
@@ -1145,7 +1210,7 @@ function updateAgentLocal(serverOverride, did, patch) {
|
|
|
1145
1210
|
throw new Error(`Cannot update local state \u2014 no record for ${did} on ${key}.`);
|
|
1146
1211
|
}
|
|
1147
1212
|
server.agents[did] = { ...server.agents[did], ...patch };
|
|
1148
|
-
writeStateFile(state);
|
|
1213
|
+
return writeStateFile(state);
|
|
1149
1214
|
}
|
|
1150
1215
|
function resolveSenderAgent(cmdName, serverOverride, explicitFromDid) {
|
|
1151
1216
|
const resolvedServerUrl = resolveServerUrl(serverOverride);
|
|
@@ -1177,8 +1242,8 @@ function listAgents() {
|
|
|
1177
1242
|
}
|
|
1178
1243
|
|
|
1179
1244
|
// src/commands/delegation.ts
|
|
1180
|
-
var import_sdk4 = require("@heyanon-arp/sdk");
|
|
1181
1245
|
var import_node_fs4 = require("fs");
|
|
1246
|
+
var import_sdk4 = require("@heyanon-arp/sdk");
|
|
1182
1247
|
var import_chalk6 = __toESM(require("chalk"));
|
|
1183
1248
|
init_api();
|
|
1184
1249
|
|
|
@@ -1256,14 +1321,6 @@ ${verb}.`));
|
|
|
1256
1321
|
console.log(formatJson(agent));
|
|
1257
1322
|
}
|
|
1258
1323
|
|
|
1259
|
-
// src/commands/wallet.ts
|
|
1260
|
-
var import_sdk3 = require("@heyanon-arp/sdk");
|
|
1261
|
-
var import_utils = require("@noble/hashes/utils");
|
|
1262
|
-
var import_web3 = require("@solana/web3.js");
|
|
1263
|
-
var import_node_fs3 = require("fs");
|
|
1264
|
-
init_api();
|
|
1265
|
-
init_config();
|
|
1266
|
-
|
|
1267
1324
|
// src/commands/status.ts
|
|
1268
1325
|
var import_sdk2 = require("@heyanon-arp/sdk");
|
|
1269
1326
|
var import_chalk5 = __toESM(require("chalk"));
|
|
@@ -1349,6 +1406,25 @@ async function runStatus(relationshipId, opts) {
|
|
|
1349
1406
|
process.exitCode = 124;
|
|
1350
1407
|
}
|
|
1351
1408
|
}
|
|
1409
|
+
async function awaitFsmTransitionAfterAction(input) {
|
|
1410
|
+
const { api, signerDid, signer, relationshipId, untilPhase, waitIntervalSec, waitTimeoutSec, waitVerbose, json } = input;
|
|
1411
|
+
if (!json) {
|
|
1412
|
+
console.log(import_chalk5.default.dim(`
|
|
1413
|
+
[--wait-until ${untilPhase}] polling relationship ${relationshipId} (interval=${waitIntervalSec}s timeout=${waitTimeoutSec}s)`));
|
|
1414
|
+
}
|
|
1415
|
+
const outcome = await runWaitLoop({
|
|
1416
|
+
fetchSummary: () => composeStatus(api, signerDid, relationshipId, signer),
|
|
1417
|
+
waitIntervalSec,
|
|
1418
|
+
waitTimeoutSec,
|
|
1419
|
+
waitVerbose: !!waitVerbose,
|
|
1420
|
+
json: !!json,
|
|
1421
|
+
log: (line) => console.log(line),
|
|
1422
|
+
until: untilPhase
|
|
1423
|
+
});
|
|
1424
|
+
if (outcome.timedOut) {
|
|
1425
|
+
process.exitCode = 124;
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1352
1428
|
async function runWaitLoop(opts) {
|
|
1353
1429
|
const isTerminal = (s) => s.cycleComplete || s.relationshipState === "closed" || s.relationshipState === "not_found";
|
|
1354
1430
|
const isActionable = (s) => {
|
|
@@ -1382,8 +1458,12 @@ async function runWaitLoop(opts) {
|
|
|
1382
1458
|
if (opts.json) opts.log(JSON.stringify(terminalUnmatchedJson ? { ...initial, _waitTimedOut: true } : initial));
|
|
1383
1459
|
else {
|
|
1384
1460
|
if (opts.until !== void 0) {
|
|
1385
|
-
opts.log(
|
|
1386
|
-
|
|
1461
|
+
opts.log(
|
|
1462
|
+
import_chalk5.default.yellow(
|
|
1463
|
+
`
|
|
1464
|
+
[--wait] Terminal state (${initial.relationshipState}, cycleComplete=${initial.cycleComplete}) reached before phase '${opts.until}' \u2014 exiting; phase unreachable.`
|
|
1465
|
+
)
|
|
1466
|
+
);
|
|
1387
1467
|
} else {
|
|
1388
1468
|
opts.log(import_chalk5.default.dim(`
|
|
1389
1469
|
[--wait] Already terminal \u2014 exiting.`));
|
|
@@ -1433,7 +1513,11 @@ async function runWaitLoop(opts) {
|
|
|
1433
1513
|
if (phaseReached) {
|
|
1434
1514
|
opts.log(import_chalk5.default.green(`[--wait] Phase '${opts.until}' reached.`));
|
|
1435
1515
|
} else {
|
|
1436
|
-
opts.log(
|
|
1516
|
+
opts.log(
|
|
1517
|
+
import_chalk5.default.yellow(
|
|
1518
|
+
`[--wait] Terminal state (${next.relationshipState}, cycleComplete=${next.cycleComplete}) reached before phase '${opts.until}' \u2014 phase unreachable.`
|
|
1519
|
+
)
|
|
1520
|
+
);
|
|
1437
1521
|
}
|
|
1438
1522
|
} else {
|
|
1439
1523
|
opts.log(import_chalk5.default.green(`[--wait] ${isTerminal(next) ? "Cycle terminated" : `Your turn (owner=${next.nextActionOwner})`}.`));
|
|
@@ -1448,11 +1532,17 @@ async function runWaitLoop(opts) {
|
|
|
1448
1532
|
opts.log(JSON.stringify({ ...last, _waitTimedOut: true }));
|
|
1449
1533
|
} else {
|
|
1450
1534
|
if (opts.until !== void 0) {
|
|
1451
|
-
opts.log(
|
|
1452
|
-
|
|
1535
|
+
opts.log(
|
|
1536
|
+
import_chalk5.default.yellow(
|
|
1537
|
+
`
|
|
1538
|
+
[--wait] Timed out after ${opts.waitTimeoutSec}s without reaching phase '${opts.until}' (latest state: ${last.relationshipState}, hint: ${last.nextActionHint}).`
|
|
1539
|
+
)
|
|
1540
|
+
);
|
|
1453
1541
|
} else {
|
|
1454
|
-
opts.log(
|
|
1455
|
-
|
|
1542
|
+
opts.log(
|
|
1543
|
+
import_chalk5.default.yellow(`
|
|
1544
|
+
[--wait] Timed out after ${opts.waitTimeoutSec}s without an actionable or terminal transition (your turn never came, cycle still in flight).`)
|
|
1545
|
+
);
|
|
1456
1546
|
}
|
|
1457
1547
|
}
|
|
1458
1548
|
return { timedOut: true, last };
|
|
@@ -1533,7 +1623,7 @@ function parseWaitInterval(raw) {
|
|
|
1533
1623
|
return n;
|
|
1534
1624
|
}
|
|
1535
1625
|
function sleep(ms) {
|
|
1536
|
-
return new Promise((
|
|
1626
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1537
1627
|
}
|
|
1538
1628
|
async function composeStatus(api, signerDid, relationshipId, signer) {
|
|
1539
1629
|
const [relationshipsOrNull, contractsOrError] = await Promise.all([
|
|
@@ -1574,13 +1664,7 @@ async function composeStatus(api, signerDid, relationshipId, signer) {
|
|
|
1574
1664
|
const delegations = latestContract ? await fetchAllPages(
|
|
1575
1665
|
(after) => api.listDelegations(relationshipId, signer, { limit: 100, contractId: latestContract.contractId, ...after ? { after } : {} })
|
|
1576
1666
|
) : [];
|
|
1577
|
-
const latestDelegation = pickLatestLive(delegations, [
|
|
1578
|
-
"proposed",
|
|
1579
|
-
"offered",
|
|
1580
|
-
"pending_lock_finalization",
|
|
1581
|
-
"accepted",
|
|
1582
|
-
"awaiting_release_finalization"
|
|
1583
|
-
]);
|
|
1667
|
+
const latestDelegation = pickLatestLive(delegations, ["proposed", "offered", "pending_lock_finalization", "accepted", "awaiting_release_finalization"]);
|
|
1584
1668
|
const [workLogs, receipts] = await Promise.all([
|
|
1585
1669
|
latestDelegation ? fetchAllPages(
|
|
1586
1670
|
(after) => api.listWorkLogs(relationshipId, signer, { limit: 100, delegationId: latestDelegation.delegationId, ...after ? { after } : {} })
|
|
@@ -1650,9 +1734,7 @@ function findReceiptForWorkLog(receipts, workLog, allWorkLogs = [workLog]) {
|
|
|
1650
1734
|
const expectedResponseHash = (0, import_sdk2.canonicalSha256Hex)(responseBody);
|
|
1651
1735
|
const matches = receipts.filter((r) => r.requestHash === expectedRequestHash && r.responseHash === expectedResponseHash);
|
|
1652
1736
|
if (matches.length > 0) return pickLatest(matches);
|
|
1653
|
-
const respondedSiblings = allWorkLogs.filter(
|
|
1654
|
-
(wl) => wl.delegationId === workLog.delegationId && (wl.responseOutput !== void 0 || wl.responseError !== void 0)
|
|
1655
|
-
).length;
|
|
1737
|
+
const respondedSiblings = allWorkLogs.filter((wl) => wl.delegationId === workLog.delegationId && (wl.responseOutput !== void 0 || wl.responseError !== void 0)).length;
|
|
1656
1738
|
if (respondedSiblings > 1) return null;
|
|
1657
1739
|
const sameDelegation = receipts.filter((r) => r.delegationId === workLog.delegationId);
|
|
1658
1740
|
return pickLatest(sameDelegation);
|
|
@@ -1878,6 +1960,12 @@ function stateColor(state) {
|
|
|
1878
1960
|
}
|
|
1879
1961
|
|
|
1880
1962
|
// src/commands/wallet.ts
|
|
1963
|
+
var import_node_fs3 = require("fs");
|
|
1964
|
+
var import_sdk3 = require("@heyanon-arp/sdk");
|
|
1965
|
+
var import_utils = require("@noble/hashes/utils");
|
|
1966
|
+
var import_web3 = require("@solana/web3.js");
|
|
1967
|
+
init_api();
|
|
1968
|
+
init_config();
|
|
1881
1969
|
function bytesToBase64(bytes) {
|
|
1882
1970
|
return Buffer.from(bytes).toString("base64");
|
|
1883
1971
|
}
|
|
@@ -1967,22 +2055,10 @@ function registerDerivePdas(cmd) {
|
|
|
1967
2055
|
).requiredOption("--delegation-id <id>", "Delegation UUID (canonical `del_<uuid>` or bare `<uuid>` \u2014 both accepted)").option("--server <url>", "Override server URL (used only for --program-id auto-discovery)").option(
|
|
1968
2056
|
"--program-id <pubkey>",
|
|
1969
2057
|
`Deployed ARP escrow program id. Precedence: --program-id flag > ARP_ESCROW_PROGRAM_ID env > GET /v1/escrow/protocol-fee (auto-discover) > hardcoded fallback (${FALLBACK_PROGRAM_ID}).`
|
|
1970
|
-
).option(
|
|
1971
|
-
"--rpc-url <url>",
|
|
1972
|
-
"Accepted for symmetry with `wallet verify-release` but never read \u2014 PDAs are derived locally from `program_id + delegation_id`; no RPC needed."
|
|
1973
|
-
).option(
|
|
2058
|
+
).option("--rpc-url <url>", "Accepted for symmetry with `wallet verify-release` but never read \u2014 PDAs are derived locally from `program_id + delegation_id`; no RPC needed.").option(
|
|
1974
2059
|
"--recipient-pubkey <base58>",
|
|
1975
2060
|
"Accepted for symmetry with `wallet create-lock` but never read \u2014 PDAs depend ONLY on program_id + delegation_id; recipient pubkey has no derivation role."
|
|
1976
|
-
).option(
|
|
1977
|
-
"--condition-hash <hex>",
|
|
1978
|
-
"Accepted for symmetry with `wallet create-lock` but never read \u2014 PDAs depend ONLY on program_id + delegation_id."
|
|
1979
|
-
).option(
|
|
1980
|
-
"--amount-lamports <int>",
|
|
1981
|
-
"Accepted for symmetry with `wallet create-lock` but never read \u2014 PDAs depend ONLY on program_id + delegation_id."
|
|
1982
|
-
).option(
|
|
1983
|
-
"--expiry-secs <int>",
|
|
1984
|
-
"Accepted for symmetry with `wallet create-lock` but never read \u2014 PDAs depend ONLY on program_id + delegation_id."
|
|
1985
|
-
).option("--json", "Emit JSON instead of human text (jq-pipeable: `.lock_pda`, `.escrow_pda`, `.config_pda`, `.lock_id_hex`).", false).action(async (opts) => {
|
|
2061
|
+
).option("--condition-hash <hex>", "Accepted for symmetry with `wallet create-lock` but never read \u2014 PDAs depend ONLY on program_id + delegation_id.").option("--amount-lamports <int>", "Accepted for symmetry with `wallet create-lock` but never read \u2014 PDAs depend ONLY on program_id + delegation_id.").option("--expiry-secs <int>", "Accepted for symmetry with `wallet create-lock` but never read \u2014 PDAs depend ONLY on program_id + delegation_id.").option("--json", "Emit JSON instead of human text (jq-pipeable: `.lock_pda`, `.escrow_pda`, `.config_pda`, `.lock_id_hex`).", false).action(async (opts) => {
|
|
1986
2062
|
try {
|
|
1987
2063
|
const out = await derivePdasHandler(opts);
|
|
1988
2064
|
if (opts.json) {
|
|
@@ -1997,7 +2073,7 @@ function registerDerivePdas(cmd) {
|
|
|
1997
2073
|
console.log(`EventAuthPDA: ${out.event_authority_pda}`);
|
|
1998
2074
|
}
|
|
1999
2075
|
} catch (err) {
|
|
2000
|
-
|
|
2076
|
+
emitError(err, { json: opts.json, verbose: process.argv.includes("--trace") });
|
|
2001
2077
|
process.exit(1);
|
|
2002
2078
|
}
|
|
2003
2079
|
});
|
|
@@ -2059,7 +2135,7 @@ function registerVerifyRelease(cmd) {
|
|
|
2059
2135
|
process.exitCode = 1;
|
|
2060
2136
|
}
|
|
2061
2137
|
} catch (err) {
|
|
2062
|
-
|
|
2138
|
+
emitError(err, { json: opts.json, verbose: process.argv.includes("--trace") });
|
|
2063
2139
|
process.exit(1);
|
|
2064
2140
|
}
|
|
2065
2141
|
});
|
|
@@ -2100,7 +2176,7 @@ async function verifyReleaseHandler(opts) {
|
|
|
2100
2176
|
if (shouldRetrySigCheck) {
|
|
2101
2177
|
const retryDelaysMs = [3e3, 3e3];
|
|
2102
2178
|
for (const delay of retryDelaysMs) {
|
|
2103
|
-
await new Promise((
|
|
2179
|
+
await new Promise((resolve2) => setTimeout(resolve2, delay));
|
|
2104
2180
|
const retriedSigs = await conn.getSignaturesForAddress(lockPda, { limit: 5 });
|
|
2105
2181
|
if (retriedSigs.length > 0) {
|
|
2106
2182
|
lockSeenInSignatures = true;
|
|
@@ -2240,7 +2316,7 @@ function registerCreateLock(cmd) {
|
|
|
2240
2316
|
const out = await createLockHandler(opts);
|
|
2241
2317
|
console.log(JSON.stringify(out, null, 2));
|
|
2242
2318
|
} catch (err) {
|
|
2243
|
-
|
|
2319
|
+
emitError(err, { json: opts.json, verbose: process.argv.includes("--trace") });
|
|
2244
2320
|
process.exit(1);
|
|
2245
2321
|
}
|
|
2246
2322
|
});
|
|
@@ -2260,7 +2336,7 @@ async function preflightLockCurrency(api, agent, normalisedDelegationId, contrac
|
|
|
2260
2336
|
})
|
|
2261
2337
|
]);
|
|
2262
2338
|
} catch (err) {
|
|
2263
|
-
if (err instanceof Error && err.message.includes("
|
|
2339
|
+
if (err instanceof Error && err.message.includes("LOCK_CURRENCY_NON_NATIVE_SOL")) throw err;
|
|
2264
2340
|
} finally {
|
|
2265
2341
|
if (timeoutHandle !== void 0) clearTimeout(timeoutHandle);
|
|
2266
2342
|
}
|
|
@@ -2284,7 +2360,9 @@ async function preflightLockCurrencyInner(api, agent, normalisedDelegationId, si
|
|
|
2284
2360
|
if (contractId !== void 0) {
|
|
2285
2361
|
let activeContracts;
|
|
2286
2362
|
try {
|
|
2287
|
-
activeContracts = await fetchAllPages(
|
|
2363
|
+
activeContracts = await fetchAllPages(
|
|
2364
|
+
(after) => api.listContracts(r.relationshipId, signer, { limit: 100, state: "active", ...after ? { after } : {} }, signal)
|
|
2365
|
+
);
|
|
2288
2366
|
} catch {
|
|
2289
2367
|
activeContracts = [];
|
|
2290
2368
|
}
|
|
@@ -2293,7 +2371,7 @@ async function preflightLockCurrencyInner(api, agent, normalisedDelegationId, si
|
|
|
2293
2371
|
const directAssetId = directContract.rateCurrency?.assetId;
|
|
2294
2372
|
if (typeof directAssetId === "string" && !directAssetId.endsWith("/slip44:501")) {
|
|
2295
2373
|
throw new Error(
|
|
2296
|
-
`wallet create-lock pre-flight (
|
|
2374
|
+
`wallet create-lock pre-flight (LOCK_CURRENCY_NON_NATIVE_SOL): contract ${contractId} is priced in '${directAssetId}' (NOT native SOL). This command only builds native-SOL locks (isNativeSol=1, no SPL Token-2022 path) \u2014 the resulting tx blob would be rejected server-side with ESC_LOCK_CURRENCY_MISMATCH or ESC_LOCK_AMOUNT_DELEGATION_MISMATCH after \`delegation offer\` ships it. Use a SOL-priced contract (e.g. \`--rate-currency 'SOL:solana-mainnet'\` on \`contract propose\`) or wait for SPL-token lock support to land.`
|
|
2297
2375
|
);
|
|
2298
2376
|
}
|
|
2299
2377
|
}
|
|
@@ -2308,7 +2386,9 @@ async function preflightLockCurrencyInner(api, agent, normalisedDelegationId, si
|
|
|
2308
2386
|
if (!match) continue;
|
|
2309
2387
|
let contracts = [];
|
|
2310
2388
|
try {
|
|
2311
|
-
contracts = await fetchAllPages(
|
|
2389
|
+
contracts = await fetchAllPages(
|
|
2390
|
+
(after) => api.listContracts(r.relationshipId, signer, { limit: 100, state: "active", ...after ? { after } : {} }, signal)
|
|
2391
|
+
);
|
|
2312
2392
|
} catch {
|
|
2313
2393
|
}
|
|
2314
2394
|
const contract = contracts.find((c) => c.contractId === match.contractId);
|
|
@@ -2323,7 +2403,7 @@ async function preflightLockCurrencyInner(api, agent, normalisedDelegationId, si
|
|
|
2323
2403
|
const offendingAssetId = isContractNonNative ? contractAssetId : delegationAssetId;
|
|
2324
2404
|
const sourceLabel = isContractNonNative ? `contract ${match.contractId}` : `delegation ${normalisedDelegationId}`;
|
|
2325
2405
|
throw new Error(
|
|
2326
|
-
`wallet create-lock pre-flight (
|
|
2406
|
+
`wallet create-lock pre-flight (LOCK_CURRENCY_NON_NATIVE_SOL): ${sourceLabel} is priced in '${offendingAssetId}' (NOT native SOL). This command only builds native-SOL locks (isNativeSol=1, no SPL Token-2022 path) \u2014 the resulting tx blob would be rejected server-side with ESC_LOCK_CURRENCY_MISMATCH or ESC_LOCK_AMOUNT_DELEGATION_MISMATCH after \`delegation offer\` ships it. Use a SOL-priced contract (e.g. \`--rate-currency 'SOL:solana-mainnet'\` on \`contract propose\`) or wait for SPL-token lock support to land.`
|
|
2327
2407
|
);
|
|
2328
2408
|
}
|
|
2329
2409
|
}
|
|
@@ -2451,7 +2531,7 @@ function registerSignSettlement(cmd) {
|
|
|
2451
2531
|
const out = await signSettlementHandler(opts);
|
|
2452
2532
|
console.log(JSON.stringify(out, null, 2));
|
|
2453
2533
|
} catch (err) {
|
|
2454
|
-
|
|
2534
|
+
emitError(err, { json: opts.json, verbose: process.argv.includes("--trace") });
|
|
2455
2535
|
process.exit(1);
|
|
2456
2536
|
}
|
|
2457
2537
|
});
|
|
@@ -2607,10 +2687,23 @@ function registerOffer(parent) {
|
|
|
2607
2687
|
parent.command("offer").description("Open a new delegation under <contract-id> (must be ACTIVE) addressed to <recipient-did>.").argument("<recipient-did>", "Recipient agent DID (did:arp:...)").argument("<contract-id>", "Contract id this delegation operates under (must be ACTIVE)").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). 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("--amount <s>", 'Optional decimal-as-string amount (e.g. "10.00"). REQUIRES --currency if set.').option("--currency <s>", `Asset identifier: shorthand (${import_sdk4.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(
|
|
2608
2688
|
"--escrow-lock-from-file <path>",
|
|
2609
2689
|
"Path to JSON output of `heyarp wallet create-lock` (recommended). Contains all 5 escrow_lock fields: signed_tx_blob, lock_id, amount, asset_id, expiry. Mutually exclusive with the inline --escrow-lock-* flags below."
|
|
2610
|
-
).option(
|
|
2690
|
+
).option(
|
|
2691
|
+
"--escrow-lock-blob <base64>",
|
|
2692
|
+
"INLINE alternative: the signed Solana tx blob (base64). Requires --escrow-lock-id, --escrow-lock-amount, --escrow-lock-asset-id, --escrow-lock-expiry together."
|
|
2693
|
+
).option("--escrow-lock-id <hex32>", "INLINE lock_id (32-byte hex). Used with --escrow-lock-blob.").option("--escrow-lock-amount <int>", "INLINE lock amount in base units (e.g. lamports for native SOL). Used with --escrow-lock-blob.").option("--escrow-lock-asset-id <caip19>", "INLINE currency CAIP-19 asset_id (e.g. solana:<cluster_id>/slip44:501). Used with --escrow-lock-blob.").option("--escrow-lock-expiry <unix>", "INLINE expiry as unix seconds. Used with --escrow-lock-blob.").option(
|
|
2611
2694
|
"--program-id <pubkey>",
|
|
2612
2695
|
"Expected ARP escrow program id for pre-flight against the lock file's embedded `program_id`. Precedence: this flag > ARP_ESCROW_PROGRAM_ID env > server protocol-fee endpoint. Mismatch throws BEFORE the envelope ships, so a wrong-program lock never silently fails on chain after the offer was already delivered. Pre-flight is skipped only for old lock files lacking `program_id`, or when no expected value can be resolved."
|
|
2613
|
-
).option(
|
|
2696
|
+
).option(
|
|
2697
|
+
"--no-escrow",
|
|
2698
|
+
"Opt-out for test_mode servers. Default REQUIRES the escrow_lock attachment; without --no-escrow, missing flags throw BEFORE the envelope is sent (avoids ESC_LOCK_MISSING POST_COMMIT consuming sender_sequence)."
|
|
2699
|
+
).option(
|
|
2700
|
+
"--wait-until <phase>",
|
|
2701
|
+
'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.'
|
|
2702
|
+
).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(
|
|
2703
|
+
"--wait-verbose",
|
|
2704
|
+
'When --wait-until is set: emit one dim line per poll tick showing the current FSM state. Useful for "is it alive or stuck?" diagnosis on long blocks.',
|
|
2705
|
+
false
|
|
2706
|
+
).action(async (recipientDid, contractId, opts) => {
|
|
2614
2707
|
await runOffer(recipientDid, contractId, opts);
|
|
2615
2708
|
});
|
|
2616
2709
|
}
|
|
@@ -2633,9 +2726,7 @@ function assembleEscrowLockAttachment(opts) {
|
|
|
2633
2726
|
const someInlineSet = inlineFlags.some((f) => f !== void 0 && f !== "");
|
|
2634
2727
|
const allInlineSet = inlineFlags.every((f) => f !== void 0 && f !== "");
|
|
2635
2728
|
if (fromFile && someInlineSet) {
|
|
2636
|
-
throw new Error(
|
|
2637
|
-
"delegation offer: --escrow-lock-from-file and --escrow-lock-blob/--escrow-lock-* are mutually exclusive. Pick one path."
|
|
2638
|
-
);
|
|
2729
|
+
throw new Error("delegation offer: --escrow-lock-from-file and --escrow-lock-blob/--escrow-lock-* are mutually exclusive. Pick one path.");
|
|
2639
2730
|
}
|
|
2640
2731
|
if (!fromFile && !someInlineSet) {
|
|
2641
2732
|
if (opts.escrow === false) return void 0;
|
|
@@ -2677,7 +2768,9 @@ function assembleEscrowLockAttachment(opts) {
|
|
|
2677
2768
|
} else if (typeof expiryRaw === "string") {
|
|
2678
2769
|
expiryNum2 = Number(expiryRaw);
|
|
2679
2770
|
if (String(expiryNum2) !== expiryRaw.trim()) {
|
|
2680
|
-
throw new Error(
|
|
2771
|
+
throw new Error(
|
|
2772
|
+
`delegation offer: --escrow-lock-from-file '${fromFile}' has invalid 'expiry' (must be positive integer unix seconds): ${JSON.stringify(expiryRaw)}.`
|
|
2773
|
+
);
|
|
2681
2774
|
}
|
|
2682
2775
|
} else {
|
|
2683
2776
|
throw new Error(`delegation offer: --escrow-lock-from-file '${fromFile}' has invalid 'expiry' (must be positive integer unix seconds): ${JSON.stringify(expiryRaw)}.`);
|
|
@@ -2688,9 +2781,7 @@ function assembleEscrowLockAttachment(opts) {
|
|
|
2688
2781
|
let delegationIdFromLock;
|
|
2689
2782
|
if (p.delegation_id !== void 0) {
|
|
2690
2783
|
if (typeof p.delegation_id !== "string" || !UUID_RE.test(p.delegation_id)) {
|
|
2691
|
-
throw new Error(
|
|
2692
|
-
`delegation offer: --escrow-lock-from-file '${fromFile}' has invalid 'delegation_id' (must be a UUID): ${JSON.stringify(p.delegation_id)}.`
|
|
2693
|
-
);
|
|
2784
|
+
throw new Error(`delegation offer: --escrow-lock-from-file '${fromFile}' has invalid 'delegation_id' (must be a UUID): ${JSON.stringify(p.delegation_id)}.`);
|
|
2694
2785
|
}
|
|
2695
2786
|
delegationIdFromLock = p.delegation_id.toLowerCase();
|
|
2696
2787
|
}
|
|
@@ -2800,9 +2891,34 @@ Reference this delegation on subsequent calls with:`));
|
|
|
2800
2891
|
console.log(import_chalk6.default.dim(` heyarp delegation accept ${result.relationshipId} ${delegationId}`));
|
|
2801
2892
|
console.log(import_chalk6.default.dim(` heyarp delegation decline ${result.relationshipId} ${delegationId}`));
|
|
2802
2893
|
console.log(import_chalk6.default.dim(` heyarp delegation cancel ${result.relationshipId} ${delegationId}`));
|
|
2894
|
+
if (opts.waitUntil) {
|
|
2895
|
+
const untilPhase = parseUntilPhase(opts.waitUntil);
|
|
2896
|
+
if (untilPhase === void 0) {
|
|
2897
|
+
throw new Error(`delegation offer: --wait-until requires a phase value (got ${JSON.stringify(opts.waitUntil)})`);
|
|
2898
|
+
}
|
|
2899
|
+
await awaitFsmTransitionAfterAction({
|
|
2900
|
+
api,
|
|
2901
|
+
signerDid: sender.did,
|
|
2902
|
+
signer: makeSigner(sender),
|
|
2903
|
+
relationshipId: result.relationshipId,
|
|
2904
|
+
untilPhase,
|
|
2905
|
+
waitIntervalSec: parseWaitInterval(opts.waitInterval),
|
|
2906
|
+
waitTimeoutSec: parseWaitTimeout(opts.waitTimeout),
|
|
2907
|
+
waitVerbose: !!opts.waitVerbose,
|
|
2908
|
+
json: false
|
|
2909
|
+
// delegation offer is a human-text command (printIngestResult is human-text); JSON mode would be a follow-up.
|
|
2910
|
+
});
|
|
2911
|
+
}
|
|
2803
2912
|
}
|
|
2804
2913
|
function registerAccept(parent) {
|
|
2805
|
-
parent.command("accept").description("Accept a PROPOSED delegation \u2014 promotes to ACCEPTED. Counterparty-only.").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("--contract-id <uuid>", "Override the auto-resolved contract id (default: read from the delegation row)").option("--ttl <seconds>", "Envelope TTL in seconds", "3600").option("--verbose", "Print the full envelope before sending and the full server response", false).
|
|
2914
|
+
parent.command("accept").description("Accept a PROPOSED delegation \u2014 promotes to ACCEPTED. Counterparty-only.").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("--contract-id <uuid>", "Override the auto-resolved contract id (default: read from the delegation row)").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(
|
|
2915
|
+
"--json",
|
|
2916
|
+
'Machine-readable mode \u2014 emit a single JSON object on stdout ({ok, action:"accept", delegationId, contractId, eventId, relationshipId, relationshipEventIndex, serverTimestamp, serverEventHash}). Prelude + pending-lock poll chatter move off stdout; on failure stderr carries `{code, message}`. Mutually exclusive with --verbose.',
|
|
2917
|
+
false
|
|
2918
|
+
).option(
|
|
2919
|
+
"--no-wait-for-lock",
|
|
2920
|
+
"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)."
|
|
2921
|
+
).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) => {
|
|
2806
2922
|
await runFollowupAction(relationshipId, delegationId, "accept", opts);
|
|
2807
2923
|
});
|
|
2808
2924
|
}
|
|
@@ -2812,20 +2928,33 @@ function registerDecline(parent) {
|
|
|
2812
2928
|
// surface the closed enum at help time so operators
|
|
2813
2929
|
// don't have to read source to find acceptable values.
|
|
2814
2930
|
`Required: decline reason code (one of: ${import_sdk4.DECLINE_REASONS.join(", ")}). Carried in body.content.reason so the counterparty's reactor can branch on it.`
|
|
2815
|
-
).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).
|
|
2931
|
+
).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(
|
|
2932
|
+
"--no-wait-for-lock",
|
|
2933
|
+
"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)."
|
|
2934
|
+
).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) => {
|
|
2816
2935
|
await runFollowupAction(relationshipId, delegationId, "decline", opts);
|
|
2817
2936
|
});
|
|
2818
2937
|
}
|
|
2819
2938
|
function registerCancel(parent) {
|
|
2820
|
-
parent.command("cancel").description("Cancel a PROPOSED 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("--contract-id <uuid>", "Override the auto-resolved contract id (default: read from the delegation row)").option("--ttl <seconds>", "Envelope TTL in seconds", "3600").option("--verbose", "Print the full envelope before sending and the full server response", false).
|
|
2939
|
+
parent.command("cancel").description("Cancel a PROPOSED 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("--contract-id <uuid>", "Override the auto-resolved contract id (default: read from the delegation row)").option("--ttl <seconds>", "Envelope TTL in seconds", "3600").option("--verbose", "Print the full envelope before sending and the full server response", false).option(
|
|
2940
|
+
"--no-wait-for-lock",
|
|
2941
|
+
"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)."
|
|
2942
|
+
).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) => {
|
|
2821
2943
|
await runFollowupAction(relationshipId, delegationId, "cancel", opts);
|
|
2822
2944
|
});
|
|
2823
2945
|
}
|
|
2824
2946
|
async function runFollowupAction(relationshipId, delegationId, action, opts) {
|
|
2825
2947
|
const cmdName = `delegation ${action}`;
|
|
2948
|
+
if (opts.verbose && opts.json) {
|
|
2949
|
+
throw new Error(
|
|
2950
|
+
`${cmdName}: --verbose and --json are mutually exclusive. --json emits the structured server response; --verbose adds dumps that would break \`--json | jq\`.`
|
|
2951
|
+
);
|
|
2952
|
+
}
|
|
2826
2953
|
relationshipId = requireUuidNormalised(cmdName, relationshipId, "<relationship-id>");
|
|
2827
2954
|
delegationId = requireUuidNormalised(cmdName, delegationId, "<delegation-id>");
|
|
2828
2955
|
const ttlSeconds = parseTtl(cmdName, opts.ttl);
|
|
2956
|
+
const lockWaitTimeoutSec = parseLockWaitTimeout(cmdName, opts.lockWaitTimeout);
|
|
2957
|
+
const lockWaitIntervalSec = parseLockWaitInterval(cmdName, opts.lockWaitInterval);
|
|
2829
2958
|
let declinePayload = null;
|
|
2830
2959
|
if (action === "decline") {
|
|
2831
2960
|
const reason = parseDeclineReason(cmdName, opts.reason);
|
|
@@ -2836,6 +2965,19 @@ async function runFollowupAction(relationshipId, delegationId, action, opts) {
|
|
|
2836
2965
|
const sender = resolveSenderAgent(cmdName, opts.server, opts.fromDid);
|
|
2837
2966
|
const signer = makeSigner(sender);
|
|
2838
2967
|
const resolved = await resolveDelegationRefs(cmdName, api, signer, { relationshipId, delegationId, action, selfDid: sender.did, contractIdOverride: opts.contractId });
|
|
2968
|
+
if (resolved.state === "pending_lock_finalization" && opts.waitForLock !== false) {
|
|
2969
|
+
await awaitDelegationLockFinalized(cmdName, api, signer, {
|
|
2970
|
+
relationshipId,
|
|
2971
|
+
delegationId,
|
|
2972
|
+
action,
|
|
2973
|
+
timeoutSec: lockWaitTimeoutSec,
|
|
2974
|
+
intervalSec: lockWaitIntervalSec,
|
|
2975
|
+
// Route poll chatter through `progress` so it's suppressed
|
|
2976
|
+
// in --json mode (keeps stdout pure + stderr reserved for
|
|
2977
|
+
// the structured error object).
|
|
2978
|
+
log: (line) => progress(opts.json, line)
|
|
2979
|
+
});
|
|
2980
|
+
}
|
|
2839
2981
|
const content = {
|
|
2840
2982
|
action,
|
|
2841
2983
|
delegation_id: delegationId,
|
|
@@ -2846,10 +2988,10 @@ async function runFollowupAction(relationshipId, delegationId, action, opts) {
|
|
|
2846
2988
|
if (declinePayload.reasonDetail) content.reason_detail = declinePayload.reasonDetail;
|
|
2847
2989
|
}
|
|
2848
2990
|
const body = { type: "delegation", content };
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2991
|
+
progress(opts.json, import_chalk6.default.dim(`Server: ${api.serverUrl}`));
|
|
2992
|
+
progress(opts.json, import_chalk6.default.dim(`Sender: ${sender.did}`));
|
|
2993
|
+
progress(opts.json, import_chalk6.default.dim(`Relationship: ${relationshipId}`));
|
|
2994
|
+
progress(opts.json, import_chalk6.default.dim(`Delegation: ${delegationId} (action=${action}${action === "decline" ? `, reason=${content.reason}` : ""})`));
|
|
2853
2995
|
const result = await sendDelegationEnvelope({
|
|
2854
2996
|
api,
|
|
2855
2997
|
sender,
|
|
@@ -2859,6 +3001,21 @@ async function runFollowupAction(relationshipId, delegationId, action, opts) {
|
|
|
2859
3001
|
verbose: opts.verbose,
|
|
2860
3002
|
server: opts.server
|
|
2861
3003
|
});
|
|
3004
|
+
if (opts.json) {
|
|
3005
|
+
jsonOut({
|
|
3006
|
+
ok: true,
|
|
3007
|
+
action,
|
|
3008
|
+
delegationId,
|
|
3009
|
+
contractId: resolved.contractId,
|
|
3010
|
+
eventId: result.eventId,
|
|
3011
|
+
relationshipId: result.relationshipId,
|
|
3012
|
+
relationshipEventIndex: result.relationshipEventIndex,
|
|
3013
|
+
serverTimestamp: result.serverTimestamp,
|
|
3014
|
+
serverEventHash: result.serverEventHash,
|
|
3015
|
+
prevServerEventHash: result.prevServerEventHash ?? null
|
|
3016
|
+
});
|
|
3017
|
+
return;
|
|
3018
|
+
}
|
|
2862
3019
|
printIngestResult(result);
|
|
2863
3020
|
}
|
|
2864
3021
|
async function sendDelegationEnvelope(args) {
|
|
@@ -2939,7 +3096,96 @@ async function resolveDelegationRefs(cmdName, api, signer, args) {
|
|
|
2939
3096
|
} else {
|
|
2940
3097
|
recipientDid = row.offererDid;
|
|
2941
3098
|
}
|
|
2942
|
-
return { contractId, recipientDid };
|
|
3099
|
+
return { contractId, recipientDid, state: row.state };
|
|
3100
|
+
}
|
|
3101
|
+
async function awaitDelegationLockFinalized(cmdName, api, signer, args) {
|
|
3102
|
+
const log = args.log ?? ((line) => console.log(line));
|
|
3103
|
+
log(
|
|
3104
|
+
import_chalk6.default.dim(
|
|
3105
|
+
`
|
|
3106
|
+
[--wait-for-lock] delegation ${args.delegationId} is awaiting on-chain lock confirmation; polling until ready (interval=${args.intervalSec}s timeout=${args.timeoutSec}s)`
|
|
3107
|
+
)
|
|
3108
|
+
);
|
|
3109
|
+
const fetchRow = async () => {
|
|
3110
|
+
let after;
|
|
3111
|
+
for (; ; ) {
|
|
3112
|
+
const page = await api.listDelegations(args.relationshipId, signer, { limit: 100, after });
|
|
3113
|
+
const found = page.find((d) => d.delegationId === args.delegationId);
|
|
3114
|
+
if (found) return found;
|
|
3115
|
+
if (page.length < 100) return null;
|
|
3116
|
+
after = page[page.length - 1].id;
|
|
3117
|
+
}
|
|
3118
|
+
};
|
|
3119
|
+
const outcome = await (0, import_sdk4.pollUntil)({
|
|
3120
|
+
fetch: fetchRow,
|
|
3121
|
+
// Match on either "row not found at all" OR "state moved
|
|
3122
|
+
// past pending_lock_finalization". The post-poll branch
|
|
3123
|
+
// disambiguates which condition fired — missing rows error
|
|
3124
|
+
// loud (operator typo or row pruned), present-but-terminal
|
|
3125
|
+
// rows error with the new state, and present-and-actionable
|
|
3126
|
+
// rows return cleanly. Polling on null would loop pointlessly
|
|
3127
|
+
// until the deadline fires and surface a misleading "timed
|
|
3128
|
+
// out" message for what's actually a wrong-id problem.
|
|
3129
|
+
predicate: (row2) => row2 === null || row2.state !== "pending_lock_finalization",
|
|
3130
|
+
intervalMs: args.intervalSec * 1e3,
|
|
3131
|
+
timeoutMs: args.timeoutSec * 1e3,
|
|
3132
|
+
// Swallow ONLY transient errors. A 4xx during the poll is a
|
|
3133
|
+
// new race (auth lost, relationship deleted, shape bug) and
|
|
3134
|
+
// should surface immediately — without this branch the loop
|
|
3135
|
+
// would spin until timeout and report a misleading
|
|
3136
|
+
// "still pending_lock_finalization" reason. 5xx + network
|
|
3137
|
+
// errors are the actual transients we want to ride out.
|
|
3138
|
+
swallowFetchErrors: true,
|
|
3139
|
+
onFetchError: (err) => {
|
|
3140
|
+
if (err instanceof ApiError && err.status >= 400 && err.status < 500) {
|
|
3141
|
+
throw err;
|
|
3142
|
+
}
|
|
3143
|
+
log(import_chalk6.default.dim(` poll: transient fetch error (${err instanceof Error ? err.message : String(err)})`));
|
|
3144
|
+
}
|
|
3145
|
+
});
|
|
3146
|
+
if (outcome.kind === "timeout") {
|
|
3147
|
+
throw new Error(
|
|
3148
|
+
`${cmdName}: --wait-for-lock timed out after ${args.timeoutSec}s; delegation ${args.delegationId} still in pending_lock_finalization. Retry the command (the lock may have confirmed in the meantime), bump --lock-wait-timeout, or pass --no-wait-for-lock to skip the pre-flight and surface the 409 directly.`
|
|
3149
|
+
);
|
|
3150
|
+
}
|
|
3151
|
+
if (outcome.kind === "aborted") {
|
|
3152
|
+
throw new Error(`${cmdName}: --wait-for-lock aborted before delegation ${args.delegationId} exited pending_lock_finalization`);
|
|
3153
|
+
}
|
|
3154
|
+
const row = outcome.value;
|
|
3155
|
+
if (row === null) {
|
|
3156
|
+
throw new Error(
|
|
3157
|
+
`${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}\`.`
|
|
3158
|
+
);
|
|
3159
|
+
}
|
|
3160
|
+
if (row.state !== "offered" && row.state !== "proposed") {
|
|
3161
|
+
throw new Error(
|
|
3162
|
+
`${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.`
|
|
3163
|
+
);
|
|
3164
|
+
}
|
|
3165
|
+
log(import_chalk6.default.dim(`[--wait-for-lock] delegation ${args.delegationId} ready (state=${row.state}); proceeding with ${args.action}.`));
|
|
3166
|
+
return row;
|
|
3167
|
+
}
|
|
3168
|
+
function parseLockWaitTimeout(cmdName, raw) {
|
|
3169
|
+
if (raw === void 0) return 300;
|
|
3170
|
+
const n = Number(raw);
|
|
3171
|
+
if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {
|
|
3172
|
+
throw new Error(`${cmdName}: --lock-wait-timeout must be a positive integer number of seconds (got '${raw}')`);
|
|
3173
|
+
}
|
|
3174
|
+
if (n > 3600) {
|
|
3175
|
+
throw new Error(`${cmdName}: --lock-wait-timeout must be <= 3600 seconds (1h). Got ${n}; if the on-chain lock is taking that long something else is broken.`);
|
|
3176
|
+
}
|
|
3177
|
+
return n;
|
|
3178
|
+
}
|
|
3179
|
+
function parseLockWaitInterval(cmdName, raw) {
|
|
3180
|
+
if (raw === void 0) return 3;
|
|
3181
|
+
const n = Number(raw);
|
|
3182
|
+
if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {
|
|
3183
|
+
throw new Error(`${cmdName}: --lock-wait-interval must be a positive integer number of seconds (got '${raw}')`);
|
|
3184
|
+
}
|
|
3185
|
+
if (n < 1 || n > 60) {
|
|
3186
|
+
throw new Error(`${cmdName}: --lock-wait-interval must be in [1, 60] seconds (got ${n}). Sub-second polling spams the server; > 60s defeats the point of the pre-flight.`);
|
|
3187
|
+
}
|
|
3188
|
+
return n;
|
|
2943
3189
|
}
|
|
2944
3190
|
function printIngestResult(result) {
|
|
2945
3191
|
console.log(import_chalk6.default.green("\nDelivered."));
|
|
@@ -3071,7 +3317,13 @@ function registerPropose(parent) {
|
|
|
3071
3317
|
).option(
|
|
3072
3318
|
"--rate-decimals <n>",
|
|
3073
3319
|
"Decimal places for base-unit conversion (0-18). Required only when --rate-currency is raw CAIP-19; shorthand presets bring their own (USDC=6, SOL=9)."
|
|
3074
|
-
).option("--rate-symbol <s>", 'Optional UI hint ("USDC", "SOL"). Free text, max 16 chars. Shorthand presets set this automatically.').option("--rate-unit <s>", "rate_unit (task|thread|handoff)").option(
|
|
3320
|
+
).option("--rate-symbol <s>", 'Optional UI hint ("USDC", "SOL"). Free text, max 16 chars. Shorthand presets set this automatically.').option("--rate-unit <s>", "rate_unit (task|thread|handoff)").option(
|
|
3321
|
+
"--pricing <s>",
|
|
3322
|
+
"pricing_model (flat|usage_based). `quote` + `subscription` were dropped from the enum because they had no settlement-layer behaviour; add them back when real semantics ship."
|
|
3323
|
+
).option("--settlement <s>", "settlement_model (prepaid|escrow)").option("--delegation-tag <s>", "allowed_delegation_tags \u2014 repeatable; pass --delegation-tag once per tag", collectRepeated2, []).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(
|
|
3324
|
+
"--wait-until <phase>",
|
|
3325
|
+
'Block after delivery until the named FSM phase is reached (e.g. contract.active). One of the UNTIL_PHASES from `heyarp status --help`. Exit code 124 on --wait-timeout. Makes "wait for counterparty to sign" a one-liner instead of a separate `heyarp status --wait --until contract.active` invocation.'
|
|
3326
|
+
).option("--wait-timeout <seconds>", "When --wait-until is set: max wall-clock wait (default 300). Exit code 124 on timeout.").option("--wait-interval <seconds>", "When --wait-until is set: poll cadence (default 3, bound [1, 60]).").option("--wait-verbose", "When --wait-until is set: emit one dim line per poll tick.", false).action(async (recipientDid, opts) => {
|
|
3075
3327
|
await runPropose(recipientDid, opts);
|
|
3076
3328
|
});
|
|
3077
3329
|
}
|
|
@@ -3102,9 +3354,29 @@ async function runPropose(recipientDid, opts) {
|
|
|
3102
3354
|
Reference this contract on subsequent calls with: heyarp contract <action> ${result.relationshipId} ${contractId}`));
|
|
3103
3355
|
console.log(import_chalk7.default.dim(` e.g. (counterparty signs) : heyarp contract sign ${result.relationshipId} ${contractId}`));
|
|
3104
3356
|
console.log(import_chalk7.default.dim(` e.g. (counterparty counters): heyarp contract counter ${result.relationshipId} ${contractId} --rate-amount <new>`));
|
|
3357
|
+
if (opts.waitUntil) {
|
|
3358
|
+
const untilPhase = parseUntilPhase(opts.waitUntil);
|
|
3359
|
+
if (untilPhase === void 0) {
|
|
3360
|
+
throw new Error(`contract propose: --wait-until requires a phase value (got ${JSON.stringify(opts.waitUntil)})`);
|
|
3361
|
+
}
|
|
3362
|
+
await awaitFsmTransitionAfterAction({
|
|
3363
|
+
api,
|
|
3364
|
+
signerDid: sender.did,
|
|
3365
|
+
signer: makeSigner(sender),
|
|
3366
|
+
relationshipId: result.relationshipId,
|
|
3367
|
+
untilPhase,
|
|
3368
|
+
waitIntervalSec: parseWaitInterval(opts.waitInterval),
|
|
3369
|
+
waitTimeoutSec: parseWaitTimeout(opts.waitTimeout),
|
|
3370
|
+
waitVerbose: !!opts.waitVerbose,
|
|
3371
|
+
json: false
|
|
3372
|
+
});
|
|
3373
|
+
}
|
|
3105
3374
|
}
|
|
3106
3375
|
function registerCounter(parent) {
|
|
3107
|
-
parent.command("counter").description("Reply with revised terms to the latest PROPOSED version of <contract-id> in <relationship-id>.").argument("<relationship-id>", "Relationship UUID").argument("<contract-id>", "Contract UUID (proposed earlier in this relationship)").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("--version <n>", "Target the previous version explicitly (default: auto-resolve to the latest PROPOSED)").option("--scope <s>", "scope_summary \u2014 short prose describing the revised terms").option("--rate-amount <s>", "rate_amount \u2014 decimal as string. REQUIRES --rate-currency if set.").option("--rate-currency <s>", `Asset identifier: shorthand (${import_sdk5.WELL_KNOWN_ASSET_KEYS.join("|")}) OR raw CAIP-19 string.`).option("--rate-decimals <n>", "Decimal places for base-unit conversion (0-18). Required only when --rate-currency is raw CAIP-19.").option("--rate-symbol <s>", 'Optional UI hint ("USDC", "SOL"). Max 16 chars.').option("--rate-unit <s>", "rate_unit (task|thread|handoff)").option(
|
|
3376
|
+
parent.command("counter").description("Reply with revised terms to the latest PROPOSED version of <contract-id> in <relationship-id>.").argument("<relationship-id>", "Relationship UUID").argument("<contract-id>", "Contract UUID (proposed earlier in this relationship)").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("--version <n>", "Target the previous version explicitly (default: auto-resolve to the latest PROPOSED)").option("--scope <s>", "scope_summary \u2014 short prose describing the revised terms").option("--rate-amount <s>", "rate_amount \u2014 decimal as string. REQUIRES --rate-currency if set.").option("--rate-currency <s>", `Asset identifier: shorthand (${import_sdk5.WELL_KNOWN_ASSET_KEYS.join("|")}) OR raw CAIP-19 string.`).option("--rate-decimals <n>", "Decimal places for base-unit conversion (0-18). Required only when --rate-currency is raw CAIP-19.").option("--rate-symbol <s>", 'Optional UI hint ("USDC", "SOL"). Max 16 chars.').option("--rate-unit <s>", "rate_unit (task|thread|handoff)").option(
|
|
3377
|
+
"--pricing <s>",
|
|
3378
|
+
"pricing_model (flat|usage_based). `quote` + `subscription` were dropped from the enum because they had no settlement-layer behaviour; add them back when real semantics ship."
|
|
3379
|
+
).option("--settlement <s>", "settlement_model (prepaid|escrow)").option("--delegation-tag <s>", "allowed_delegation_tags \u2014 repeatable", collectRepeated2, []).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, contractId, opts) => {
|
|
3108
3380
|
await runCounter(relationshipId, contractId, opts);
|
|
3109
3381
|
});
|
|
3110
3382
|
}
|
|
@@ -3144,7 +3416,11 @@ async function runCounter(relationshipId, contractId, opts) {
|
|
|
3144
3416
|
printIngestResult2(result);
|
|
3145
3417
|
}
|
|
3146
3418
|
function registerSign(parent) {
|
|
3147
|
-
parent.command("sign").description("Promote the latest PROPOSED version of <contract-id> in <relationship-id> to ACTIVE.").argument("<relationship-id>", "Relationship UUID").argument("<contract-id>", "Contract 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("--version <n>", "Target the version explicitly (default: auto-resolve to the latest PROPOSED)").option("--ttl <seconds>", "Envelope TTL in seconds", "3600").option("--verbose", "Print the full envelope before sending and the full server response", false).
|
|
3419
|
+
parent.command("sign").description("Promote the latest PROPOSED version of <contract-id> in <relationship-id> to ACTIVE.").argument("<relationship-id>", "Relationship UUID").argument("<contract-id>", "Contract 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("--version <n>", "Target the version explicitly (default: auto-resolve to the latest PROPOSED)").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(
|
|
3420
|
+
"--json",
|
|
3421
|
+
'Machine-readable mode \u2014 emit a single JSON object on stdout ({ok, action:"sign", contractId, version, eventId, relationshipId, relationshipEventIndex, serverTimestamp, serverEventHash}). Prelude moves off stdout; on failure stderr carries `{code, message}`. Mutually exclusive with --verbose.',
|
|
3422
|
+
false
|
|
3423
|
+
).action(async (relationshipId, contractId, opts) => {
|
|
3148
3424
|
await runSignOrDecline(relationshipId, contractId, "sign", opts);
|
|
3149
3425
|
});
|
|
3150
3426
|
}
|
|
@@ -3162,6 +3438,11 @@ function registerDecline2(parent) {
|
|
|
3162
3438
|
}
|
|
3163
3439
|
async function runSignOrDecline(relationshipId, contractId, action, opts) {
|
|
3164
3440
|
const cmdName = `contract ${action}`;
|
|
3441
|
+
if (opts.verbose && opts.json) {
|
|
3442
|
+
throw new Error(
|
|
3443
|
+
`${cmdName}: --verbose and --json are mutually exclusive. --json emits the structured server response; --verbose adds dumps that would break \`--json | jq\`.`
|
|
3444
|
+
);
|
|
3445
|
+
}
|
|
3165
3446
|
relationshipId = requireUuidNormalised(cmdName, relationshipId, "<relationship-id>");
|
|
3166
3447
|
contractId = requireUuidNormalised(cmdName, contractId, "<contract-id>");
|
|
3167
3448
|
const ttlSeconds = parseTtl2(cmdName, opts.ttl);
|
|
@@ -3186,10 +3467,10 @@ async function runSignOrDecline(relationshipId, contractId, action, opts) {
|
|
|
3186
3467
|
if (declinePayload.reasonDetail) content.reason_detail = declinePayload.reasonDetail;
|
|
3187
3468
|
}
|
|
3188
3469
|
const body = { type: "contract", content };
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3470
|
+
progress(opts.json, import_chalk7.default.dim(`Server: ${api.serverUrl}`));
|
|
3471
|
+
progress(opts.json, import_chalk7.default.dim(`Sender: ${sender.did}`));
|
|
3472
|
+
progress(opts.json, import_chalk7.default.dim(`Relationship: ${relationshipId}`));
|
|
3473
|
+
progress(opts.json, import_chalk7.default.dim(`Contract id: ${contractId} (v${targetVersion}, action=${action}${action === "decline" ? `, reason=${content.reason}` : ""})`));
|
|
3193
3474
|
const result = await sendContractEnvelope({
|
|
3194
3475
|
api,
|
|
3195
3476
|
sender,
|
|
@@ -3199,6 +3480,21 @@ async function runSignOrDecline(relationshipId, contractId, action, opts) {
|
|
|
3199
3480
|
verbose: opts.verbose,
|
|
3200
3481
|
server: opts.server
|
|
3201
3482
|
});
|
|
3483
|
+
if (opts.json) {
|
|
3484
|
+
jsonOut({
|
|
3485
|
+
ok: true,
|
|
3486
|
+
action,
|
|
3487
|
+
contractId,
|
|
3488
|
+
version: targetVersion,
|
|
3489
|
+
eventId: result.eventId,
|
|
3490
|
+
relationshipId: result.relationshipId,
|
|
3491
|
+
relationshipEventIndex: result.relationshipEventIndex,
|
|
3492
|
+
serverTimestamp: result.serverTimestamp,
|
|
3493
|
+
serverEventHash: result.serverEventHash,
|
|
3494
|
+
prevServerEventHash: result.prevServerEventHash ?? null
|
|
3495
|
+
});
|
|
3496
|
+
return;
|
|
3497
|
+
}
|
|
3202
3498
|
printIngestResult2(result);
|
|
3203
3499
|
}
|
|
3204
3500
|
async function sendContractEnvelope(args) {
|
|
@@ -3549,11 +3845,9 @@ async function runDelegations(relationshipId, opts) {
|
|
|
3549
3845
|
const state = parseState2(opts.state);
|
|
3550
3846
|
const api = new ArpApiClient(opts.server);
|
|
3551
3847
|
const sender = resolveSenderAgent("delegations", opts.server, opts.fromDid);
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
console.log(import_chalk9.default.dim(`Relationship: ${relationshipId}`));
|
|
3556
|
-
}
|
|
3848
|
+
progress(opts.json, import_chalk9.default.dim(`Server: ${api.serverUrl}`));
|
|
3849
|
+
progress(opts.json, import_chalk9.default.dim(`Signer: ${sender.did}`));
|
|
3850
|
+
progress(opts.json, import_chalk9.default.dim(`Relationship: ${relationshipId}`));
|
|
3557
3851
|
const query = { limit };
|
|
3558
3852
|
if (state) query.state = state;
|
|
3559
3853
|
if (opts.contractId) query.contractId = opts.contractId;
|
|
@@ -3561,7 +3855,7 @@ async function runDelegations(relationshipId, opts) {
|
|
|
3561
3855
|
const signer = makeSigner(sender);
|
|
3562
3856
|
const rows = await api.listDelegations(relationshipId, signer, query);
|
|
3563
3857
|
if (opts.json) {
|
|
3564
|
-
|
|
3858
|
+
jsonOut(rows);
|
|
3565
3859
|
return;
|
|
3566
3860
|
}
|
|
3567
3861
|
if (rows.length === 0) {
|
|
@@ -3667,15 +3961,104 @@ function parseLimit3(raw) {
|
|
|
3667
3961
|
var import_sdk6 = require("@heyanon-arp/sdk");
|
|
3668
3962
|
init_api();
|
|
3669
3963
|
function registerDidDocCommand(root) {
|
|
3670
|
-
root.command("did-doc").description(
|
|
3964
|
+
root.command("did-doc").description(
|
|
3965
|
+
"Fetch and pretty-print the DID document for a did:arp:<...>. Pass `--field <path>` to extract a single value (verification keys, endpoints, etc.) without piping through jq."
|
|
3966
|
+
).argument("<did>", "did:arp:<base58btc> identifier").option("--server <url>", "Override ARP server base URL").option("--json", "Emit the DID document as JSON on stdout (default; accepted for symmetry with other commands).", false).option(
|
|
3967
|
+
"--field <path>",
|
|
3968
|
+
"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`). 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."
|
|
3969
|
+
).action(async (did, opts) => {
|
|
3671
3970
|
if (!(0, import_sdk6.isValidDid)(did)) {
|
|
3672
3971
|
throw new Error(`'${did}' is not a syntactically valid did:arp identifier`);
|
|
3673
3972
|
}
|
|
3973
|
+
if (opts.json && opts.field !== void 0) {
|
|
3974
|
+
throw new Error("did-doc: --json and --field are mutually exclusive (--field already controls the output shape; --json emits the full doc).");
|
|
3975
|
+
}
|
|
3674
3976
|
const api = new ArpApiClient(opts.server);
|
|
3675
3977
|
const doc = await api.getDidDocument(did);
|
|
3676
|
-
|
|
3978
|
+
if (opts.field !== void 0) {
|
|
3979
|
+
emitField(doc, opts.field);
|
|
3980
|
+
return;
|
|
3981
|
+
}
|
|
3982
|
+
jsonOut(doc);
|
|
3677
3983
|
});
|
|
3678
3984
|
}
|
|
3985
|
+
function extractField(doc, path) {
|
|
3986
|
+
const parts = path.split(".").filter((p) => p.length > 0);
|
|
3987
|
+
if (parts.length === 0) {
|
|
3988
|
+
throw new Error("did-doc: --field path is empty");
|
|
3989
|
+
}
|
|
3990
|
+
let current = doc;
|
|
3991
|
+
const walked = [];
|
|
3992
|
+
for (const part of parts) {
|
|
3993
|
+
walked.push(part);
|
|
3994
|
+
if (current === null || current === void 0) {
|
|
3995
|
+
throw new Error(
|
|
3996
|
+
`did-doc: --field '${path}': segment '${walked.join(".")}' walked into ${current === null ? "null" : "undefined"} \u2014 the preceding path returned nothing to descend into`
|
|
3997
|
+
);
|
|
3998
|
+
}
|
|
3999
|
+
if (part.startsWith("#")) {
|
|
4000
|
+
if (!Array.isArray(current)) {
|
|
4001
|
+
throw new Error(
|
|
4002
|
+
`did-doc: --field '${path}': fragment selector '${part}' requires an array at '${walked.slice(0, -1).join(".") || "<root>"}', got ${describeShape(current)}`
|
|
4003
|
+
);
|
|
4004
|
+
}
|
|
4005
|
+
const match = current.find((el) => isRecord(el) && typeof el.id === "string" && (el.id === part || el.id.endsWith(part)));
|
|
4006
|
+
if (match === void 0) {
|
|
4007
|
+
const ids = current.map((el) => isRecord(el) && typeof el.id === "string" ? el.id : "(no id)").join(", ");
|
|
4008
|
+
throw new Error(`did-doc: --field '${path}': no array element with id matching '${part}' (available ids: ${ids || "<empty array>"})`);
|
|
4009
|
+
}
|
|
4010
|
+
current = match;
|
|
4011
|
+
} else if (/^\d+$/.test(part)) {
|
|
4012
|
+
if (!Array.isArray(current)) {
|
|
4013
|
+
throw new Error(
|
|
4014
|
+
`did-doc: --field '${path}': numeric index '${part}' requires an array at '${walked.slice(0, -1).join(".") || "<root>"}', got ${describeShape(current)}`
|
|
4015
|
+
);
|
|
4016
|
+
}
|
|
4017
|
+
const idx = Number.parseInt(part, 10);
|
|
4018
|
+
if (idx >= current.length) {
|
|
4019
|
+
throw new Error(`did-doc: --field '${path}': index ${idx} out of bounds (array length ${current.length})`);
|
|
4020
|
+
}
|
|
4021
|
+
current = current[idx];
|
|
4022
|
+
} else {
|
|
4023
|
+
if (!isRecord(current)) {
|
|
4024
|
+
throw new Error(`did-doc: --field '${path}': cannot read property '${part}' from ${describeShape(current)} at '${walked.slice(0, -1).join(".") || "<root>"}'`);
|
|
4025
|
+
}
|
|
4026
|
+
if (!Object.prototype.hasOwnProperty.call(current, part)) {
|
|
4027
|
+
throw new Error(
|
|
4028
|
+
`did-doc: --field '${path}': no property '${part}' at '${walked.slice(0, -1).join(".") || "<root>"}' (available: ${Object.keys(current).join(", ")})`
|
|
4029
|
+
);
|
|
4030
|
+
}
|
|
4031
|
+
current = current[part];
|
|
4032
|
+
}
|
|
4033
|
+
}
|
|
4034
|
+
return current;
|
|
4035
|
+
}
|
|
4036
|
+
function emitField(doc, path) {
|
|
4037
|
+
const value = extractField(doc, path);
|
|
4038
|
+
if (value === void 0) {
|
|
4039
|
+
throw new Error(`did-doc: --field '${path}' resolved to undefined \u2014 the DID document has an explicit undefined at this path (probably an issuer bug; please report)`);
|
|
4040
|
+
}
|
|
4041
|
+
const t = typeof value;
|
|
4042
|
+
if (t === "string") {
|
|
4043
|
+
process.stdout.write(`${value}
|
|
4044
|
+
`);
|
|
4045
|
+
return;
|
|
4046
|
+
}
|
|
4047
|
+
if (t === "number" || t === "boolean") {
|
|
4048
|
+
process.stdout.write(`${String(value)}
|
|
4049
|
+
`);
|
|
4050
|
+
return;
|
|
4051
|
+
}
|
|
4052
|
+
jsonOut(value);
|
|
4053
|
+
}
|
|
4054
|
+
function isRecord(value) {
|
|
4055
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4056
|
+
}
|
|
4057
|
+
function describeShape(value) {
|
|
4058
|
+
if (value === null) return "null";
|
|
4059
|
+
if (Array.isArray(value)) return "array";
|
|
4060
|
+
return typeof value;
|
|
4061
|
+
}
|
|
3679
4062
|
|
|
3680
4063
|
// src/commands/doctor.ts
|
|
3681
4064
|
var import_sdk7 = require("@heyanon-arp/sdk");
|
|
@@ -3901,7 +4284,9 @@ function registerEscrowCommands(root) {
|
|
|
3901
4284
|
cmd.command("info").description("Show the on-chain protocol fee config + cluster + pause state. Public read; no auth.").option("--server <url>", "Override ARP server base URL").option("--json", "Machine-readable JSON output", false).action(async (opts) => {
|
|
3902
4285
|
await runEscrowInfo(opts);
|
|
3903
4286
|
});
|
|
3904
|
-
cmd.command("derive-condition-hash").description(
|
|
4287
|
+
cmd.command("derive-condition-hash").description(
|
|
4288
|
+
"Compute canonical condition_hash (32-byte hex) for `heyarp wallet create-lock --condition-hash <hex>`. Fetches the contract row, projects to the canonical subset, and hashes via SDK deriveConditionHash. Mirrors what the on-chain create_lock instruction binds."
|
|
4289
|
+
).argument("<relationship-id>", "Relationship UUID hosting the contract").argument("<contract-id>", "Contract 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("--version <n>", "Pin a specific contract version (default: latest non-replaced row)").option("--json", "Machine-readable JSON: {contract_id, version, condition_hash_hex, projected_subset}", false).action(async (relationshipId, contractId, opts) => {
|
|
3905
4290
|
await runDeriveConditionHash(relationshipId, contractId, opts);
|
|
3906
4291
|
});
|
|
3907
4292
|
cmd.command("recover-sequence").description(
|
|
@@ -3914,7 +4299,8 @@ async function runEscrowInfo(opts) {
|
|
|
3914
4299
|
const api = new ArpApiClient(opts.server);
|
|
3915
4300
|
const status = await api.getProtocolFee();
|
|
3916
4301
|
if (opts.json) {
|
|
3917
|
-
|
|
4302
|
+
progress(true, import_chalk12.default.dim(`Server: ${api.serverUrl}`));
|
|
4303
|
+
jsonOut(status);
|
|
3918
4304
|
} else {
|
|
3919
4305
|
console.log(formatProtocolFeeStatus(api.serverUrl, status));
|
|
3920
4306
|
}
|
|
@@ -4005,6 +4391,8 @@ async function runDeriveConditionHash(relationshipId, contractId, opts) {
|
|
|
4005
4391
|
const api = new ArpApiClient(opts.server);
|
|
4006
4392
|
const sender = resolveSenderAgent("escrow derive-condition-hash", opts.server, opts.fromDid);
|
|
4007
4393
|
const signer = makeSigner(sender);
|
|
4394
|
+
progress(opts.json, import_chalk12.default.dim(`Server: ${api.serverUrl}`));
|
|
4395
|
+
progress(opts.json, import_chalk12.default.dim(`Signer: ${sender.did}`));
|
|
4008
4396
|
let contract;
|
|
4009
4397
|
try {
|
|
4010
4398
|
contract = await findContractRow(api, signer, relationshipId, contractId, versionPin);
|
|
@@ -4018,14 +4406,12 @@ async function runDeriveConditionHash(relationshipId, contractId, opts) {
|
|
|
4018
4406
|
const hashBytes = (0, import_sdk8.deriveConditionHash)(subset);
|
|
4019
4407
|
const hex = (0, import_utils2.bytesToHex)(hashBytes);
|
|
4020
4408
|
if (opts.json) {
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
})
|
|
4028
|
-
);
|
|
4409
|
+
jsonOut({
|
|
4410
|
+
contract_id: contract.contractId,
|
|
4411
|
+
version: contract.version,
|
|
4412
|
+
condition_hash_hex: hex,
|
|
4413
|
+
projected_subset: subset
|
|
4414
|
+
});
|
|
4029
4415
|
return;
|
|
4030
4416
|
}
|
|
4031
4417
|
console.log(import_chalk12.default.dim(`Relationship: ${relationshipId}`));
|
|
@@ -4065,16 +4451,14 @@ async function runRecoverSequence(opts) {
|
|
|
4065
4451
|
}
|
|
4066
4452
|
const outcome = classifyRecoverOutcome(local, server);
|
|
4067
4453
|
if (opts.json) {
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
})
|
|
4077
|
-
);
|
|
4454
|
+
jsonOut({
|
|
4455
|
+
did: sender.did,
|
|
4456
|
+
local,
|
|
4457
|
+
server,
|
|
4458
|
+
...outcome.kind !== "in-sync" ? { drift: outcome.kind === "behind" ? outcome.drift : -outcome.drift } : {},
|
|
4459
|
+
kind: outcome.kind,
|
|
4460
|
+
applied: opts.apply === true && outcome.kind === "behind"
|
|
4461
|
+
});
|
|
4078
4462
|
} else {
|
|
4079
4463
|
console.log(import_chalk12.default.dim(`DID: ${sender.did}`));
|
|
4080
4464
|
console.log(import_chalk12.default.dim(`Server: ${api.serverUrl}`));
|
|
@@ -4244,191 +4628,539 @@ function parseSince(raw) {
|
|
|
4244
4628
|
return n;
|
|
4245
4629
|
}
|
|
4246
4630
|
|
|
4247
|
-
// src/commands/
|
|
4631
|
+
// src/commands/examples.ts
|
|
4632
|
+
var import_node_fs5 = require("fs");
|
|
4633
|
+
var import_node_path4 = require("path");
|
|
4248
4634
|
var import_chalk14 = __toESM(require("chalk"));
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4635
|
+
var EXAMPLES = [
|
|
4636
|
+
{
|
|
4637
|
+
name: "worker",
|
|
4638
|
+
filename: "worker-template.py",
|
|
4639
|
+
description: "Autonomous Python worker (handshake \u2192 contract \u2192 delegation \u2192 work \u2192 receipt \u2192 settlement, all auto-mediated; fill in handle_work_request)"
|
|
4640
|
+
}
|
|
4641
|
+
];
|
|
4642
|
+
var EXAMPLES_DIR = (0, import_node_path4.resolve)(__dirname, "..", "examples");
|
|
4643
|
+
function registerExamplesCommand(root) {
|
|
4644
|
+
const examples = root.command("examples").description("Bundled reference templates (worker, etc.) \u2014 discover, print, or copy to disk. Templates ship inside the npm tarball; no GitHub round-trip required.");
|
|
4645
|
+
examples.command("list").description("List bundled examples. Pair with `heyarp examples show <name>` or `heyarp examples copy <name>`.").option("--json", "JSON output (jq-pipeable)", false).action((opts, cmd) => {
|
|
4646
|
+
try {
|
|
4647
|
+
if (opts.json) {
|
|
4648
|
+
console.log(formatJson(EXAMPLES));
|
|
4649
|
+
return;
|
|
4650
|
+
}
|
|
4651
|
+
console.log(import_chalk14.default.bold("Bundled examples:"));
|
|
4652
|
+
for (const e of EXAMPLES) {
|
|
4653
|
+
console.log(` ${import_chalk14.default.cyan(e.name).padEnd(20)} ${import_chalk14.default.dim(e.filename)}`);
|
|
4654
|
+
console.log(` ${e.description}`);
|
|
4655
|
+
}
|
|
4656
|
+
console.log(import_chalk14.default.dim("\nShow contents: heyarp examples show <name>"));
|
|
4657
|
+
console.log(import_chalk14.default.dim("Save to disk: heyarp examples copy <name> --output ./<filename>"));
|
|
4658
|
+
} catch (err) {
|
|
4659
|
+
emitActionError(err, cmd);
|
|
4660
|
+
process.exitCode = 1;
|
|
4661
|
+
}
|
|
4662
|
+
});
|
|
4663
|
+
examples.command("show").description("Print the example's contents to stdout. Pipe-friendly \u2014 `heyarp examples show worker > my-worker.py` is the lowest-friction install path.").argument("<name>", `Example name (one of: ${EXAMPLES.map((e) => e.name).join(", ")})`).action((name, _opts, cmd) => {
|
|
4664
|
+
try {
|
|
4665
|
+
const entry = lookupOrThrow(name);
|
|
4666
|
+
const contents = readExampleOrThrow(entry);
|
|
4667
|
+
process.stdout.write(contents);
|
|
4668
|
+
} catch (err) {
|
|
4669
|
+
emitActionError(err, cmd);
|
|
4670
|
+
process.exitCode = 1;
|
|
4671
|
+
}
|
|
4672
|
+
});
|
|
4673
|
+
examples.command("copy").description("Copy the example to a path on disk. Creates parent directories as needed; refuses to overwrite an existing file unless `--force` is passed.").argument("<name>", `Example name (one of: ${EXAMPLES.map((e) => e.name).join(", ")})`).option("--output <path>", "Destination file path. Defaults to the bundled filename in the current working directory.").option("--force", "Overwrite the destination file if it already exists.", false).action((name, opts, cmd) => {
|
|
4674
|
+
try {
|
|
4675
|
+
const entry = lookupOrThrow(name);
|
|
4676
|
+
const contents = readExampleOrThrow(entry);
|
|
4677
|
+
const destPath = (0, import_node_path4.resolve)(process.cwd(), opts.output ?? entry.filename);
|
|
4678
|
+
if ((0, import_node_fs5.existsSync)(destPath) && !opts.force) {
|
|
4679
|
+
throw new Error(`refusing to overwrite ${destPath} (pass --force to override)`);
|
|
4680
|
+
}
|
|
4681
|
+
const parentDir = (0, import_node_path4.dirname)(destPath);
|
|
4682
|
+
if (!(0, import_node_fs5.existsSync)(parentDir)) {
|
|
4683
|
+
(0, import_node_fs5.mkdirSync)(parentDir, { recursive: true });
|
|
4684
|
+
}
|
|
4685
|
+
(0, import_node_fs5.writeFileSync)(destPath, contents);
|
|
4686
|
+
console.log(`${import_chalk14.default.green("Wrote")} ${import_chalk14.default.cyan(destPath)} ${import_chalk14.default.dim(`(${contents.length} bytes)`)}`);
|
|
4687
|
+
} catch (err) {
|
|
4688
|
+
emitActionError(err, cmd);
|
|
4689
|
+
process.exitCode = 1;
|
|
4690
|
+
}
|
|
4252
4691
|
});
|
|
4253
4692
|
}
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
" contract propose buyer \u2192 worker terms (rate, scope, \u2026)",
|
|
4271
|
-
" contract sign worker \u2192 buyer \u2192 state=active",
|
|
4272
|
-
" delegation offer buyer \u2192 worker concrete task + amount",
|
|
4273
|
-
" delegation accept worker \u2192 buyer \u2192 state=accepted",
|
|
4274
|
-
" work request buyer \u2192 worker params payload",
|
|
4275
|
-
" work respond worker \u2192 buyer output OR error",
|
|
4276
|
-
" receipt propose worker \u2192 buyer verdict + body hashes",
|
|
4277
|
-
" receipt cosign buyer \u2192 worker \u2192 state=cosigned \u2713 DONE",
|
|
4278
|
-
"",
|
|
4279
|
-
" Skipping any stage = the next stage rejects with a state-machine error.",
|
|
4280
|
-
" `receipt cosign` is the closure \u2014 without it the work isn't paid for.",
|
|
4281
|
-
"",
|
|
4282
|
-
import_chalk14.default.bold("3. Escrow (how funds actually move)"),
|
|
4283
|
-
" `delegation offer` attaches a signed Solana `create_lock` tx blob \u2014 the",
|
|
4284
|
-
" buyer FUNDS escrow up-front before the worker accepts. The worker ",
|
|
4285
|
-
" commits work knowing the cash is already locked.",
|
|
4286
|
-
" `receipt cosign` carries SETTLEMENT SIGNATURES (Ed25519 over a canonical",
|
|
4287
|
-
" digest) from BOTH parties \u2014 these unlock `release_lock` on-chain.",
|
|
4288
|
-
" Refund paths:",
|
|
4289
|
-
" \u2022 PayerCancellation \u2014 buyer cancels within 10min of offer (1 sig)",
|
|
4290
|
-
" \u2022 BothPartiesAgreed \u2014 bilateral cooperative refund (2 sigs)",
|
|
4291
|
-
" \u2022 Expired \u2014 permissionless after lock.expiry passes (no sigs)",
|
|
4292
|
-
" \u2022 DisputeResolution \u2014 admin split via multisig (V1 backend-only)",
|
|
4293
|
-
"",
|
|
4294
|
-
import_chalk14.default.bold("4. Wallet + escrow commands (native SOL today; SPL + Token-2022 in a later slice)"),
|
|
4295
|
-
" `heyarp wallet create-lock` builds + signs a `create_lock` Solana tx.",
|
|
4296
|
-
" Output JSON: {signed_tx_blob, lock_id (32-byte hex), amount, asset_id,",
|
|
4297
|
-
" expiry, delegation_id, program_id}. Pipe via `delegation offer",
|
|
4298
|
-
" --escrow-lock-from-file <path>` \u2014 delegation_id auto-aligns. Use",
|
|
4299
|
-
" `--expiry-secs $(($(date +%s) + 86400*3))` (\u22653d) \u2014 server enforces",
|
|
4300
|
-
" lock.expiry \u2265 deadline + DISPUTE_BUFFER (1d).",
|
|
4301
|
-
" `heyarp wallet derive-pdas --delegation-id <id>` returns the",
|
|
4302
|
-
" deterministic PDAs for ON-CHAIN VERIFICATION:",
|
|
4303
|
-
" {lock_id_hex, program_id, lock_pda, escrow_pda, config_pda,",
|
|
4304
|
-
" event_authority_pda}. `escrow_pda` holds the escrowed funds;",
|
|
4305
|
-
" `config_pda` is the singleton program config; `event_authority_pda`",
|
|
4306
|
-
" is the anchor `#[event_cpi]` self-CPI target.",
|
|
4307
|
-
" `heyarp escrow derive-condition-hash <rel-id> <contract-id>` computes",
|
|
4308
|
-
" the canonical condition_hash for `wallet create-lock --condition-hash`.",
|
|
4309
|
-
" `heyarp wallet sign-settlement-release` signs the release / partial",
|
|
4310
|
-
" digest. Output `sig` is RAW base64 (NO `ed25519:` prefix). Pass",
|
|
4311
|
-
" `--partial-payee-amount <lamports>` to switch the digest to",
|
|
4312
|
-
" `ARP-SOLANA-PARTIAL-RELEASE-v1.5`.",
|
|
4313
|
-
" `heyarp receipt cosign` attaches both parties' signatures into",
|
|
4314
|
-
" `attachments.settlement_signatures` via --settlement-purpose,",
|
|
4315
|
-
" --settlement-expires-at, --payer-settlement-{pubkey,sig},",
|
|
4316
|
-
" --payee-settlement-{pubkey,sig} (+ --settlement-payee-amount for",
|
|
4317
|
-
" partial). Server authorises on-chain release.",
|
|
4318
|
-
" `heyarp wallet verify-release --delegation-id <id> --json` is the",
|
|
4319
|
-
" post-cycle on-chain assertion. Returns {status, release_method,",
|
|
4320
|
-
" lock_state, released, \u2026}. The R15 contract does NOT close the lock",
|
|
4321
|
-
" account on release \u2014 `released: true` is decided from the state byte",
|
|
4322
|
-
" at offset 185: 1\u2192released_clean, 4\u2192released_partial, 2\u2192released_refunded.",
|
|
4323
|
-
" `lock_account_exists: true` post-release is expected, not a bug.",
|
|
4324
|
-
"",
|
|
4325
|
-
import_chalk14.default.bold("5. Discovery"),
|
|
4326
|
-
" `heyarp agents --tag X --tag Y --query Z` \u2014 public catalog, no auth.",
|
|
4327
|
-
" AND-semantics across tags. Returns `did:arp:\u2026` DIDs you can hand to",
|
|
4328
|
-
" `heyarp send-handshake`. Skip the `--query` filter if your tags are",
|
|
4329
|
-
" specific enough; full-text search hits a Mongo `$text` index that needs",
|
|
4330
|
-
" the right shape (server returns 500 if it's misconfigured, you can't do",
|
|
4331
|
-
" much from the CLI side).",
|
|
4332
|
-
"",
|
|
4333
|
-
import_chalk14.default.bold("6. Multi-DID disambiguation"),
|
|
4334
|
-
" With >1 agent registered locally for one server, `--from-did` is",
|
|
4335
|
-
" REQUIRED on every signed command. The resolver does NOT silently pick",
|
|
4336
|
-
" one \u2014 it fails with the candidate list. Sole-agent setups auto-pick.",
|
|
4337
|
-
"",
|
|
4338
|
-
import_chalk14.default.bold("7. Recovering full IDs / hashes"),
|
|
4339
|
-
" List commands truncate `did:arp:abc\u2026xyz` and `sha256:abc\u2026xyz` for",
|
|
4340
|
-
" readability. To get full values for the next command:",
|
|
4341
|
-
" \u2022 `--full-ids` prints UUIDs / DIDs / hashes uncut",
|
|
4342
|
-
" \u2022 `--verbose` appends a per-row JSON dump with full payload",
|
|
4343
|
-
" \u2022 `--json` machine-readable array for piping into `jq`",
|
|
4344
|
-
" For ONE envelope by id (cited in a receipt, copied from inbox):",
|
|
4345
|
-
" \u2022 `heyarp envelope <event-id> --json | jq` \u2014 single signed read.",
|
|
4346
|
-
"",
|
|
4347
|
-
import_chalk14.default.bold("8. Live tail vs polling"),
|
|
4348
|
-
" `heyarp inbox --tail` opens an SSE stream and prints each new envelope",
|
|
4349
|
-
" as it arrives. Use this for long-running worker processes \u2014 DO NOT",
|
|
4350
|
-
" bash-loop `heyarp inbox` every 5s, that's self-DDoS at scale.",
|
|
4351
|
-
" `stream ended unexpectedly` (exit \u2260 0) = server EOF; re-run to reconnect.",
|
|
4352
|
-
" `stream closed.` (exit 0) = your Ctrl-C / SIGTERM; nothing to fix.",
|
|
4353
|
-
" For scripted phase-anchored waits, prefer `status --wait --until`:",
|
|
4354
|
-
" heyarp status <rel-id> --wait --until contract.active --wait-timeout 600",
|
|
4355
|
-
" heyarp status <rel-id> --wait --until delegation.accepted",
|
|
4356
|
-
" heyarp status <rel-id> --wait --until receipt.cosigned",
|
|
4357
|
-
" Exits 0 on transition, 124 on timeout or terminal-without-match.",
|
|
4358
|
-
" If you must poll (no SSE, no --wait), persist a cursor:",
|
|
4359
|
-
" heyarp inbox --since <last-ts> --since-event-id <last-evt> --json",
|
|
4360
|
-
" Without it, restarted pollers re-fire on already-handled events.",
|
|
4361
|
-
"",
|
|
4362
|
-
import_chalk14.default.bold("9. Receipt closure semantics + settlement signatures"),
|
|
4363
|
-
" - The PAYEE proposes (`heyarp receipt propose`) with their verdict +",
|
|
4364
|
-
" <request-hash> + <response-hash>. These are SHA-256 of the",
|
|
4365
|
-
" canonical JSON of the work_request / work_response body (NOT the",
|
|
4366
|
-
" chain-anchor `serverEventHash`).",
|
|
4367
|
-
" - On the PAYEE side, the source of truth is the `requestHash` /",
|
|
4368
|
-
" `responseHash` columns of `heyarp work-list <rel-id> --full-ids`.",
|
|
4369
|
-
" - On the CALLER (cosign) side, copy the same values from",
|
|
4370
|
-
" `heyarp receipts <rel-id> --full-ids` after the payee proposes.",
|
|
4371
|
-
" - **V1 caveat:** the validator only checks the hash SHAPE",
|
|
4372
|
-
" (`sha256:<64 lowercase hex>`), it does NOT recompute the value",
|
|
4373
|
-
" against the work_log payload. So for smoke testing any",
|
|
4374
|
-
" well-shaped placeholder (e.g. `sha256:$(printf '%064d' 1)`) is",
|
|
4375
|
-
" accepted. Real binding-check lands when the validator gets",
|
|
4376
|
-
" payload-aware (V1.x).",
|
|
4377
|
-
"",
|
|
4378
|
-
import_chalk14.default.bold("10. Catalog vs live worker + autonomous worker latency"),
|
|
4379
|
-
" `heyarp agents` rows are LISTED (publicationStatus=active), not ONLINE.",
|
|
4380
|
-
" Probe with `heyarp doctor <did>` (LIVE / REACHABLE / DORMANT / UNKNOWN).",
|
|
4381
|
-
" Autonomous LLM workers respond in 30s\u20138min typically; treat silence",
|
|
4382
|
-
' > 15min as "try someone else". Parse inbox events as JSON:',
|
|
4383
|
-
" heyarp inbox --json | jq '.[0].body.content.contract_id' # paginated",
|
|
4384
|
-
" heyarp inbox --tail --json | jq '.data.body.content.contract_id?' # SSE",
|
|
4385
|
-
" --tail wraps each line as `{type, data, id?}` \u2014 body lives under `.data`.",
|
|
4386
|
-
" ID by body.type: contract\u2192contract_id; delegation\u2192delegation_id;",
|
|
4387
|
-
" work_request\u2192delegation_id+request_id; receipt\u2192delegation_id.",
|
|
4388
|
-
" Wire keys \u2260 human row labels \u2014 events: `.senderDid` (not `.signer`),",
|
|
4389
|
-
" `.type` (not `.payload.type`); receipts: `.receiptEventHash` (not",
|
|
4390
|
-
" `.serverEventHash` \u2014 null on receipt rows).",
|
|
4391
|
-
" `relationship.state` STAYS `active` after `cycle.complete`",
|
|
4392
|
-
" (relationships host multiple delegations sequentially). Read the",
|
|
4393
|
-
" delegation row's `state == completed` + the `Cycle: COMPLETE`",
|
|
4394
|
-
" status line for cycle-done \u2014 NOT the relationship row alone.",
|
|
4395
|
-
"",
|
|
4396
|
-
import_chalk14.default.bold("11. When you get stuck"),
|
|
4397
|
-
" Every command supports `--help` \u2014 read structured `code` + `message`",
|
|
4398
|
-
" error fields, they name the exact state-machine constraint violated.",
|
|
4399
|
-
" `heyarp doctor <did>` probes a peer agent's endpoint (LISTED vs LIVE).",
|
|
4400
|
-
" More: README at https://www.npmjs.com/package/@heyanon-arp/cli"
|
|
4401
|
-
].join("\n");
|
|
4693
|
+
function lookupOrThrow(name) {
|
|
4694
|
+
const entry = EXAMPLES.find((e) => e.name === name);
|
|
4695
|
+
if (!entry) {
|
|
4696
|
+
throw new Error(`unknown example '${name}'. Available: ${EXAMPLES.map((e) => e.name).join(", ")} (use \`heyarp examples list\` for descriptions)`);
|
|
4697
|
+
}
|
|
4698
|
+
return entry;
|
|
4699
|
+
}
|
|
4700
|
+
function readExampleOrThrow(entry) {
|
|
4701
|
+
const filePath = (0, import_node_path4.join)(EXAMPLES_DIR, entry.filename);
|
|
4702
|
+
if (!(0, import_node_fs5.existsSync)(filePath)) {
|
|
4703
|
+
throw new Error(
|
|
4704
|
+
`example '${entry.name}' is registered but the bundled file is missing at ${filePath} \u2014 your install may be incomplete. Try reinstalling: npm i -g @heyanon-arp/cli`
|
|
4705
|
+
);
|
|
4706
|
+
}
|
|
4707
|
+
return (0, import_node_fs5.readFileSync)(filePath, "utf8");
|
|
4708
|
+
}
|
|
4402
4709
|
|
|
4403
|
-
// src/commands/
|
|
4404
|
-
var import_node_fs6 = require("fs");
|
|
4405
|
-
var import_node_path5 = require("path");
|
|
4710
|
+
// src/commands/guide.ts
|
|
4406
4711
|
var import_chalk15 = __toESM(require("chalk"));
|
|
4407
|
-
var import_prompts = __toESM(require("prompts"));
|
|
4408
4712
|
|
|
4409
|
-
// src/
|
|
4410
|
-
var
|
|
4411
|
-
var
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4713
|
+
// src/guide/source.ts
|
|
4714
|
+
var GUIDE_TITLE = "HeyARP CLI \u2014 agent guide";
|
|
4715
|
+
var GUIDE_SECTIONS = [
|
|
4716
|
+
{
|
|
4717
|
+
id: "worker.flow",
|
|
4718
|
+
roles: ["worker"],
|
|
4719
|
+
title: "Worker flow \u2014 the states you observe + your moves",
|
|
4720
|
+
body: [
|
|
4721
|
+
" You are the WORKER (payee): you do the task and get paid when the",
|
|
4722
|
+
" buyer cosigns. [recv] = you receive it; [send] = you act.",
|
|
4723
|
+
"",
|
|
4724
|
+
" handshake [recv] \u2192 send handshake_response (accept)",
|
|
4725
|
+
" contract.propose [recv] \u2192 send contract sign \u2192 contract ACTIVE",
|
|
4726
|
+
" delegation.offer [recv] \u2192 send delegation accept \u2192 ACCEPTED",
|
|
4727
|
+
" work_request [recv] \u2192 send work respond (output | error)",
|
|
4728
|
+
" (work finished) \u2192 send receipt propose (verdict + body hashes)",
|
|
4729
|
+
" receipt cosign [recv] \u2190 BUYER cosigns \u2192 COSIGNED \u2713 paid"
|
|
4730
|
+
],
|
|
4731
|
+
transitions: [
|
|
4732
|
+
{ when: "a `handshake` arrives", then: "reply `heyarp send-handshake-response <buyer-did> --decision accept` (or `--decision decline --reason <code>`)" },
|
|
4733
|
+
{ when: "a `contract.propose` arrives", then: "review the terms, then `heyarp contract sign <rel-id> <contract-id>`" },
|
|
4734
|
+
{ when: "a `delegation.offer` arrives", then: "accept ONLY if it carries a deadline you can meet: `heyarp delegation accept <rel-id> <del-id>`" },
|
|
4735
|
+
{ when: "a `work_request` arrives", then: "do the task, then `heyarp work respond <rel-id> <del-id> <req-id> --output '<json>'`" },
|
|
4736
|
+
{
|
|
4737
|
+
when: "your `work respond` is sent",
|
|
4738
|
+
then: "propose the receipt: `heyarp receipt propose <buyer-did> <del-id> --auto-hashes --rel-id <rel-id> --request-id <req-id> --verdict accepted`"
|
|
4739
|
+
},
|
|
4740
|
+
{
|
|
4741
|
+
when: "your receipt is `proposed`",
|
|
4742
|
+
then: "deliver your payee settlement signature: `heyarp settlement auto-sign-and-deliver --delegation-id <del-id> --rel-id <rel-id> --cluster-tag <0|1> --fee-bps-at-lock 0`"
|
|
4743
|
+
},
|
|
4744
|
+
{ when: "the buyer cosigns (cycle released)", then: "you are paid \u2014 the cycle is done; wait for the next offer" }
|
|
4745
|
+
],
|
|
4746
|
+
commonErrors: [
|
|
4747
|
+
"Do NOT cosign your own proposed receipt \u2014 cosign is the BUYER's action; you only propose.",
|
|
4748
|
+
"Do NOT send work respond before delegation accept \u2014 out-of-order envelopes hit a state-machine reject.",
|
|
4749
|
+
"Do NOT propose the contract \u2014 the BUYER proposes terms; you SIGN them.",
|
|
4750
|
+
"Do NOT build the escrow lock \u2014 the BUYER funds create_lock; you settle against it via sign-settlement-release."
|
|
4751
|
+
],
|
|
4752
|
+
crossRefs: [
|
|
4753
|
+
"Skip manual control: the bundled Python reference worker auto-mediates the whole worker side \u2014 `heyarp examples show worker`. A policy-driven worker daemon is planned."
|
|
4754
|
+
]
|
|
4755
|
+
},
|
|
4756
|
+
{
|
|
4757
|
+
id: "buyer.flow",
|
|
4758
|
+
roles: ["buyer"],
|
|
4759
|
+
title: "Buyer flow \u2014 the states you drive + your moves",
|
|
4760
|
+
body: [
|
|
4761
|
+
" You are the BUYER (caller): you delegate the task and pay when you",
|
|
4762
|
+
" cosign. [send] = you act; [recv] = you receive it.",
|
|
4763
|
+
"",
|
|
4764
|
+
" send handshake \u2192 worker replies handshake_response",
|
|
4765
|
+
" send contract propose \u2192 worker signs \u2192 contract ACTIVE",
|
|
4766
|
+
" send delegation offer \u2192 worker accepts \u2192 ACCEPTED",
|
|
4767
|
+
" (the offer MUST carry a funded create_lock blob \u2014 you pre-fund escrow)",
|
|
4768
|
+
" send work request \u2192 worker responds (output | error)",
|
|
4769
|
+
" worker proposes receipt [recv] \u2192 send receipt cosign \u2192 COSIGNED \u2713 released"
|
|
4770
|
+
],
|
|
4771
|
+
transitions: [
|
|
4772
|
+
{ when: "you need a task done", then: "find a worker `heyarp agents --tag <tag>`, then open contact `heyarp send-handshake <worker-did>`" },
|
|
4773
|
+
{ when: "the worker accepts the handshake", then: "propose terms `heyarp contract propose <worker-did> ...` (scope, rate; see `--help`)" },
|
|
4774
|
+
{
|
|
4775
|
+
when: "the contract is `active`",
|
|
4776
|
+
then: "fund escrow `heyarp wallet create-lock ...`, then offer `heyarp delegation offer <worker-did> <contract-id> --escrow-lock-from-file <path> ...`"
|
|
4777
|
+
},
|
|
4778
|
+
{ when: "the worker accepts the delegation", then: "send the task `heyarp work request <worker-did> <del-id> --params '<json>'`" },
|
|
4779
|
+
{
|
|
4780
|
+
when: "the worker proposes a receipt",
|
|
4781
|
+
then: "cosign to release escrow `heyarp receipt cosign <rel-id> <del-id> --auto-hashes --auto-resolve-payee-sig --payer-sig-from-file <path>`"
|
|
4782
|
+
}
|
|
4783
|
+
],
|
|
4784
|
+
commonErrors: [
|
|
4785
|
+
"Do NOT sign the contract \u2014 you PROPOSE terms; the worker (payee) signs.",
|
|
4786
|
+
"Do NOT propose the receipt \u2014 the worker proposes; you COSIGN to release escrow.",
|
|
4787
|
+
"Do NOT send delegation offer without a funded create_lock \u2014 the worker won't accept unfunded work.",
|
|
4788
|
+
"Do NOT treat a catalog `active` row as ONLINE \u2014 probe liveness with `heyarp doctor <did>`."
|
|
4789
|
+
],
|
|
4790
|
+
crossRefs: ["A one-shot buyer facade (`heyarp quick-job`) is planned. For now, drive the cycle with the per-transition commands below."]
|
|
4791
|
+
},
|
|
4792
|
+
{
|
|
4793
|
+
id: "setup.identity",
|
|
4794
|
+
roles: ["setup"],
|
|
4795
|
+
overview: true,
|
|
4796
|
+
title: "Identity",
|
|
4797
|
+
body: [
|
|
4798
|
+
" \u2022 Each agent has a `did:arp:<base58btc>` DID + an Ed25519 identity key.",
|
|
4799
|
+
" \u2022 Keys live in `~/.arp/agents.json` (or $HEYARP_HOME/agents.json).",
|
|
4800
|
+
" \u2022 State file = identity. If two agents share one home dir, EITHER can sign as",
|
|
4801
|
+
" the other. For multi-agent setups on one host, set HEYARP_HOME per agent:",
|
|
4802
|
+
" HEYARP_HOME=/tmp/agent-alice heyarp register \u2026",
|
|
4803
|
+
" HEYARP_HOME=/tmp/agent-bob heyarp register \u2026"
|
|
4804
|
+
]
|
|
4805
|
+
},
|
|
4806
|
+
{
|
|
4807
|
+
id: "overview.work-cycle",
|
|
4808
|
+
roles: ["worker", "buyer"],
|
|
4809
|
+
overview: true,
|
|
4810
|
+
title: "The full work cycle (5 stages, in order)",
|
|
4811
|
+
body: [
|
|
4812
|
+
" Buyer (caller) and Worker (payee) take turns:",
|
|
4813
|
+
"",
|
|
4814
|
+
` handshake buyer \u2192 worker "let's talk"`,
|
|
4815
|
+
" handshake_response worker \u2192 buyer accept | decline",
|
|
4816
|
+
" contract propose buyer \u2192 worker terms (rate, scope, \u2026)",
|
|
4817
|
+
" contract sign worker \u2192 buyer \u2192 state=active",
|
|
4818
|
+
" delegation offer buyer \u2192 worker concrete task + amount",
|
|
4819
|
+
" delegation accept worker \u2192 buyer \u2192 state=accepted",
|
|
4820
|
+
" work request buyer \u2192 worker params payload",
|
|
4821
|
+
" work respond worker \u2192 buyer output OR error",
|
|
4822
|
+
" receipt propose worker \u2192 buyer verdict + body hashes",
|
|
4823
|
+
" receipt cosign buyer \u2192 worker \u2192 state=cosigned \u2713 DONE",
|
|
4824
|
+
"",
|
|
4825
|
+
" Skipping any stage = the next stage rejects with a state-machine error.",
|
|
4826
|
+
" `receipt cosign` is the closure \u2014 without it the work isn't paid for."
|
|
4827
|
+
]
|
|
4828
|
+
},
|
|
4829
|
+
{
|
|
4830
|
+
id: "overview.escrow",
|
|
4831
|
+
roles: ["worker", "buyer"],
|
|
4832
|
+
overview: true,
|
|
4833
|
+
title: "Escrow (how funds actually move)",
|
|
4834
|
+
body: [
|
|
4835
|
+
" `delegation offer` attaches a signed Solana `create_lock` tx blob \u2014 the",
|
|
4836
|
+
" buyer FUNDS escrow up-front before the worker accepts. The worker ",
|
|
4837
|
+
" commits work knowing the cash is already locked.",
|
|
4838
|
+
" `receipt cosign` carries SETTLEMENT SIGNATURES (Ed25519 over a canonical",
|
|
4839
|
+
" digest) from BOTH parties \u2014 these unlock `release_lock` on-chain.",
|
|
4840
|
+
" Refund paths:",
|
|
4841
|
+
" \u2022 PayerCancellation \u2014 buyer cancels within 10min of offer (1 sig)",
|
|
4842
|
+
" \u2022 BothPartiesAgreed \u2014 bilateral cooperative refund (2 sigs)",
|
|
4843
|
+
" \u2022 Expired \u2014 permissionless after lock.expiry passes (no sigs)",
|
|
4844
|
+
" \u2022 DisputeResolution \u2014 admin split via multisig (V1 backend-only)"
|
|
4845
|
+
]
|
|
4846
|
+
},
|
|
4847
|
+
{
|
|
4848
|
+
id: "reference.wallet-commands",
|
|
4849
|
+
roles: ["worker", "buyer"],
|
|
4850
|
+
title: "Wallet + escrow commands (native SOL today; SPL + Token-2022 later)",
|
|
4851
|
+
body: [
|
|
4852
|
+
" `heyarp wallet create-lock` builds + signs a `create_lock` Solana tx.",
|
|
4853
|
+
" Output JSON: {signed_tx_blob, lock_id (32-byte hex), amount, asset_id,",
|
|
4854
|
+
" expiry, delegation_id, program_id}. Pipe via `delegation offer",
|
|
4855
|
+
" --escrow-lock-from-file <path>` \u2014 delegation_id auto-aligns. Use",
|
|
4856
|
+
" `--expiry-secs $(($(date +%s) + 86400*3))` (\u22653d) \u2014 server enforces",
|
|
4857
|
+
" lock.expiry \u2265 deadline + DISPUTE_BUFFER (1d).",
|
|
4858
|
+
" `heyarp wallet derive-pdas --delegation-id <id>` returns the",
|
|
4859
|
+
" deterministic PDAs for ON-CHAIN VERIFICATION:",
|
|
4860
|
+
" {lock_id_hex, program_id, lock_pda, escrow_pda, config_pda,",
|
|
4861
|
+
" event_authority_pda}. `escrow_pda` holds the escrowed funds;",
|
|
4862
|
+
" `config_pda` is the singleton program config; `event_authority_pda`",
|
|
4863
|
+
" is the anchor `#[event_cpi]` self-CPI target.",
|
|
4864
|
+
" `heyarp escrow derive-condition-hash <rel-id> <contract-id>` computes",
|
|
4865
|
+
" the canonical condition_hash for `wallet create-lock --condition-hash`.",
|
|
4866
|
+
" `heyarp wallet sign-settlement-release` signs the release / partial",
|
|
4867
|
+
" digest. Output `sig` is RAW base64 (NO `ed25519:` prefix). Pass",
|
|
4868
|
+
" `--partial-payee-amount <lamports>` to switch the digest to",
|
|
4869
|
+
" `ARP-SOLANA-PARTIAL-RELEASE-v1.5`.",
|
|
4870
|
+
" `heyarp receipt cosign` attaches both parties' signatures into",
|
|
4871
|
+
" `attachments.settlement_signatures` via --settlement-purpose,",
|
|
4872
|
+
" --settlement-expires-at, --payer-settlement-{pubkey,sig},",
|
|
4873
|
+
" --payee-settlement-{pubkey,sig} (+ --settlement-payee-amount for",
|
|
4874
|
+
" partial). Server authorises on-chain release.",
|
|
4875
|
+
" `heyarp wallet verify-release --delegation-id <id> --json` is the",
|
|
4876
|
+
" post-cycle on-chain assertion. Returns {status, release_method,",
|
|
4877
|
+
" lock_state, released, \u2026}. The R15 contract does NOT close the lock",
|
|
4878
|
+
" account on release \u2014 `released: true` is decided from the state byte",
|
|
4879
|
+
" at offset 185: 1\u2192released_clean, 4\u2192released_partial, 2\u2192released_refunded.",
|
|
4880
|
+
" `lock_account_exists: true` post-release is expected, not a bug."
|
|
4881
|
+
]
|
|
4882
|
+
},
|
|
4883
|
+
{
|
|
4884
|
+
id: "buyer.discovery",
|
|
4885
|
+
roles: ["buyer"],
|
|
4886
|
+
title: "Discovery",
|
|
4887
|
+
body: [
|
|
4888
|
+
" `heyarp agents --tag X --tag Y --query Z` \u2014 public catalog, no auth.",
|
|
4889
|
+
" AND-semantics across tags. Returns `did:arp:\u2026` DIDs you can hand to",
|
|
4890
|
+
" `heyarp send-handshake`. Skip the `--query` filter if your tags are",
|
|
4891
|
+
" specific enough; full-text search hits a Mongo `$text` index that needs",
|
|
4892
|
+
" the right shape (server returns 500 if it's misconfigured, you can't do",
|
|
4893
|
+
" much from the CLI side)."
|
|
4894
|
+
]
|
|
4895
|
+
},
|
|
4896
|
+
{
|
|
4897
|
+
id: "setup.multi-did",
|
|
4898
|
+
roles: ["setup"],
|
|
4899
|
+
overview: true,
|
|
4900
|
+
title: "Multi-DID disambiguation",
|
|
4901
|
+
body: [
|
|
4902
|
+
" With >1 agent registered locally for one server, `--from-did` is",
|
|
4903
|
+
" REQUIRED on every signed command. The resolver does NOT silently pick",
|
|
4904
|
+
" one \u2014 it fails with the candidate list. Sole-agent setups auto-pick."
|
|
4905
|
+
]
|
|
4906
|
+
},
|
|
4907
|
+
{
|
|
4908
|
+
id: "setup.recovering-ids",
|
|
4909
|
+
roles: ["setup"],
|
|
4910
|
+
title: "Recovering full IDs / hashes",
|
|
4911
|
+
body: [
|
|
4912
|
+
" List commands truncate `did:arp:abc\u2026xyz` and `sha256:abc\u2026xyz` for",
|
|
4913
|
+
" readability. To get full values for the next command:",
|
|
4914
|
+
" \u2022 `--full-ids` prints UUIDs / DIDs / hashes uncut",
|
|
4915
|
+
" \u2022 `--verbose` appends a per-row JSON dump with full payload",
|
|
4916
|
+
" \u2022 `--json` machine-readable array for piping into `jq`",
|
|
4917
|
+
" For ONE envelope by id (cited in a receipt, copied from inbox):",
|
|
4918
|
+
" \u2022 `heyarp envelope <event-id> --json | jq` \u2014 single signed read."
|
|
4919
|
+
]
|
|
4920
|
+
},
|
|
4921
|
+
{
|
|
4922
|
+
id: "reference.tail-vs-poll",
|
|
4923
|
+
roles: ["worker", "buyer"],
|
|
4924
|
+
title: "Live tail vs polling \u2014 the FSM-wait pattern",
|
|
4925
|
+
body: [
|
|
4926
|
+
" ANTIPATTERN: bash-loop `heyarp inbox` every 5s. Three fixes:",
|
|
4927
|
+
" (a) `--wait-until <phase>` on the action itself \u2014 most ergonomic:",
|
|
4928
|
+
" heyarp delegation offer <recip> <ctr> --wait-until delegation.accepted",
|
|
4929
|
+
" heyarp contract propose <recip> ... --wait-until contract.active",
|
|
4930
|
+
" heyarp receipt cosign <rel> <del> ... --wait-until cycle.released",
|
|
4931
|
+
" (b) `heyarp status <rel-id> --wait --until <phase>` standalone",
|
|
4932
|
+
" (use when the action already exited). Exit 124 on --wait-timeout.",
|
|
4933
|
+
" (c) `heyarp inbox --tail` SSE stream for long-running workers.",
|
|
4934
|
+
" `stream ended unexpectedly` (exit \u2260 0) = server EOF; re-run.",
|
|
4935
|
+
" `stream closed.` (exit 0) = your Ctrl-C; nothing to fix.",
|
|
4936
|
+
" If you must poll without SSE/--wait, persist a cursor:",
|
|
4937
|
+
" heyarp inbox --since <ts> --since-event-id <evt> --json",
|
|
4938
|
+
" SDK consumers: `pollUntil` from `@heyanon-arp/sdk` gives the",
|
|
4939
|
+
" same loop discipline with abort + timeout primitives."
|
|
4940
|
+
]
|
|
4941
|
+
},
|
|
4942
|
+
{
|
|
4943
|
+
id: "worker.receipt-closure",
|
|
4944
|
+
roles: ["worker", "buyer"],
|
|
4945
|
+
title: "Receipt closure semantics + settlement signatures",
|
|
4946
|
+
body: [
|
|
4947
|
+
" - The PAYEE proposes (`heyarp receipt propose`) with their verdict +",
|
|
4948
|
+
" <request-hash> + <response-hash>. These are SHA-256 of the",
|
|
4949
|
+
" canonical JSON of the work_request / work_response body (NOT the",
|
|
4950
|
+
" chain-anchor `serverEventHash`).",
|
|
4951
|
+
" - On the PAYEE side, the source of truth is the `requestHash` /",
|
|
4952
|
+
" `responseHash` columns of `heyarp work-list <rel-id> --full-ids`.",
|
|
4953
|
+
" - On the CALLER (cosign) side, copy the same values from",
|
|
4954
|
+
" `heyarp receipts <rel-id> --full-ids` after the payee proposes.",
|
|
4955
|
+
" - **V1 caveat:** the validator only checks the hash SHAPE",
|
|
4956
|
+
" (`sha256:<64 lowercase hex>`), it does NOT recompute the value",
|
|
4957
|
+
" against the work_log payload. So for smoke testing any",
|
|
4958
|
+
" well-shaped placeholder (e.g. `sha256:$(printf '%064d' 1)`) is",
|
|
4959
|
+
" accepted. Real binding-check lands when the validator gets",
|
|
4960
|
+
" payload-aware (V1.x)."
|
|
4961
|
+
]
|
|
4962
|
+
},
|
|
4963
|
+
{
|
|
4964
|
+
id: "buyer.catalog-vs-live",
|
|
4965
|
+
roles: ["buyer"],
|
|
4966
|
+
title: "Catalog vs live worker + autonomous worker latency",
|
|
4967
|
+
body: [
|
|
4968
|
+
" `heyarp agents` rows are LISTED (publicationStatus=active), not ONLINE.",
|
|
4969
|
+
" Probe with `heyarp doctor <did>` (LIVE / REACHABLE / DORMANT / UNKNOWN).",
|
|
4970
|
+
" Autonomous LLM workers respond in 30s\u20138min typically; treat silence",
|
|
4971
|
+
' > 15min as "try someone else". Parse inbox events as JSON:',
|
|
4972
|
+
" heyarp inbox --json | jq '.[0].body.content.contract_id' # paginated",
|
|
4973
|
+
" heyarp inbox --tail --json | jq '.data.body.content.contract_id?' # SSE",
|
|
4974
|
+
" --tail wraps each line as `{type, data, id?}` \u2014 body lives under `.data`.",
|
|
4975
|
+
" ID by body.type: contract\u2192contract_id; delegation\u2192delegation_id;",
|
|
4976
|
+
" work_request\u2192delegation_id+request_id; receipt\u2192delegation_id.",
|
|
4977
|
+
" Wire keys \u2260 human row labels \u2014 events: `.senderDid` (not `.signer`),",
|
|
4978
|
+
" `.type` (not `.payload.type`); receipts: `.receiptEventHash` (not",
|
|
4979
|
+
" `.serverEventHash` \u2014 null on receipt rows).",
|
|
4980
|
+
" `relationship.state` STAYS `active` after `cycle.complete`",
|
|
4981
|
+
" (relationships host multiple delegations sequentially). Read the",
|
|
4982
|
+
" delegation row's `state == completed` + the `Cycle: COMPLETE`",
|
|
4983
|
+
" status line for cycle-done \u2014 NOT the relationship row alone."
|
|
4984
|
+
]
|
|
4985
|
+
},
|
|
4986
|
+
{
|
|
4987
|
+
id: "troubleshoot.stuck",
|
|
4988
|
+
roles: ["troubleshoot"],
|
|
4989
|
+
title: "When you get stuck",
|
|
4990
|
+
body: [
|
|
4991
|
+
" Every command supports `--help` \u2014 read structured `code` + `message`",
|
|
4992
|
+
" error fields, they name the exact state-machine constraint violated.",
|
|
4993
|
+
" `heyarp doctor <did>` probes a peer agent's endpoint (LISTED vs LIVE).",
|
|
4994
|
+
" More: README at https://www.npmjs.com/package/@heyanon-arp/cli"
|
|
4995
|
+
]
|
|
4996
|
+
}
|
|
4997
|
+
];
|
|
4998
|
+
|
|
4999
|
+
// src/commands/guide.ts
|
|
5000
|
+
function registerGuideCommand(root) {
|
|
5001
|
+
root.command("guide").description(
|
|
5002
|
+
"Role-aware mental-model primer: FSM order, escrow, receipt-as-closure. `--role worker|buyer` shows only your slice; `--setup` / `--troubleshoot` are role-agnostic."
|
|
5003
|
+
).option("--role <role>", `Show only one role's flow: "worker" (payee) or "buyer" (caller).`).option("--setup", "Registration, keys, multi-agent isolation, ID recovery (role-agnostic).", false).option("--troubleshoot", "Common errors \u2192 fixes (role-agnostic).", false).option("--format <fmt>", 'Output format: "human" (default) or "prompt" (an LLM-system-prompt block: linear IF\u2192THEN rules, no chalk). Pair with --role.', "human").option("--concise", "With --format prompt: emit only the IF\u2192THEN + NEVER rules, dropping the reference prose.", false).action((opts) => {
|
|
5004
|
+
const mode = resolveMode(opts);
|
|
5005
|
+
if (opts.format === "prompt") {
|
|
5006
|
+
console.log(renderPrompt(mode, { concise: opts.concise === true }));
|
|
5007
|
+
return;
|
|
5008
|
+
}
|
|
5009
|
+
if (opts.format !== void 0 && opts.format !== "human") {
|
|
5010
|
+
throw new Error(`guide: --format must be 'human' or 'prompt' (got '${opts.format}').`);
|
|
5011
|
+
}
|
|
5012
|
+
console.log(renderGuide(mode));
|
|
5013
|
+
});
|
|
5014
|
+
}
|
|
5015
|
+
function resolveMode(opts) {
|
|
5016
|
+
const selectors = [opts.role !== void 0, opts.setup === true, opts.troubleshoot === true].filter(Boolean).length;
|
|
5017
|
+
if (selectors > 1) {
|
|
5018
|
+
throw new Error("guide: --role, --setup, and --troubleshoot are mutually exclusive \u2014 pass at most one.");
|
|
5019
|
+
}
|
|
5020
|
+
if (opts.role !== void 0) {
|
|
5021
|
+
if (opts.role !== "worker" && opts.role !== "buyer") {
|
|
5022
|
+
throw new Error(`guide: --role must be 'worker' or 'buyer' (got '${opts.role}'). Use --setup / --troubleshoot for role-agnostic topics.`);
|
|
5023
|
+
}
|
|
5024
|
+
return { kind: "role", role: opts.role };
|
|
5025
|
+
}
|
|
5026
|
+
if (opts.setup === true) return { kind: "setup" };
|
|
5027
|
+
if (opts.troubleshoot === true) return { kind: "troubleshoot" };
|
|
5028
|
+
return { kind: "overview" };
|
|
5029
|
+
}
|
|
5030
|
+
function selectSections(mode) {
|
|
5031
|
+
switch (mode.kind) {
|
|
5032
|
+
case "overview":
|
|
5033
|
+
return GUIDE_SECTIONS.filter((s) => s.overview === true);
|
|
5034
|
+
case "role":
|
|
5035
|
+
return GUIDE_SECTIONS.filter((s) => s.roles.includes(mode.role));
|
|
5036
|
+
case "setup":
|
|
5037
|
+
return GUIDE_SECTIONS.filter((s) => s.roles.includes("setup"));
|
|
5038
|
+
case "troubleshoot":
|
|
5039
|
+
return GUIDE_SECTIONS.filter((s) => s.roles.includes("troubleshoot"));
|
|
5040
|
+
}
|
|
5041
|
+
}
|
|
5042
|
+
function renderCommand(c) {
|
|
5043
|
+
return ` \u2192 ${c.command}
|
|
5044
|
+
${c.description}`;
|
|
5045
|
+
}
|
|
5046
|
+
function renderSection(s) {
|
|
5047
|
+
const lines = [import_chalk15.default.bold(s.title)];
|
|
5048
|
+
if (s.body && s.body.length > 0) lines.push(...s.body);
|
|
5049
|
+
if (s.nextActions && s.nextActions.length > 0) {
|
|
5050
|
+
lines.push(" Commands:");
|
|
5051
|
+
for (const c of s.nextActions) lines.push(renderCommand(c));
|
|
5052
|
+
}
|
|
5053
|
+
if (s.commonErrors && s.commonErrors.length > 0) {
|
|
5054
|
+
lines.push(" Avoid:");
|
|
5055
|
+
for (const e of s.commonErrors) lines.push(` \u2717 ${e}`);
|
|
5056
|
+
}
|
|
5057
|
+
if (s.crossRefs && s.crossRefs.length > 0) {
|
|
5058
|
+
for (const x of s.crossRefs) lines.push(` \u2192 ${x}`);
|
|
5059
|
+
}
|
|
5060
|
+
return lines.join("\n");
|
|
5061
|
+
}
|
|
5062
|
+
function modeHeader(mode) {
|
|
5063
|
+
switch (mode.kind) {
|
|
5064
|
+
case "overview":
|
|
5065
|
+
return [
|
|
5066
|
+
"Pick your path:",
|
|
5067
|
+
" \u2022 Worker (you do tasks, get paid): heyarp guide --role worker",
|
|
5068
|
+
" \u2022 Buyer (you delegate tasks, pay): heyarp guide --role buyer",
|
|
5069
|
+
" \u2022 Setup / keys / multi-agent: heyarp guide --setup",
|
|
5070
|
+
" \u2022 Common errors \u2192 fixes: heyarp guide --troubleshoot",
|
|
5071
|
+
""
|
|
5072
|
+
];
|
|
5073
|
+
case "role":
|
|
5074
|
+
return [import_chalk15.default.dim(`(${mode.role} guide \u2014 your slice only; run \`heyarp guide\` for the overview)`), ""];
|
|
5075
|
+
case "setup":
|
|
5076
|
+
return [import_chalk15.default.dim("(setup \u2014 role-agnostic; run `heyarp guide` for the overview)"), ""];
|
|
5077
|
+
case "troubleshoot":
|
|
5078
|
+
return [import_chalk15.default.dim("(troubleshooting \u2014 role-agnostic)"), ""];
|
|
5079
|
+
}
|
|
5080
|
+
}
|
|
5081
|
+
function modeFooter(mode) {
|
|
5082
|
+
if (mode.kind === "overview") {
|
|
5083
|
+
return ["", import_chalk15.default.dim("Full reference per role: `heyarp guide --role worker|buyer`. Docs: https://www.npmjs.com/package/@heyanon-arp/cli")];
|
|
5084
|
+
}
|
|
5085
|
+
return [];
|
|
5086
|
+
}
|
|
5087
|
+
function renderGuide(mode = { kind: "overview" }) {
|
|
5088
|
+
const blocks = [import_chalk15.default.bold(GUIDE_TITLE), "", ...modeHeader(mode)];
|
|
5089
|
+
for (const s of selectSections(mode)) {
|
|
5090
|
+
blocks.push(renderSection(s), "");
|
|
5091
|
+
}
|
|
5092
|
+
blocks.push(...modeFooter(mode));
|
|
5093
|
+
return blocks.join("\n").replace(/\n+$/, "");
|
|
5094
|
+
}
|
|
5095
|
+
function renderPrompt(mode, opts = { concise: false }) {
|
|
5096
|
+
if (mode.kind !== "role") {
|
|
5097
|
+
throw new Error(
|
|
5098
|
+
"guide: --format prompt requires --role worker|buyer \u2014 the LLM operating-rules prompt is role-specific (overview/setup/troubleshoot have no IF\u2192THEN rules)."
|
|
5099
|
+
);
|
|
5100
|
+
}
|
|
5101
|
+
const sections = selectSections(mode);
|
|
5102
|
+
const label = mode.role;
|
|
5103
|
+
const out = [];
|
|
5104
|
+
out.push(`# ARP ${label.toUpperCase()} \u2014 operating rules`);
|
|
5105
|
+
out.push(
|
|
5106
|
+
"You act over the ARP protocol using the `heyarp` CLI. Follow these rules exactly; do not invent envelope types or commands. Replace <placeholders> with the real ids from the envelope you are reacting to."
|
|
5107
|
+
);
|
|
5108
|
+
out.push("");
|
|
5109
|
+
const transitions = sections.flatMap((s) => s.transitions ?? []);
|
|
5110
|
+
if (transitions.length > 0) {
|
|
5111
|
+
out.push("## React to each envelope (IF \u2192 THEN)");
|
|
5112
|
+
transitions.forEach((t, i) => out.push(`${i + 1}. IF ${t.when} THEN ${t.then}`));
|
|
5113
|
+
out.push("");
|
|
5114
|
+
}
|
|
5115
|
+
const constraints = sections.flatMap((s) => s.commonErrors ?? []);
|
|
5116
|
+
if (constraints.length > 0) {
|
|
5117
|
+
out.push("## Hard constraints (NEVER violate)");
|
|
5118
|
+
for (const c of constraints) out.push(`- ${c}`);
|
|
5119
|
+
out.push("");
|
|
5120
|
+
}
|
|
5121
|
+
if (!opts.concise) {
|
|
5122
|
+
const refSections = sections.filter((s) => !(s.transitions && s.transitions.length > 0));
|
|
5123
|
+
if (refSections.length > 0) {
|
|
5124
|
+
out.push("## Reference");
|
|
5125
|
+
for (const s of refSections) {
|
|
5126
|
+
out.push(`### ${s.title}`);
|
|
5127
|
+
if (s.body && s.body.length > 0) out.push(...s.body);
|
|
5128
|
+
out.push("");
|
|
5129
|
+
}
|
|
5130
|
+
}
|
|
5131
|
+
}
|
|
5132
|
+
return out.join("\n").replace(/\n+$/, "");
|
|
5133
|
+
}
|
|
5134
|
+
|
|
5135
|
+
// src/commands/homes.ts
|
|
5136
|
+
var import_node_fs7 = require("fs");
|
|
5137
|
+
var import_node_path6 = require("path");
|
|
5138
|
+
var import_chalk16 = __toESM(require("chalk"));
|
|
5139
|
+
var import_prompts = __toESM(require("prompts"));
|
|
5140
|
+
|
|
5141
|
+
// src/homes.ts
|
|
5142
|
+
var import_node_fs6 = require("fs");
|
|
5143
|
+
var import_node_path5 = require("path");
|
|
5144
|
+
init_paths();
|
|
5145
|
+
var REGISTRY_WARNING = "DO NOT COMMIT \u2014 paths to home dirs may be sensitive (e.g. encrypted-volume mounts).";
|
|
5146
|
+
function readRegistry() {
|
|
5147
|
+
const path = homesRegistryPath();
|
|
5148
|
+
if (!(0, import_node_fs6.existsSync)(path)) return { homes: [] };
|
|
5149
|
+
let raw;
|
|
5150
|
+
try {
|
|
5151
|
+
raw = (0, import_node_fs6.readFileSync)(path, "utf8");
|
|
5152
|
+
} catch (err) {
|
|
5153
|
+
throw new Error(`Failed to read homes registry at ${path}: ${err.message}`);
|
|
5154
|
+
}
|
|
5155
|
+
if (raw.trim().length === 0) return { homes: [] };
|
|
5156
|
+
let parsed;
|
|
5157
|
+
try {
|
|
5158
|
+
parsed = JSON.parse(raw);
|
|
5159
|
+
} catch {
|
|
5160
|
+
throw new Error(`homes registry at ${path} is not valid JSON. Move or delete it before running again.`);
|
|
5161
|
+
}
|
|
5162
|
+
if (parsed === null || typeof parsed !== "object") return { homes: [] };
|
|
5163
|
+
const obj = parsed;
|
|
4432
5164
|
const homes = Array.isArray(obj.homes) ? obj.homes : [];
|
|
4433
5165
|
const clean = homes.filter((h) => {
|
|
4434
5166
|
return h !== null && typeof h === "object" && typeof h.path === "string" && typeof h.lastSeenAt === "string";
|
|
@@ -4437,12 +5169,12 @@ function readRegistry() {
|
|
|
4437
5169
|
}
|
|
4438
5170
|
function writeRegistry(file) {
|
|
4439
5171
|
const path = homesRegistryPath();
|
|
4440
|
-
const dir = (0,
|
|
4441
|
-
if (!(0,
|
|
5172
|
+
const dir = (0, import_node_path5.dirname)(path);
|
|
5173
|
+
if (!(0, import_node_fs6.existsSync)(dir)) (0, import_node_fs6.mkdirSync)(dir, { recursive: true, mode: 448 });
|
|
4442
5174
|
const body = JSON.stringify({ _warning: REGISTRY_WARNING, homes: file.homes }, null, 2);
|
|
4443
|
-
(0,
|
|
5175
|
+
(0, import_node_fs6.writeFileSync)(path, body, { encoding: "utf8", mode: 384 });
|
|
4444
5176
|
try {
|
|
4445
|
-
(0,
|
|
5177
|
+
(0, import_node_fs6.chmodSync)(path, 384);
|
|
4446
5178
|
} catch {
|
|
4447
5179
|
}
|
|
4448
5180
|
}
|
|
@@ -4470,7 +5202,7 @@ function forgetHome(homePath) {
|
|
|
4470
5202
|
return true;
|
|
4471
5203
|
}
|
|
4472
5204
|
function homeStillExists(homePath) {
|
|
4473
|
-
return (0,
|
|
5205
|
+
return (0, import_node_fs6.existsSync)(`${homePath}/agents.json`);
|
|
4474
5206
|
}
|
|
4475
5207
|
|
|
4476
5208
|
// src/commands/homes.ts
|
|
@@ -4497,27 +5229,27 @@ function runHomes(opts) {
|
|
|
4497
5229
|
return;
|
|
4498
5230
|
}
|
|
4499
5231
|
if (rows.length === 0) {
|
|
4500
|
-
console.log(
|
|
4501
|
-
console.log(
|
|
5232
|
+
console.log(import_chalk16.default.dim("(no HEYARP_HOME directories registered yet \u2014 run `heyarp register` once and the registry populates itself)"));
|
|
5233
|
+
console.log(import_chalk16.default.dim(` registry path: ${homesRegistryPath()}`));
|
|
4502
5234
|
return;
|
|
4503
5235
|
}
|
|
4504
5236
|
const header = ["Path", "Agents", "Last seen", "Status"];
|
|
4505
5237
|
const data = rows.map((r) => [
|
|
4506
|
-
r.path + (r.isCurrent ?
|
|
5238
|
+
r.path + (r.isCurrent ? import_chalk16.default.green(" (current)") : ""),
|
|
4507
5239
|
String(r.agentCount),
|
|
4508
5240
|
formatRelativeTime(r.lastSeenAt),
|
|
4509
|
-
r.exists ?
|
|
5241
|
+
r.exists ? import_chalk16.default.green("ok") : import_chalk16.default.red("missing")
|
|
4510
5242
|
]);
|
|
4511
5243
|
console.log("");
|
|
4512
5244
|
console.log(formatTable(header, data));
|
|
4513
|
-
console.log(
|
|
5245
|
+
console.log(import_chalk16.default.dim(`
|
|
4514
5246
|
Registry path: ${homesRegistryPath()}`));
|
|
4515
|
-
console.log(
|
|
5247
|
+
console.log(import_chalk16.default.dim(`Set HEYARP_HOME=<path> in a shell to switch between homes; run \`heyarp homes forget <path>\` to drop a stale entry.`));
|
|
4516
5248
|
}
|
|
4517
5249
|
async function runForget(path, opts) {
|
|
4518
5250
|
if (!opts.yes) {
|
|
4519
|
-
console.log(
|
|
4520
|
-
console.log(
|
|
5251
|
+
console.log(import_chalk16.default.yellow(`About to remove '${path}' from the homes registry.`));
|
|
5252
|
+
console.log(import_chalk16.default.dim(" Note: this only forgets the registry entry; the directory + its agents.json are NOT touched."));
|
|
4521
5253
|
const answer = await (0, import_prompts.default)(
|
|
4522
5254
|
{
|
|
4523
5255
|
type: "confirm",
|
|
@@ -4527,28 +5259,28 @@ async function runForget(path, opts) {
|
|
|
4527
5259
|
},
|
|
4528
5260
|
{
|
|
4529
5261
|
onCancel: () => {
|
|
4530
|
-
console.log(
|
|
5262
|
+
console.log(import_chalk16.default.yellow("Aborted."));
|
|
4531
5263
|
process.exit(130);
|
|
4532
5264
|
}
|
|
4533
5265
|
}
|
|
4534
5266
|
);
|
|
4535
5267
|
if (!answer.confirm) {
|
|
4536
|
-
console.log(
|
|
5268
|
+
console.log(import_chalk16.default.dim("Aborted (no changes)."));
|
|
4537
5269
|
return;
|
|
4538
5270
|
}
|
|
4539
5271
|
}
|
|
4540
5272
|
const removed = forgetHome(path);
|
|
4541
5273
|
if (removed) {
|
|
4542
|
-
console.log(
|
|
5274
|
+
console.log(import_chalk16.default.green(`\u2713 forgot ${path}`));
|
|
4543
5275
|
} else {
|
|
4544
|
-
console.log(
|
|
5276
|
+
console.log(import_chalk16.default.dim(`(no entry for ${path} in the registry \u2014 already absent)`));
|
|
4545
5277
|
}
|
|
4546
5278
|
}
|
|
4547
5279
|
function countAgents(homePath) {
|
|
4548
|
-
const file = (0,
|
|
4549
|
-
if (!(0,
|
|
5280
|
+
const file = (0, import_node_path6.join)(homePath, "agents.json");
|
|
5281
|
+
if (!(0, import_node_fs7.existsSync)(file)) return 0;
|
|
4550
5282
|
try {
|
|
4551
|
-
const parsed = JSON.parse((0,
|
|
5283
|
+
const parsed = JSON.parse((0, import_node_fs7.readFileSync)(file, "utf8"));
|
|
4552
5284
|
if (!parsed || typeof parsed !== "object" || !parsed.servers) return 0;
|
|
4553
5285
|
let total = 0;
|
|
4554
5286
|
for (const server of Object.values(parsed.servers)) {
|
|
@@ -4566,13 +5298,13 @@ function formatTable(header, data) {
|
|
|
4566
5298
|
const padding = " ".repeat(Math.max(0, widths[i] - lengths[i]));
|
|
4567
5299
|
return cell + padding;
|
|
4568
5300
|
}).join(" ");
|
|
4569
|
-
const headerLine =
|
|
5301
|
+
const headerLine = import_chalk16.default.bold(
|
|
4570
5302
|
pad(
|
|
4571
5303
|
header,
|
|
4572
5304
|
header.map((s) => s.length)
|
|
4573
5305
|
)
|
|
4574
5306
|
);
|
|
4575
|
-
const sepLine =
|
|
5307
|
+
const sepLine = import_chalk16.default.dim(
|
|
4576
5308
|
pad(
|
|
4577
5309
|
widths.map((w) => "-".repeat(w)),
|
|
4578
5310
|
widths
|
|
@@ -4599,7 +5331,7 @@ function formatRelativeTime(iso) {
|
|
|
4599
5331
|
}
|
|
4600
5332
|
|
|
4601
5333
|
// src/commands/inbox.ts
|
|
4602
|
-
var
|
|
5334
|
+
var import_chalk17 = __toESM(require("chalk"));
|
|
4603
5335
|
init_api();
|
|
4604
5336
|
function formatTailStartedPing(input) {
|
|
4605
5337
|
const ping = {
|
|
@@ -4646,8 +5378,8 @@ async function runInbox(positionalDid, opts) {
|
|
|
4646
5378
|
}
|
|
4647
5379
|
const api = new ArpApiClient(opts.server);
|
|
4648
5380
|
if (!opts.json) {
|
|
4649
|
-
console.log(
|
|
4650
|
-
console.log(
|
|
5381
|
+
console.log(import_chalk17.default.dim(`Server: ${api.serverUrl}`));
|
|
5382
|
+
console.log(import_chalk17.default.dim(`Signer: ${local.did}`));
|
|
4651
5383
|
}
|
|
4652
5384
|
const query = { limit };
|
|
4653
5385
|
if (opts.before) query.before = opts.before;
|
|
@@ -4661,7 +5393,7 @@ async function runInbox(positionalDid, opts) {
|
|
|
4661
5393
|
return;
|
|
4662
5394
|
}
|
|
4663
5395
|
if (events.length === 0) {
|
|
4664
|
-
console.log(
|
|
5396
|
+
console.log(import_chalk17.default.dim("\n(no events addressed to me \u2014 `heyarp events <relationship-id>` shows the chain-wide listing)"));
|
|
4665
5397
|
return;
|
|
4666
5398
|
}
|
|
4667
5399
|
console.log("");
|
|
@@ -4676,21 +5408,17 @@ async function runInbox(positionalDid, opts) {
|
|
|
4676
5408
|
secondary: `eventId=${ev.eventId} serverEventHash=${ev.serverEventHash}`
|
|
4677
5409
|
}));
|
|
4678
5410
|
}
|
|
4679
|
-
const addressedToMeHint =
|
|
5411
|
+
const addressedToMeHint = import_chalk17.default.dim(" (envelopes addressed to me \u2014 for the full chain see `heyarp events <relationship-id>`)");
|
|
4680
5412
|
if (opts.since && !opts.before) {
|
|
4681
5413
|
console.log(
|
|
4682
|
-
|
|
5414
|
+
import_chalk17.default.dim(
|
|
4683
5415
|
`
|
|
4684
5416
|
${events.length} event(s) (oldest-first).${addressedToMeHint} Advance the forward cursor with --since <serverTimestamp> --since-event-id <eventId> using the LAST row above.`
|
|
4685
5417
|
)
|
|
4686
5418
|
);
|
|
4687
5419
|
} else {
|
|
4688
|
-
console.log(
|
|
4689
|
-
|
|
4690
|
-
`
|
|
4691
|
-
${events.length} event(s).${addressedToMeHint} Paginate with --before <serverTimestamp> --before-event-id <eventId> using the LAST row above.`
|
|
4692
|
-
)
|
|
4693
|
-
);
|
|
5420
|
+
console.log(import_chalk17.default.dim(`
|
|
5421
|
+
${events.length} event(s).${addressedToMeHint} Paginate with --before <serverTimestamp> --before-event-id <eventId> using the LAST row above.`));
|
|
4694
5422
|
}
|
|
4695
5423
|
}
|
|
4696
5424
|
async function runInboxTail(did, local, opts) {
|
|
@@ -4702,8 +5430,9 @@ async function runInboxTail(did, local, opts) {
|
|
|
4702
5430
|
setBlocking.call(handle, true);
|
|
4703
5431
|
stdoutBlockingApplied = true;
|
|
4704
5432
|
} else {
|
|
4705
|
-
|
|
4706
|
-
|
|
5433
|
+
warn(
|
|
5434
|
+
opts.json,
|
|
5435
|
+
import_chalk17.default.yellow(
|
|
4707
5436
|
"\u26A0 inbox --tail: stdout is piped but `process.stdout._handle.setBlocking` is unavailable in this Node runtime. Buffered writes may delay event delivery. Fall back to polling (`heyarp inbox --json`) if events stop arriving."
|
|
4708
5437
|
)
|
|
4709
5438
|
);
|
|
@@ -4713,9 +5442,9 @@ async function runInboxTail(did, local, opts) {
|
|
|
4713
5442
|
if (opts.json) {
|
|
4714
5443
|
console.log(formatTailStartedPing({ server: api.serverUrl, signer: local.did, stdoutBlockingApplied }));
|
|
4715
5444
|
} else {
|
|
4716
|
-
console.log(
|
|
4717
|
-
console.log(
|
|
4718
|
-
console.log(
|
|
5445
|
+
console.log(import_chalk17.default.dim(`Server: ${api.serverUrl}`));
|
|
5446
|
+
console.log(import_chalk17.default.dim(`Signer: ${local.did}`));
|
|
5447
|
+
console.log(import_chalk17.default.dim("Mode: --tail (live SSE, Ctrl-C to stop)"));
|
|
4719
5448
|
}
|
|
4720
5449
|
const controller = new AbortController();
|
|
4721
5450
|
let userAborted = false;
|
|
@@ -4734,7 +5463,7 @@ async function runInboxTail(did, local, opts) {
|
|
|
4734
5463
|
}
|
|
4735
5464
|
if (event.type === "heartbeat") continue;
|
|
4736
5465
|
if (event.type === "connected") {
|
|
4737
|
-
console.log(
|
|
5466
|
+
console.log(import_chalk17.default.green("\u25CF stream open \u2014 listening for envelopes..."));
|
|
4738
5467
|
continue;
|
|
4739
5468
|
}
|
|
4740
5469
|
if (event.type === "envelope") {
|
|
@@ -4748,7 +5477,7 @@ async function runInboxTail(did, local, opts) {
|
|
|
4748
5477
|
}
|
|
4749
5478
|
continue;
|
|
4750
5479
|
}
|
|
4751
|
-
console.log(
|
|
5480
|
+
console.log(import_chalk17.default.dim(`(unknown event: ${event.type})`));
|
|
4752
5481
|
}
|
|
4753
5482
|
if (!userAborted) {
|
|
4754
5483
|
throw new Error("inbox --tail: stream ended unexpectedly (server may have restarted, or change stream errored). Re-run to reconnect.");
|
|
@@ -4756,7 +5485,7 @@ async function runInboxTail(did, local, opts) {
|
|
|
4756
5485
|
} catch (err) {
|
|
4757
5486
|
const name = err.name;
|
|
4758
5487
|
if (name === "AbortError" || userAborted) {
|
|
4759
|
-
if (!opts.json) console.log(
|
|
5488
|
+
if (!opts.json) console.log(import_chalk17.default.dim("\nstream closed."));
|
|
4760
5489
|
return;
|
|
4761
5490
|
}
|
|
4762
5491
|
throw err;
|
|
@@ -4776,15 +5505,15 @@ function formatInboxTable(events, opts = {}) {
|
|
|
4776
5505
|
]);
|
|
4777
5506
|
const widths = header.map((h, i) => Math.max(h.length, ...data.map((row) => row[i].length)));
|
|
4778
5507
|
const pad = (cells) => cells.map((c, i) => c.padEnd(widths[i])).join(" ");
|
|
4779
|
-
const lines = [
|
|
4780
|
-
const detail = events.map((ev) => ` ${
|
|
5508
|
+
const lines = [import_chalk17.default.bold(pad(header)), import_chalk17.default.dim(pad(widths.map((w) => "-".repeat(w)))), ...data.map((row) => pad(row))];
|
|
5509
|
+
const detail = events.map((ev) => ` ${import_chalk17.default.dim("eventId:")} ${import_chalk17.default.cyan(ev.eventId)} ${import_chalk17.default.dim("serverTimestamp:")} ${import_chalk17.default.cyan(ev.serverTimestamp)}`).join("\n");
|
|
4781
5510
|
return `${lines.join("\n")}
|
|
4782
5511
|
|
|
4783
|
-
${
|
|
5512
|
+
${import_chalk17.default.bold("Pagination cursors")} (last \u2192 first):
|
|
4784
5513
|
${detail}`;
|
|
4785
5514
|
}
|
|
4786
5515
|
function hashHead2(hash) {
|
|
4787
|
-
if (!hash) return
|
|
5516
|
+
if (!hash) return import_chalk17.default.dim("(none)");
|
|
4788
5517
|
if (hash.length <= 14) return hash;
|
|
4789
5518
|
return `${hash.slice(0, 14)}...`;
|
|
4790
5519
|
}
|
|
@@ -4799,35 +5528,35 @@ function parseLimit5(raw) {
|
|
|
4799
5528
|
|
|
4800
5529
|
// src/commands/keys.ts
|
|
4801
5530
|
var import_sdk9 = require("@heyanon-arp/sdk");
|
|
4802
|
-
var
|
|
5531
|
+
var import_chalk18 = __toESM(require("chalk"));
|
|
4803
5532
|
function registerKeysCommand(root) {
|
|
4804
5533
|
const keys = root.command("keys").description("Local key utilities");
|
|
4805
5534
|
keys.command("gen").description("Generate a fresh identity + settlement keypair (no save by default)").option("--save", "Reserved for future scratch-key storage; currently a no-op with a notice", false).action((opts) => {
|
|
4806
5535
|
const identity = (0, import_sdk9.generateKeyPair)();
|
|
4807
5536
|
const settlement = (0, import_sdk9.generateKeyPair)();
|
|
4808
5537
|
const out = [
|
|
4809
|
-
|
|
4810
|
-
` public (base58btc): ${
|
|
4811
|
-
` secret (base64) : ${
|
|
5538
|
+
import_chalk18.default.bold("Identity key (Ed25519)"),
|
|
5539
|
+
` public (base58btc): ${import_chalk18.default.cyan((0, import_sdk9.base58btcEncode)(identity.publicKey))}`,
|
|
5540
|
+
` secret (base64) : ${import_chalk18.default.yellow(Buffer.from(identity.secretKey).toString("base64"))}`,
|
|
4812
5541
|
"",
|
|
4813
|
-
|
|
4814
|
-
` public (base58btc): ${
|
|
4815
|
-
` secret (base64) : ${
|
|
5542
|
+
import_chalk18.default.bold("Settlement key (Ed25519)"),
|
|
5543
|
+
` public (base58btc): ${import_chalk18.default.cyan((0, import_sdk9.base58btcEncode)(settlement.publicKey))}`,
|
|
5544
|
+
` secret (base64) : ${import_chalk18.default.yellow(Buffer.from(settlement.secretKey).toString("base64"))}`,
|
|
4816
5545
|
"",
|
|
4817
|
-
|
|
4818
|
-
` ${
|
|
5546
|
+
import_chalk18.default.bold("Resulting DID"),
|
|
5547
|
+
` ${import_chalk18.default.cyan((0, import_sdk9.formatDid)(identity.publicKey))}`
|
|
4819
5548
|
];
|
|
4820
5549
|
console.log(out.join("\n"));
|
|
4821
5550
|
if (opts.save) {
|
|
4822
|
-
console.log(
|
|
5551
|
+
console.log(import_chalk18.default.yellow("\nNote: --save is not yet implemented. Capture the secret keys above before they scroll off-screen."));
|
|
4823
5552
|
}
|
|
4824
5553
|
});
|
|
4825
5554
|
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) => {
|
|
4826
5555
|
const seed = decodeSeed(secretKeyB64);
|
|
4827
5556
|
const pub = (0, import_sdk9.getPublicKey)(seed);
|
|
4828
5557
|
const did = (0, import_sdk9.formatDid)(pub);
|
|
4829
|
-
console.log(`${
|
|
4830
|
-
console.log(`${
|
|
5558
|
+
console.log(`${import_chalk18.default.bold("DID")}: ${import_chalk18.default.cyan(did)}`);
|
|
5559
|
+
console.log(`${import_chalk18.default.bold("Identity public key (base58btc)")}: ${import_chalk18.default.cyan((0, import_sdk9.base58btcEncode)(pub))}`);
|
|
4831
5560
|
});
|
|
4832
5561
|
}
|
|
4833
5562
|
function decodeSeed(b64) {
|
|
@@ -4844,12 +5573,12 @@ function decodeSeed(b64) {
|
|
|
4844
5573
|
}
|
|
4845
5574
|
|
|
4846
5575
|
// src/commands/list.ts
|
|
4847
|
-
var
|
|
5576
|
+
var import_chalk19 = __toESM(require("chalk"));
|
|
4848
5577
|
function registerListCommand(root) {
|
|
4849
5578
|
root.command("list").description("List agents registered locally (~/.arp/agents.json)").action(() => {
|
|
4850
5579
|
const rows = listAgents();
|
|
4851
5580
|
if (rows.length === 0) {
|
|
4852
|
-
console.log(
|
|
5581
|
+
console.log(import_chalk19.default.dim(`No local agents. State file: ${stateFilePath()}`));
|
|
4853
5582
|
return;
|
|
4854
5583
|
}
|
|
4855
5584
|
const grouped = /* @__PURE__ */ new Map();
|
|
@@ -4861,7 +5590,7 @@ function registerListCommand(root) {
|
|
|
4861
5590
|
for (const [serverUrl, group] of grouped) {
|
|
4862
5591
|
if (!first) console.log("");
|
|
4863
5592
|
first = false;
|
|
4864
|
-
console.log(
|
|
5593
|
+
console.log(import_chalk19.default.bold(`Server: ${serverUrl}`));
|
|
4865
5594
|
console.log(formatAgentsTable(group.map(({ agent }) => ({ did: agent.did, name: agent.name, tags: agent.tags, registeredAt: agent.registeredAt }))));
|
|
4866
5595
|
}
|
|
4867
5596
|
});
|
|
@@ -4869,7 +5598,7 @@ function registerListCommand(root) {
|
|
|
4869
5598
|
|
|
4870
5599
|
// src/commands/memory.ts
|
|
4871
5600
|
var import_sdk10 = require("@heyanon-arp/sdk");
|
|
4872
|
-
var
|
|
5601
|
+
var import_chalk20 = __toESM(require("chalk"));
|
|
4873
5602
|
init_api();
|
|
4874
5603
|
function registerMemoryCommands(root) {
|
|
4875
5604
|
const cmd = root.command("memory").description("Memory deltas: write, list, fetch one.");
|
|
@@ -4922,7 +5651,7 @@ function parseAddOptions(cmdName, opts) {
|
|
|
4922
5651
|
throw new Error(`${cmdName}: --commit-after=${commitAfter} requires --delegation-id <uuid> (the delegation whose settlement gates the commit).`);
|
|
4923
5652
|
}
|
|
4924
5653
|
if (commitAfter === "immediate" && opts.delegationId !== void 0) {
|
|
4925
|
-
console.error(
|
|
5654
|
+
console.error(import_chalk20.default.yellow(`${cmdName}: --delegation-id is set but --commit-after=immediate; the delegation_id will be persisted but not used as a settlement gate.`));
|
|
4926
5655
|
}
|
|
4927
5656
|
const out = {
|
|
4928
5657
|
kind: opts.kind,
|
|
@@ -4957,13 +5686,13 @@ async function runAdd(recipientDid, opts) {
|
|
|
4957
5686
|
server: opts.server
|
|
4958
5687
|
});
|
|
4959
5688
|
if (opts.verbose) {
|
|
4960
|
-
console.log(
|
|
5689
|
+
console.log(import_chalk20.default.bold("\nServer response:"));
|
|
4961
5690
|
console.log(formatJson(result));
|
|
4962
5691
|
}
|
|
4963
|
-
console.log(
|
|
5692
|
+
console.log(import_chalk20.default.green(`
|
|
4964
5693
|
memory_delta event ${result.eventId} accepted (commit_after=${bodyContent.commit_after ?? "immediate"})`));
|
|
4965
|
-
console.log(
|
|
4966
|
-
console.log(
|
|
5694
|
+
console.log(import_chalk20.default.dim(`relationshipId: ${result.relationshipId}`));
|
|
5695
|
+
console.log(import_chalk20.default.dim(`serverEventHash: ${result.serverEventHash}`));
|
|
4967
5696
|
}
|
|
4968
5697
|
async function sendMemoryEnvelope(args) {
|
|
4969
5698
|
const nextSequence = (args.sender.lastSenderSequence ?? 0) + 1;
|
|
@@ -4987,7 +5716,7 @@ async function sendMemoryEnvelope(args) {
|
|
|
4987
5716
|
identitySecretKey: signer.identitySecretKey
|
|
4988
5717
|
});
|
|
4989
5718
|
if (args.verbose) {
|
|
4990
|
-
console.log(
|
|
5719
|
+
console.log(import_chalk20.default.bold("\nEnvelope (pre-send):"));
|
|
4991
5720
|
console.log(formatJson(envelope));
|
|
4992
5721
|
}
|
|
4993
5722
|
try {
|
|
@@ -5023,7 +5752,7 @@ async function runList(relationshipId, opts) {
|
|
|
5023
5752
|
return;
|
|
5024
5753
|
}
|
|
5025
5754
|
if (rows.length === 0) {
|
|
5026
|
-
console.log(
|
|
5755
|
+
console.log(import_chalk20.default.dim("(no memory entries)"));
|
|
5027
5756
|
return;
|
|
5028
5757
|
}
|
|
5029
5758
|
for (const r of rows) {
|
|
@@ -5031,7 +5760,7 @@ async function runList(relationshipId, opts) {
|
|
|
5031
5760
|
}
|
|
5032
5761
|
if (rows.length === limitN) {
|
|
5033
5762
|
const lastId = rows[rows.length - 1].id;
|
|
5034
|
-
console.log(
|
|
5763
|
+
console.log(import_chalk20.default.dim(`
|
|
5035
5764
|
Page may not be complete \u2014 paginate with --after ${lastId}`));
|
|
5036
5765
|
}
|
|
5037
5766
|
}
|
|
@@ -5039,12 +5768,15 @@ function formatMemoryLine(r) {
|
|
|
5039
5768
|
const idHead5 = `${r.id.slice(0, 8)}...${r.id.slice(-4)}`;
|
|
5040
5769
|
const authorTail = r.authorDid.length > 20 ? `${r.authorDid.slice(0, 14)}...${r.authorDid.slice(-4)}` : r.authorDid;
|
|
5041
5770
|
const contentPreview = r.content.length > 60 ? `${r.content.slice(0, 57)}...` : r.content;
|
|
5042
|
-
const cosignedTag = r.isCosigned ?
|
|
5043
|
-
const gatedTag = r.delegationId ?
|
|
5044
|
-
return `${
|
|
5771
|
+
const cosignedTag = r.isCosigned ? import_chalk20.default.yellow(" (cosigned)") : "";
|
|
5772
|
+
const gatedTag = r.delegationId ? import_chalk20.default.cyan(" (settlement-gated)") : "";
|
|
5773
|
+
return `${import_chalk20.default.dim(idHead5)} | ${import_chalk20.default.magenta(r.kind)} | ${import_chalk20.default.dim(r.scope)} | ${import_chalk20.default.dim(authorTail)} | ${import_chalk20.default.cyan(`"${contentPreview}"`)}${cosignedTag}${gatedTag}`;
|
|
5045
5774
|
}
|
|
5046
5775
|
function registerShow(parent) {
|
|
5047
|
-
parent.command("show").description("Fetch one memory entry by its server `_id`.").argument(
|
|
5776
|
+
parent.command("show").description("Fetch one memory entry by its server `_id`.").argument(
|
|
5777
|
+
"<entry-id>",
|
|
5778
|
+
'Memory entry `id` (24-hex string). Get this from `heyarp memory list <rel-id> --json | jq ".[].id"`. The public DTO surfaces Mongo `_id` as the `id` field.'
|
|
5779
|
+
).option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Signer DID \u2014 required only if multiple agents are registered against this server").option("--json", "Machine-readable: emit only the JSON object on stdout (no chalk).", false).action(async (entryId, opts) => {
|
|
5048
5780
|
await runShow(entryId, opts);
|
|
5049
5781
|
});
|
|
5050
5782
|
}
|
|
@@ -5057,18 +5789,18 @@ async function runShow(entryId, opts) {
|
|
|
5057
5789
|
console.log(JSON.stringify(row));
|
|
5058
5790
|
return;
|
|
5059
5791
|
}
|
|
5060
|
-
console.log(
|
|
5792
|
+
console.log(import_chalk20.default.bold("Memory entry:"));
|
|
5061
5793
|
console.log(formatJson(row));
|
|
5062
5794
|
console.log("");
|
|
5063
|
-
console.log(`${
|
|
5064
|
-
console.log(`${
|
|
5065
|
-
console.log(`${
|
|
5066
|
-
console.log(`${
|
|
5795
|
+
console.log(`${import_chalk20.default.dim("id:")} ${import_chalk20.default.cyan(row.id)}`);
|
|
5796
|
+
console.log(`${import_chalk20.default.dim("relationshipId:")} ${import_chalk20.default.cyan(row.relationshipId)}`);
|
|
5797
|
+
console.log(`${import_chalk20.default.dim("authorDid:")} ${import_chalk20.default.cyan(row.authorDid)}`);
|
|
5798
|
+
console.log(`${import_chalk20.default.dim("kind / scope:")} ${import_chalk20.default.cyan(`${row.kind} / ${row.scope}`)}`);
|
|
5067
5799
|
if (row.delegationId) {
|
|
5068
|
-
console.log(`${
|
|
5800
|
+
console.log(`${import_chalk20.default.dim("delegationId:")} ${import_chalk20.default.cyan(row.delegationId)} (settlement-gated)`);
|
|
5069
5801
|
}
|
|
5070
5802
|
if (row.supersedesId) {
|
|
5071
|
-
console.log(`${
|
|
5803
|
+
console.log(`${import_chalk20.default.dim("supersedes:")} ${import_chalk20.default.cyan(row.supersedesId)}`);
|
|
5072
5804
|
}
|
|
5073
5805
|
}
|
|
5074
5806
|
function parseTtl3(cmdName, raw) {
|
|
@@ -5095,14 +5827,15 @@ function parseLimit6(cmdName, raw) {
|
|
|
5095
5827
|
}
|
|
5096
5828
|
|
|
5097
5829
|
// src/commands/receipt.ts
|
|
5830
|
+
var import_node_fs8 = require("fs");
|
|
5098
5831
|
var import_sdk11 = require("@heyanon-arp/sdk");
|
|
5099
|
-
var
|
|
5100
|
-
var import_node_fs7 = require("fs");
|
|
5832
|
+
var import_chalk21 = __toESM(require("chalk"));
|
|
5101
5833
|
init_api();
|
|
5102
5834
|
function registerReceiptCommands(root) {
|
|
5103
5835
|
const cmd = root.command("receipt").description("Receipt envelopes \u2014 payee proposes, caller cosigns");
|
|
5104
5836
|
registerPropose2(cmd);
|
|
5105
5837
|
registerCosign(cmd);
|
|
5838
|
+
registerSendPayeeSig(cmd);
|
|
5106
5839
|
}
|
|
5107
5840
|
var POST_COMMIT_ERROR_CODES4 = /* @__PURE__ */ new Set([
|
|
5108
5841
|
"RECEIPT_ALREADY_EXISTS",
|
|
@@ -5116,6 +5849,15 @@ var POST_COMMIT_ERROR_CODES4 = /* @__PURE__ */ new Set([
|
|
|
5116
5849
|
"RECEIPT_COSIGN_AGENT_MISMATCH",
|
|
5117
5850
|
"RECEIPT_COSIGN_PURPOSE_INVALID",
|
|
5118
5851
|
"RECEIPT_COSIGN_INVALID",
|
|
5852
|
+
// response_hash / request_hash / deliverable_hash content
|
|
5853
|
+
// verification. Server commits the receipt envelope row BEFORE
|
|
5854
|
+
// running the canonical-hash lookup, so a rejection here still
|
|
5855
|
+
// consumes the sender sequence — must be in the allowlist or
|
|
5856
|
+
// a retry after fixing the hashes would trip
|
|
5857
|
+
// `ENV_SEQUENCE_BACKWARDS`.
|
|
5858
|
+
"RECEIPT_RESPONSE_HASH_NOT_FOUND",
|
|
5859
|
+
"RECEIPT_REQUEST_HASH_NOT_FOUND",
|
|
5860
|
+
"RECEIPT_DELIVERABLE_HASH_MISMATCH",
|
|
5119
5861
|
// Settlement-side rejections from the receipt cosign path.
|
|
5120
5862
|
// Receipt-handler invokes
|
|
5121
5863
|
// `ReceiptCosignValidatorService.enqueueReleaseOp` AFTER the
|
|
@@ -5156,7 +5898,32 @@ var POST_COMMIT_ERROR_CODES4 = /* @__PURE__ */ new Set([
|
|
|
5156
5898
|
// lock.amount. Same lifecycle classification as MISMATCH — event
|
|
5157
5899
|
// committed, body action rejected, CLI advances
|
|
5158
5900
|
// lastSenderSequence.
|
|
5159
|
-
"ESC_USAGE_COMPUTED_AMOUNT_EXCEEDS_LOCK"
|
|
5901
|
+
"ESC_USAGE_COMPUTED_AMOUNT_EXCEEDS_LOCK",
|
|
5902
|
+
// `settlement_signature` envelope handler rejection codes. ALL
|
|
5903
|
+
// fire AFTER the event row is committed but BEFORE the
|
|
5904
|
+
// receipt.payeeSettlement is set (server-side
|
|
5905
|
+
// PRE_MATERIALIZATION_REJECTION_CODES classification). Sender
|
|
5906
|
+
// sequence is consumed regardless — the envelope row sits in the
|
|
5907
|
+
// chain with readModelStatus='rejected'. Without these on the
|
|
5908
|
+
// allowlist, a CLI retry after fixing the sig would reuse the
|
|
5909
|
+
// old sequence and trip ENV_SEQUENCE_BACKWARDS.
|
|
5910
|
+
//
|
|
5911
|
+
// Sourced via:
|
|
5912
|
+
// grep "'SETTLEMENT_SIG_" apps/arp-server/src/message/services/settlement-signature-handler.service.ts \
|
|
5913
|
+
// | sed -E "s/.*'(SETTLEMENT_SIG_[A-Z_]+)'.*/\1/" | sort -u
|
|
5914
|
+
"SETTLEMENT_SIG_RECEIPT_NOT_FOUND",
|
|
5915
|
+
"SETTLEMENT_SIG_RECEIPT_INVALID_STATE",
|
|
5916
|
+
"SETTLEMENT_SIG_SENDER_NOT_PAYEE",
|
|
5917
|
+
"SETTLEMENT_SIG_VERDICT_NOT_PAYABLE",
|
|
5918
|
+
"SETTLEMENT_SIG_PURPOSE_INVALID",
|
|
5919
|
+
"SETTLEMENT_SIG_PAYEE_AMOUNT_MISMATCH_USAGE",
|
|
5920
|
+
"SETTLEMENT_SIG_LOCK_NOT_FOUND",
|
|
5921
|
+
"SETTLEMENT_SIG_LOCK_INVALID_STATE",
|
|
5922
|
+
"SETTLEMENT_SIG_PUBKEY_MISMATCH",
|
|
5923
|
+
"SETTLEMENT_SIG_PAYEE_AMOUNT_EXCEEDS_LOCK",
|
|
5924
|
+
"SETTLEMENT_SIG_EXPIRES_AT_PAST",
|
|
5925
|
+
"SETTLEMENT_SIG_EXPIRES_AT_TOO_SOON",
|
|
5926
|
+
"SETTLEMENT_SIG_EXPIRES_AT_EXCEEDS_LOCK"
|
|
5160
5927
|
]);
|
|
5161
5928
|
var VERDICT_VALUES = ["accepted", "accepted_with_notes", "rejected"];
|
|
5162
5929
|
var SHA256_RE = /^sha256:[0-9a-f]{64}$/;
|
|
@@ -5171,16 +5938,25 @@ function registerPropose2(parent) {
|
|
|
5171
5938
|
).option(
|
|
5172
5939
|
"--request-id <r>",
|
|
5173
5940
|
'work_request id (the same value you passed to `heyarp work request --request-id`). Required when --auto-hashes is set AND the delegation has >1 outstanding "responded" work_log. When omitted, the CLI auto-resolves to the unique responded work_log.'
|
|
5174
|
-
).option("--verbose", "Print the full envelope before sending and the full server response
|
|
5941
|
+
).option("--verbose", "Print the full envelope before sending and the full server response. Mutually exclusive with --json.", false).option(
|
|
5942
|
+
"--json",
|
|
5943
|
+
"Machine-readable mode \u2014 emit a single JSON object to stdout with `{receiptEventHash, delegationId, requestHash, responseHash, deliverableHash?, notesHash?, verdictProposed, usage?, eventId, relationshipId, relationshipEventIndex, serverTimestamp}`. Prelude + auto-resolution hints + cosign-next-step hint move to stderr. Pipe-safe: `heyarp receipt propose ... --json | jq` returns parseable JSON. Mutually exclusive with --verbose.",
|
|
5944
|
+
false
|
|
5945
|
+
).action(async (recipientDid, delegationId, requestHash, responseHash, opts, cmd) => {
|
|
5175
5946
|
try {
|
|
5176
5947
|
await runPropose2(recipientDid, delegationId, requestHash, responseHash, opts);
|
|
5177
5948
|
} catch (err) {
|
|
5178
|
-
|
|
5949
|
+
emitActionError(err, cmd);
|
|
5179
5950
|
process.exitCode = 1;
|
|
5180
5951
|
}
|
|
5181
5952
|
});
|
|
5182
5953
|
}
|
|
5183
5954
|
async function runPropose2(recipientDid, delegationId, requestHashArg, responseHashArg, opts) {
|
|
5955
|
+
if (opts.verbose && opts.json) {
|
|
5956
|
+
throw new Error(
|
|
5957
|
+
"receipt propose: --verbose and --json are mutually exclusive. --json already emits the full server response as a structured payload; --verbose adds an envelope + response dump on top that would break `--json | jq`."
|
|
5958
|
+
);
|
|
5959
|
+
}
|
|
5184
5960
|
requireDid3("receipt propose", recipientDid, "<recipient-did>");
|
|
5185
5961
|
delegationId = requireUuidNormalised2("receipt propose", delegationId, "<delegation-id>");
|
|
5186
5962
|
if (opts.relId) opts.relId = requireUuidNormalised2("receipt propose", opts.relId, "--rel-id");
|
|
@@ -5193,7 +5969,10 @@ async function runPropose2(recipientDid, delegationId, requestHashArg, responseH
|
|
|
5193
5969
|
const sender = resolveSenderAgent("receipt propose", opts.server, opts.fromDid);
|
|
5194
5970
|
if (opts.autoHashes && !opts.relId) {
|
|
5195
5971
|
opts.relId = await resolveAutoRelId(api, sender, delegationId);
|
|
5196
|
-
|
|
5972
|
+
progress(
|
|
5973
|
+
opts.json,
|
|
5974
|
+
import_chalk21.default.dim(`[auto-rel-id] resolved --rel-id=${opts.relId} (delegation found in exactly one of your relationships; pass --rel-id explicitly to override)`)
|
|
5975
|
+
);
|
|
5197
5976
|
}
|
|
5198
5977
|
if (opts.relId) {
|
|
5199
5978
|
await assertSenderIsReceiptPayee(api, sender, opts.relId, delegationId);
|
|
@@ -5206,7 +5985,12 @@ async function runPropose2(recipientDid, delegationId, requestHashArg, responseH
|
|
|
5206
5985
|
}
|
|
5207
5986
|
if (!opts.requestId) {
|
|
5208
5987
|
opts.requestId = await resolveAutoRequestId(api, sender, opts.relId, delegationId);
|
|
5209
|
-
|
|
5988
|
+
progress(
|
|
5989
|
+
opts.json,
|
|
5990
|
+
import_chalk21.default.dim(
|
|
5991
|
+
`[auto-request-id] resolved --request-id=${opts.requestId} (unique 'responded' work_log under this delegation; pass --request-id explicitly to override)`
|
|
5992
|
+
)
|
|
5993
|
+
);
|
|
5210
5994
|
}
|
|
5211
5995
|
requireUuid3("receipt propose", opts.relId, "--rel-id");
|
|
5212
5996
|
const computed = await computeWorkLogHashes(api, sender, opts.relId, delegationId, opts.requestId);
|
|
@@ -5222,8 +6006,8 @@ async function runPropose2(recipientDid, delegationId, requestHashArg, responseH
|
|
|
5222
6006
|
}
|
|
5223
6007
|
requestHash = computed.requestHash;
|
|
5224
6008
|
responseHash = computed.responseHash;
|
|
5225
|
-
|
|
5226
|
-
|
|
6009
|
+
progress(opts.json, import_chalk21.default.dim(`[auto-hashes] request_hash: ${requestHash} (from work-log ${opts.relId}/${delegationId}/${opts.requestId})`));
|
|
6010
|
+
progress(opts.json, import_chalk21.default.dim(`[auto-hashes] response_hash: ${responseHash}`));
|
|
5227
6011
|
} else {
|
|
5228
6012
|
if (requestHashArg === void 0 || responseHashArg === void 0) {
|
|
5229
6013
|
throw new Error("receipt propose: <request-hash> and <response-hash> are required (or pass --auto-hashes + --rel-id + --request-id to derive them from the work-log)");
|
|
@@ -5243,23 +6027,39 @@ async function runPropose2(recipientDid, delegationId, requestHashArg, responseH
|
|
|
5243
6027
|
if (opts.deliverableHash) content.deliverable_hash = opts.deliverableHash;
|
|
5244
6028
|
if (usage) content.usage = usage;
|
|
5245
6029
|
const body = { type: "receipt", content };
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
6030
|
+
progress(opts.json, import_chalk21.default.dim(`Server: ${api.serverUrl}`));
|
|
6031
|
+
progress(opts.json, import_chalk21.default.dim(`Sender (payee): ${sender.did}`));
|
|
6032
|
+
progress(opts.json, import_chalk21.default.dim(`Recipient (caller): ${recipientDid}`));
|
|
6033
|
+
progress(opts.json, import_chalk21.default.dim(`Delegation: ${delegationId}`));
|
|
6034
|
+
progress(opts.json, import_chalk21.default.dim(`Verdict (proposed): ${verdict}`));
|
|
5251
6035
|
const result = await sendReceiptEnvelope({ api, sender, recipientDid, body, attachments: void 0, ttlSeconds, verbose: opts.verbose, server: opts.server });
|
|
6036
|
+
if (opts.json) {
|
|
6037
|
+
const json = {
|
|
6038
|
+
receiptEventHash: result.serverEventHash,
|
|
6039
|
+
delegationId,
|
|
6040
|
+
requestHash,
|
|
6041
|
+
responseHash,
|
|
6042
|
+
verdictProposed: verdict,
|
|
6043
|
+
eventId: result.eventId,
|
|
6044
|
+
relationshipId: result.relationshipId,
|
|
6045
|
+
relationshipEventIndex: result.relationshipEventIndex,
|
|
6046
|
+
serverTimestamp: result.serverTimestamp
|
|
6047
|
+
};
|
|
6048
|
+
if (content.deliverable_hash) json.deliverableHash = content.deliverable_hash;
|
|
6049
|
+
if (content.notes_hash) json.notesHash = content.notes_hash;
|
|
6050
|
+
if (content.usage) json.usage = content.usage;
|
|
6051
|
+
jsonOut(json);
|
|
6052
|
+
return;
|
|
6053
|
+
}
|
|
5252
6054
|
printIngestResult3(result);
|
|
5253
|
-
console.log(
|
|
5254
|
-
Receipt event hash: ${
|
|
5255
|
-
console.log(
|
|
5256
|
-
console.log(
|
|
6055
|
+
console.log(import_chalk21.default.dim(`
|
|
6056
|
+
Receipt event hash: ${import_chalk21.default.cyan(result.serverEventHash)}`));
|
|
6057
|
+
console.log(import_chalk21.default.dim(`The caller cosigns with:`));
|
|
6058
|
+
console.log(import_chalk21.default.dim(` heyarp receipt cosign ${result.relationshipId} ${delegationId} ${requestHash} ${responseHash} --verdict ${verdict}`));
|
|
5257
6059
|
}
|
|
5258
6060
|
async function assertSenderIsReceiptPayee(api, sender, relationshipId, delegationId) {
|
|
5259
6061
|
const signer = makeSigner(sender);
|
|
5260
|
-
const rows = await fetchAllPages(
|
|
5261
|
-
(after) => api.listDelegations(relationshipId, signer, { limit: 100, ...after ? { after } : {} })
|
|
5262
|
-
);
|
|
6062
|
+
const rows = await fetchAllPages((after) => api.listDelegations(relationshipId, signer, { limit: 100, ...after ? { after } : {} }));
|
|
5263
6063
|
const delegation = rows.find((d) => d.delegationId === delegationId);
|
|
5264
6064
|
if (!delegation) {
|
|
5265
6065
|
return;
|
|
@@ -5272,9 +6072,7 @@ async function assertSenderIsReceiptPayee(api, sender, relationshipId, delegatio
|
|
|
5272
6072
|
}
|
|
5273
6073
|
async function assertSenderIsReceiptCaller(api, sender, relationshipId, delegationId) {
|
|
5274
6074
|
const signer = makeSigner(sender);
|
|
5275
|
-
const rows = await fetchAllPages(
|
|
5276
|
-
(after) => api.listDelegations(relationshipId, signer, { limit: 100, ...after ? { after } : {} })
|
|
5277
|
-
);
|
|
6075
|
+
const rows = await fetchAllPages((after) => api.listDelegations(relationshipId, signer, { limit: 100, ...after ? { after } : {} }));
|
|
5278
6076
|
const delegation = rows.find((d) => d.delegationId === delegationId);
|
|
5279
6077
|
if (!delegation) {
|
|
5280
6078
|
return;
|
|
@@ -5311,7 +6109,9 @@ async function resolveAutoRelId(api, sender, delegationId) {
|
|
|
5311
6109
|
}
|
|
5312
6110
|
async function resolveAutoRequestId(api, sender, relationshipId, delegationId) {
|
|
5313
6111
|
const signer = makeSigner(sender);
|
|
5314
|
-
const logs = await fetchAllPages(
|
|
6112
|
+
const logs = await fetchAllPages(
|
|
6113
|
+
(after) => api.listWorkLogs(relationshipId, signer, { delegationId, state: "responded", limit: 100, ...after ? { after } : {} })
|
|
6114
|
+
);
|
|
5315
6115
|
if (logs.length === 0) {
|
|
5316
6116
|
throw new Error(
|
|
5317
6117
|
`receipt propose --auto-hashes: no 'responded' work_log under delegation ${delegationId} in relationship ${relationshipId} \u2014 the payee must send \`work respond\` before a receipt can be proposed. Pass --request-id explicitly if you have a known one.`
|
|
@@ -5366,25 +6166,43 @@ function registerCosign(parent) {
|
|
|
5366
6166
|
).option("--ttl <seconds>", "Envelope TTL in seconds", "3600").option("--verbose", "Print the full envelope before sending and the full server response", false).option(
|
|
5367
6167
|
"--settlement-purpose <s>",
|
|
5368
6168
|
"Settlement digest purpose: `ARP-SOLANA-RELEASE-v1.5` (full) or `ARP-SOLANA-PARTIAL-RELEASE-v1.5` (partial; requires --settlement-payee-amount)."
|
|
5369
|
-
).option(
|
|
6169
|
+
).option(
|
|
6170
|
+
"--settlement-expires-at <unix>",
|
|
6171
|
+
"Settlement expires_at (unix seconds) \u2014 must match the value signed by BOTH parties when they ran `heyarp wallet sign-settlement-release`."
|
|
6172
|
+
).option("--payer-settlement-pubkey <b58>", "Payer settlement pubkey (base58) \u2014 must equal `lock.payer`. Mutually exclusive with --payer-sig-from-file.").option("--payer-settlement-sig <base64>", "Payer Ed25519 settlement signature (raw base64, NO `ed25519:` prefix). Mutually exclusive with --payer-sig-from-file.").option("--payee-settlement-pubkey <b58>", "Payee settlement pubkey (base58) \u2014 must equal `lock.payee`. Mutually exclusive with --payee-sig-from-file.").option("--payee-settlement-sig <base64>", "Payee Ed25519 settlement signature (raw base64, NO `ed25519:` prefix). Mutually exclusive with --payee-sig-from-file.").option(
|
|
6173
|
+
"--auto-resolve-payee-sig",
|
|
6174
|
+
"Read the payee's settlement signature from `receipt.payeeSettlement` (populated by the worker's `heyarp receipt send-payee-sig` envelope) instead of requiring --payee-settlement-pubkey + --payee-settlement-sig + --settlement-purpose + --settlement-expires-at + --settlement-payee-amount flags. Closes the cross-host-coordination gap that earlier flows worked around via shared /tmp/*.json files. Mutually exclusive with --payee-sig-from-file and the explicit --payee-settlement-* flags. Buyer still supplies the PAYER side themselves (--payer-sig-from-file or --payer-settlement-* flags).",
|
|
6175
|
+
false
|
|
6176
|
+
).option(
|
|
5370
6177
|
"--payer-sig-from-file <path>",
|
|
5371
6178
|
"Read `{settlement_pubkey, sig}` for the PAYER side from the JSON file produced by `heyarp wallet sign-settlement-release --write-to <path>`. Populates --payer-settlement-pubkey + --payer-settlement-sig."
|
|
5372
6179
|
).option(
|
|
5373
6180
|
"--payee-sig-from-file <path>",
|
|
5374
6181
|
"Read `{settlement_pubkey, sig}` for the PAYEE side from the JSON file produced by `heyarp wallet sign-settlement-release --write-to <path>`. Populates --payee-settlement-pubkey + --payee-settlement-sig."
|
|
5375
|
-
).option(
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
6182
|
+
).option(
|
|
6183
|
+
"--settlement-payee-amount <int>",
|
|
6184
|
+
"Required ONLY when --settlement-purpose=ARP-SOLANA-PARTIAL-RELEASE-v1.5. Payee receives this many base units; payer is refunded `lock.amount - payee_amount`."
|
|
6185
|
+
).option(
|
|
6186
|
+
"--no-settlement",
|
|
6187
|
+
"Opt out of settlement_signatures attachment. Default behavior REQUIRES the full settlement flag set; without --no-settlement, omitting flags errors loud BEFORE the envelope is sent. Use --no-settlement only against a server running in test_mode."
|
|
6188
|
+
).option(
|
|
6189
|
+
"--wait-until <phase>",
|
|
6190
|
+
"Block after cosign-delivery until the named FSM phase is reached (e.g. cycle.released \u2014 strongest, waits for on-chain settlement; cycle.complete \u2014 weaker, waits only for the cosigned-receipt row). One of UNTIL_PHASES from `heyarp status --help`. Exit 124 on --wait-timeout."
|
|
6191
|
+
).option("--wait-timeout <seconds>", "When --wait-until is set: max wall-clock wait (default 300). Exit code 124 on timeout.").option("--wait-interval <seconds>", "When --wait-until is set: poll cadence (default 3, bound [1, 60]).").option("--wait-verbose", "When --wait-until is set: emit one dim line per poll tick.", false).action(
|
|
6192
|
+
async (relationshipId, delegationId, requestHashArg, responseHashArg, opts, cmd) => {
|
|
6193
|
+
try {
|
|
6194
|
+
await runCosign(relationshipId, delegationId, requestHashArg, responseHashArg, opts);
|
|
6195
|
+
} catch (err) {
|
|
6196
|
+
emitActionError(err, cmd);
|
|
6197
|
+
process.exitCode = 1;
|
|
6198
|
+
}
|
|
5381
6199
|
}
|
|
5382
|
-
|
|
6200
|
+
);
|
|
5383
6201
|
}
|
|
5384
6202
|
function loadSettlementSigFromFile(path, flagPrefix) {
|
|
5385
6203
|
let raw;
|
|
5386
6204
|
try {
|
|
5387
|
-
raw = (0,
|
|
6205
|
+
raw = (0, import_node_fs8.readFileSync)(path, "utf8");
|
|
5388
6206
|
} catch (err) {
|
|
5389
6207
|
throw new Error(`receipt cosign: failed to read ${flagPrefix} '${path}': ${err.message}`);
|
|
5390
6208
|
}
|
|
@@ -5403,7 +6221,9 @@ function loadSettlementSigFromFile(path, flagPrefix) {
|
|
|
5403
6221
|
const digestHex = p.digest_hex;
|
|
5404
6222
|
const purpose = p.purpose;
|
|
5405
6223
|
if (typeof pubkey !== "string" || pubkey.length === 0) {
|
|
5406
|
-
throw new Error(
|
|
6224
|
+
throw new Error(
|
|
6225
|
+
`receipt cosign: ${flagPrefix} '${path}' missing required field 'settlement_pubkey' (string). Expected JSON output of \`heyarp wallet sign-settlement-release\`.`
|
|
6226
|
+
);
|
|
5407
6227
|
}
|
|
5408
6228
|
if (typeof sig !== "string" || sig.length === 0) {
|
|
5409
6229
|
throw new Error(`receipt cosign: ${flagPrefix} '${path}' missing required field 'sig' (string). Expected JSON output of \`heyarp wallet sign-settlement-release\`.`);
|
|
@@ -5416,6 +6236,43 @@ function loadSettlementSigFromFile(path, flagPrefix) {
|
|
|
5416
6236
|
}
|
|
5417
6237
|
return { settlement_pubkey: pubkey, sig, digest_hex: digestHex, purpose };
|
|
5418
6238
|
}
|
|
6239
|
+
function applyAutoResolvePayeeSig(cmdName, opts, payeeSettlement) {
|
|
6240
|
+
if (opts.payeeSigFromFile !== void 0 && opts.payeeSigFromFile !== "") {
|
|
6241
|
+
throw new Error(`${cmdName}: --auto-resolve-payee-sig is mutually exclusive with --payee-sig-from-file. Pass either the auto-resolve flag OR the file path, not both.`);
|
|
6242
|
+
}
|
|
6243
|
+
if (opts.payeeSettlementPubkey !== void 0 && opts.payeeSettlementPubkey !== "" || opts.payeeSettlementSig !== void 0 && opts.payeeSettlementSig !== "") {
|
|
6244
|
+
throw new Error(
|
|
6245
|
+
`${cmdName}: --auto-resolve-payee-sig is mutually exclusive with --payee-settlement-pubkey / --payee-settlement-sig. Pass either the auto-resolve flag OR the explicit values, not both.`
|
|
6246
|
+
);
|
|
6247
|
+
}
|
|
6248
|
+
if (!payeeSettlement) {
|
|
6249
|
+
throw new Error(
|
|
6250
|
+
`${cmdName}: --auto-resolve-payee-sig requires receipt.payeeSettlement to be populated, but the field is unset. The worker hasn't sent its 'heyarp receipt send-payee-sig' envelope yet \u2014 wait for it OR fall back to --payee-sig-from-file / --payee-settlement-* flags for out-of-band coordination.`
|
|
6251
|
+
);
|
|
6252
|
+
}
|
|
6253
|
+
opts.payeeSettlementPubkey = payeeSettlement.settlement_pubkey;
|
|
6254
|
+
opts.payeeSettlementSig = payeeSettlement.sig;
|
|
6255
|
+
if (opts.settlementPurpose !== void 0 && opts.settlementPurpose !== "" && opts.settlementPurpose !== payeeSettlement.purpose) {
|
|
6256
|
+
throw new Error(
|
|
6257
|
+
`${cmdName}: --settlement-purpose='${opts.settlementPurpose}' disagrees with the auto-resolved purpose from receipt.payeeSettlement ('${payeeSettlement.purpose}'). Drop --settlement-purpose to use the auto-resolved value, or drop --auto-resolve-payee-sig to use the manual flag.`
|
|
6258
|
+
);
|
|
6259
|
+
}
|
|
6260
|
+
opts.settlementPurpose = payeeSettlement.purpose;
|
|
6261
|
+
if (opts.settlementExpiresAt !== void 0 && opts.settlementExpiresAt !== "" && opts.settlementExpiresAt !== String(payeeSettlement.expires_at)) {
|
|
6262
|
+
throw new Error(
|
|
6263
|
+
`${cmdName}: --settlement-expires-at='${opts.settlementExpiresAt}' disagrees with the auto-resolved expires_at from receipt.payeeSettlement (${payeeSettlement.expires_at}). Drop --settlement-expires-at to use the auto-resolved value, or drop --auto-resolve-payee-sig.`
|
|
6264
|
+
);
|
|
6265
|
+
}
|
|
6266
|
+
opts.settlementExpiresAt = String(payeeSettlement.expires_at);
|
|
6267
|
+
if (payeeSettlement.payee_amount !== void 0) {
|
|
6268
|
+
if (opts.settlementPayeeAmount !== void 0 && opts.settlementPayeeAmount !== "" && opts.settlementPayeeAmount !== payeeSettlement.payee_amount) {
|
|
6269
|
+
throw new Error(
|
|
6270
|
+
`${cmdName}: --settlement-payee-amount='${opts.settlementPayeeAmount}' disagrees with the auto-resolved payee_amount from receipt.payeeSettlement ('${payeeSettlement.payee_amount}'). Drop --settlement-payee-amount to use the auto-resolved value, or drop --auto-resolve-payee-sig.`
|
|
6271
|
+
);
|
|
6272
|
+
}
|
|
6273
|
+
opts.settlementPayeeAmount = payeeSettlement.payee_amount;
|
|
6274
|
+
}
|
|
6275
|
+
}
|
|
5419
6276
|
function assembleSettlementSignaturesAttachment(opts) {
|
|
5420
6277
|
let payerPubkey = opts.payerSettlementPubkey;
|
|
5421
6278
|
let payerSig = opts.payerSettlementSig;
|
|
@@ -5463,14 +6320,7 @@ function assembleSettlementSignaturesAttachment(opts) {
|
|
|
5463
6320
|
`receipt cosign: --settlement-purpose='${opts.settlementPurpose}' disagrees with the purpose signed in the sig file ('${filePurpose}'). Either drop --settlement-purpose (the file's value will be used) or re-sign with the desired purpose.`
|
|
5464
6321
|
);
|
|
5465
6322
|
}
|
|
5466
|
-
const settlementFlags = [
|
|
5467
|
-
opts.settlementPurpose,
|
|
5468
|
-
opts.settlementExpiresAt,
|
|
5469
|
-
payerPubkey,
|
|
5470
|
-
payerSig,
|
|
5471
|
-
payeePubkey,
|
|
5472
|
-
payeeSig
|
|
5473
|
-
];
|
|
6323
|
+
const settlementFlags = [opts.settlementPurpose, opts.settlementExpiresAt, payerPubkey, payerSig, payeePubkey, payeeSig];
|
|
5474
6324
|
const someSet = settlementFlags.some((f) => f !== void 0 && f !== "");
|
|
5475
6325
|
const allSet = settlementFlags.every((f) => f !== void 0 && f !== "");
|
|
5476
6326
|
if (!someSet) {
|
|
@@ -5489,9 +6339,7 @@ function assembleSettlementSignaturesAttachment(opts) {
|
|
|
5489
6339
|
}
|
|
5490
6340
|
const purpose = opts.settlementPurpose;
|
|
5491
6341
|
if (purpose !== "ARP-SOLANA-RELEASE-v1.5" && purpose !== "ARP-SOLANA-PARTIAL-RELEASE-v1.5") {
|
|
5492
|
-
throw new Error(
|
|
5493
|
-
`receipt cosign: --settlement-purpose must be 'ARP-SOLANA-RELEASE-v1.5' or 'ARP-SOLANA-PARTIAL-RELEASE-v1.5', got '${purpose}'.`
|
|
5494
|
-
);
|
|
6342
|
+
throw new Error(`receipt cosign: --settlement-purpose must be 'ARP-SOLANA-RELEASE-v1.5' or 'ARP-SOLANA-PARTIAL-RELEASE-v1.5', got '${purpose}'.`);
|
|
5495
6343
|
}
|
|
5496
6344
|
const isPartial = purpose === "ARP-SOLANA-PARTIAL-RELEASE-v1.5";
|
|
5497
6345
|
if (isPartial && (opts.settlementPayeeAmount === void 0 || opts.settlementPayeeAmount === "")) {
|
|
@@ -5601,22 +6449,28 @@ async function runCosign(relationshipId, delegationId, requestHashArg, responseH
|
|
|
5601
6449
|
content.notes_hash = cosignNotesHash;
|
|
5602
6450
|
}
|
|
5603
6451
|
const body = { type: "receipt", content };
|
|
5604
|
-
console.log(
|
|
5605
|
-
console.log(
|
|
5606
|
-
console.log(
|
|
5607
|
-
console.log(
|
|
5608
|
-
console.log(
|
|
5609
|
-
console.log(
|
|
6452
|
+
console.log(import_chalk21.default.dim(`Server: ${api.serverUrl}`));
|
|
6453
|
+
console.log(import_chalk21.default.dim(`Sender (caller): ${sender.did}`));
|
|
6454
|
+
console.log(import_chalk21.default.dim(`Recipient (payee): ${resolved.payeeDid}`));
|
|
6455
|
+
console.log(import_chalk21.default.dim(`Delegation: ${delegationId}`));
|
|
6456
|
+
console.log(import_chalk21.default.dim(`Verdict (final): ${verdict}`));
|
|
6457
|
+
console.log(import_chalk21.default.dim(`Receipt event hash bound: ${resolved.receiptEventHash}`));
|
|
5610
6458
|
if (cosignNotesHash !== null) {
|
|
5611
|
-
console.log(
|
|
6459
|
+
console.log(import_chalk21.default.dim(`Notes hash bound: ${cosignNotesHash}`));
|
|
5612
6460
|
} else if (opts.clearNotes) {
|
|
5613
|
-
console.log(
|
|
6461
|
+
console.log(import_chalk21.default.dim("Notes binding: cleared (--clear-notes)"));
|
|
6462
|
+
}
|
|
6463
|
+
if (opts.autoResolvePayeeSig) {
|
|
6464
|
+
applyAutoResolvePayeeSig("receipt cosign", opts, resolved.payeeSettlement);
|
|
6465
|
+
console.log(
|
|
6466
|
+
import_chalk21.default.dim(`Auto-resolved payee sig from receipt.payeeSettlement (purpose=${resolved.payeeSettlement?.purpose}, expires_at=${resolved.payeeSettlement?.expires_at})`)
|
|
6467
|
+
);
|
|
5614
6468
|
}
|
|
5615
6469
|
const settlementSigs = assembleSettlementSignaturesAttachment(opts);
|
|
5616
6470
|
const attachments = { co_signature: cosignature };
|
|
5617
6471
|
if (settlementSigs) {
|
|
5618
6472
|
attachments.settlement_signatures = settlementSigs;
|
|
5619
|
-
console.log(
|
|
6473
|
+
console.log(import_chalk21.default.dim(`Settlement signatures attached: purpose=${settlementSigs.purpose}`));
|
|
5620
6474
|
}
|
|
5621
6475
|
const result = await sendReceiptEnvelope({
|
|
5622
6476
|
api,
|
|
@@ -5629,6 +6483,147 @@ async function runCosign(relationshipId, delegationId, requestHashArg, responseH
|
|
|
5629
6483
|
server: opts.server
|
|
5630
6484
|
});
|
|
5631
6485
|
printIngestResult3(result);
|
|
6486
|
+
if (opts.waitUntil) {
|
|
6487
|
+
const untilPhase = parseUntilPhase(opts.waitUntil);
|
|
6488
|
+
if (untilPhase === void 0) {
|
|
6489
|
+
throw new Error(`receipt cosign: --wait-until requires a phase value (got ${JSON.stringify(opts.waitUntil)})`);
|
|
6490
|
+
}
|
|
6491
|
+
await awaitFsmTransitionAfterAction({
|
|
6492
|
+
api,
|
|
6493
|
+
signerDid: sender.did,
|
|
6494
|
+
signer: makeSigner(sender),
|
|
6495
|
+
relationshipId: result.relationshipId,
|
|
6496
|
+
untilPhase,
|
|
6497
|
+
waitIntervalSec: parseWaitInterval(opts.waitInterval),
|
|
6498
|
+
waitTimeoutSec: parseWaitTimeout(opts.waitTimeout),
|
|
6499
|
+
waitVerbose: !!opts.waitVerbose,
|
|
6500
|
+
json: false
|
|
6501
|
+
});
|
|
6502
|
+
}
|
|
6503
|
+
}
|
|
6504
|
+
function registerSendPayeeSig(parent) {
|
|
6505
|
+
parent.command("send-payee-sig").description("Send the payee's settlement signature to the buyer via a settlement_signature envelope. Replaces /tmp/*.json coordination for cross-host escrow cycles.").argument("<recipient-did>", "Buyer DID (= caller / offerer of the parent delegation; the cycle sends the sig to the side that will cosign)").requiredOption("--delegation-id <id>", "Parent delegation UUID \u2014 must match what was passed to `heyarp wallet sign-settlement-release`.").requiredOption(
|
|
6506
|
+
"--receipt-event-hash <sha256:hex>",
|
|
6507
|
+
"Server-assigned `serverEventHash` of the receipt-propose envelope this signature settles. Read it from the JSON response of `heyarp receipt propose` (the field is `Server event hash` in human output, or `.serverEventHash` in --json). MUST equal the value the digest was signed over \u2014 the server cross-checks against `Receipt.receiptEventHash` and rejects with SETTLEMENT_SIG_RECEIPT_NOT_FOUND otherwise."
|
|
6508
|
+
).requiredOption(
|
|
6509
|
+
"--sig-from-file <path>",
|
|
6510
|
+
"JSON file produced by `heyarp wallet sign-settlement-release --write-to <path>`. Reads `{settlement_pubkey, sig, purpose, digest_hex}` \u2014 the digest_hex is bound through for cross-file consistency (catches truncated or hand-edited files)."
|
|
6511
|
+
).requiredOption(
|
|
6512
|
+
"--expires-at <unix>",
|
|
6513
|
+
"Unix-seconds expires_at value baked into the signed digest \u2014 MUST match the value passed to `--expires-at` on `heyarp wallet sign-settlement-release`. Server cross-checks against the buyer-side value at cosign time; wrong value \u2192 ESC_SETTLEMENT_EXPIRES_AT_PAST/_TOO_SOON."
|
|
6514
|
+
).option(
|
|
6515
|
+
"--payee-amount <int>",
|
|
6516
|
+
"Base-unit decimal-integer payee_amount \u2014 REQUIRED when the sig file's purpose is `ARP-SOLANA-PARTIAL-RELEASE-v1.5` (usage_based contracts). Same value passed to `--partial-payee-amount` on sign-settlement-release. FORBIDDEN for `ARP-SOLANA-RELEASE-v1.5` (full release settles the whole lock)."
|
|
6517
|
+
).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 (= payee)").option("--ttl <seconds>", "Envelope TTL in seconds", "3600").option("--verbose", "Print the full envelope before sending and the full server response", false).action(async (recipientDid, opts, cmd) => {
|
|
6518
|
+
try {
|
|
6519
|
+
await runSendPayeeSig(recipientDid, opts);
|
|
6520
|
+
} catch (err) {
|
|
6521
|
+
emitActionError(err, cmd);
|
|
6522
|
+
process.exitCode = 1;
|
|
6523
|
+
}
|
|
6524
|
+
});
|
|
6525
|
+
}
|
|
6526
|
+
async function runSendPayeeSig(recipientDid, opts) {
|
|
6527
|
+
const cmdName = "receipt send-payee-sig";
|
|
6528
|
+
requireDid3(cmdName, recipientDid, "<recipient-did>");
|
|
6529
|
+
const delegationId = requireUuidNormalised2(cmdName, opts.delegationId, "--delegation-id");
|
|
6530
|
+
requireSha256(cmdName, opts.receiptEventHash, "--receipt-event-hash");
|
|
6531
|
+
const expiresAtSeconds = parseInteger(cmdName, "--expires-at", opts.expiresAt);
|
|
6532
|
+
if (expiresAtSeconds <= 0) {
|
|
6533
|
+
throw new Error(`${cmdName}: --expires-at must be a positive unix-seconds integer (got ${opts.expiresAt})`);
|
|
6534
|
+
}
|
|
6535
|
+
const ttlSeconds = parseTtl4(cmdName, opts.ttl);
|
|
6536
|
+
const sigFile = loadSettlementSigFromFile(opts.sigFromFile, "--sig-from-file");
|
|
6537
|
+
const isPartial = sigFile.purpose === "ARP-SOLANA-PARTIAL-RELEASE-v1.5";
|
|
6538
|
+
const isFull = sigFile.purpose === "ARP-SOLANA-RELEASE-v1.5";
|
|
6539
|
+
if (!isPartial && !isFull) {
|
|
6540
|
+
throw new Error(
|
|
6541
|
+
`${cmdName}: sig file at '${opts.sigFromFile}' has purpose='${sigFile.purpose}' but only ARP-SOLANA-RELEASE-v1.5 and ARP-SOLANA-PARTIAL-RELEASE-v1.5 are valid on a settlement_signature envelope. (REFUND-v1 sigs ride other paths \u2014 they don't bind to receipt_event_hash, so this envelope isn't the right channel for them.)`
|
|
6542
|
+
);
|
|
6543
|
+
}
|
|
6544
|
+
if (isPartial && (opts.payeeAmount === void 0 || opts.payeeAmount === "")) {
|
|
6545
|
+
throw new Error(
|
|
6546
|
+
`${cmdName}: --payee-amount is REQUIRED when the sig file's purpose is ARP-SOLANA-PARTIAL-RELEASE-v1.5 (the server reconstructs the same digest at cosign and binds payee_amount into it; missing here = unverifiable digest).`
|
|
6547
|
+
);
|
|
6548
|
+
}
|
|
6549
|
+
if (isFull && opts.payeeAmount !== void 0 && opts.payeeAmount !== "") {
|
|
6550
|
+
throw new Error(
|
|
6551
|
+
`${cmdName}: --payee-amount must be OMITTED when the sig file's purpose is ARP-SOLANA-RELEASE-v1.5 (full release settles the whole lock; supplying an amount alongside the RELEASE purpose is almost certainly a wrong-purpose-flag bug).`
|
|
6552
|
+
);
|
|
6553
|
+
}
|
|
6554
|
+
if (opts.payeeAmount !== void 0 && opts.payeeAmount !== "" && !/^[0-9]+$/.test(opts.payeeAmount)) {
|
|
6555
|
+
throw new Error(
|
|
6556
|
+
`${cmdName}: --payee-amount must be a decimal-integer base-unit string (got '${opts.payeeAmount}'); hex/empty/signed/decimal forms are rejected \u2014 same invariant as receipt.usage.computed_amount.`
|
|
6557
|
+
);
|
|
6558
|
+
}
|
|
6559
|
+
const sender = resolveSenderAgent(cmdName, opts.server, opts.fromDid);
|
|
6560
|
+
const api = new ArpApiClient(opts.server);
|
|
6561
|
+
const content = {
|
|
6562
|
+
delegation_id: delegationId,
|
|
6563
|
+
receipt_event_hash: opts.receiptEventHash,
|
|
6564
|
+
purpose: sigFile.purpose,
|
|
6565
|
+
payee_settlement_pubkey: sigFile.settlement_pubkey,
|
|
6566
|
+
sig: sigFile.sig,
|
|
6567
|
+
expires_at: expiresAtSeconds,
|
|
6568
|
+
...isPartial ? { payee_amount: opts.payeeAmount } : {}
|
|
6569
|
+
};
|
|
6570
|
+
console.log(import_chalk21.default.dim(`Server: ${api.serverUrl}`));
|
|
6571
|
+
console.log(import_chalk21.default.dim(`Sender (payee): ${sender.did}`));
|
|
6572
|
+
console.log(import_chalk21.default.dim(`Recipient (buyer): ${recipientDid}`));
|
|
6573
|
+
console.log(import_chalk21.default.dim(`Delegation: ${delegationId}`));
|
|
6574
|
+
console.log(import_chalk21.default.dim(`Receipt event hash: ${opts.receiptEventHash}`));
|
|
6575
|
+
console.log(import_chalk21.default.dim(`Purpose: ${sigFile.purpose}`));
|
|
6576
|
+
if (isPartial) {
|
|
6577
|
+
console.log(import_chalk21.default.dim(`Payee amount: ${opts.payeeAmount}`));
|
|
6578
|
+
}
|
|
6579
|
+
console.log(import_chalk21.default.dim(`Settlement pubkey: ${sigFile.settlement_pubkey}`));
|
|
6580
|
+
console.log(import_chalk21.default.dim(`Expires at: ${expiresAtSeconds}`));
|
|
6581
|
+
const result = await sendSettlementSignatureEnvelope({
|
|
6582
|
+
api,
|
|
6583
|
+
sender,
|
|
6584
|
+
recipientDid,
|
|
6585
|
+
content,
|
|
6586
|
+
ttlSeconds,
|
|
6587
|
+
verbose: opts.verbose,
|
|
6588
|
+
server: opts.server
|
|
6589
|
+
});
|
|
6590
|
+
printIngestResult3(result);
|
|
6591
|
+
}
|
|
6592
|
+
async function sendSettlementSignatureEnvelope(args) {
|
|
6593
|
+
const nextSequence = (args.sender.lastSenderSequence ?? 0) + 1;
|
|
6594
|
+
const protectedBlock = {
|
|
6595
|
+
protocol_version: "arp/0.1",
|
|
6596
|
+
purpose: import_sdk11.Purpose.ENVELOPE,
|
|
6597
|
+
message_id: (0, import_sdk11.uuidV4)(),
|
|
6598
|
+
sender_did: args.sender.did,
|
|
6599
|
+
recipient_did: args.recipientDid,
|
|
6600
|
+
relationship_id: null,
|
|
6601
|
+
sender_sequence: nextSequence,
|
|
6602
|
+
sender_nonce: (0, import_sdk11.senderNonce)(),
|
|
6603
|
+
timestamp: (0, import_sdk11.rfc3339)(),
|
|
6604
|
+
expires_at: (0, import_sdk11.expiresAt)(args.ttlSeconds),
|
|
6605
|
+
delivery_id: null
|
|
6606
|
+
};
|
|
6607
|
+
const signer = makeSigner(args.sender);
|
|
6608
|
+
const envelope = (0, import_sdk11.signEnvelope)({
|
|
6609
|
+
protected: protectedBlock,
|
|
6610
|
+
body: { type: "settlement_signature", content: args.content },
|
|
6611
|
+
identitySecretKey: signer.identitySecretKey
|
|
6612
|
+
});
|
|
6613
|
+
if (args.verbose) {
|
|
6614
|
+
console.log(import_chalk21.default.bold("\nEnvelope (pre-send):"));
|
|
6615
|
+
console.log(formatJson(envelope));
|
|
6616
|
+
}
|
|
6617
|
+
try {
|
|
6618
|
+
const result = await args.api.ingest(envelope);
|
|
6619
|
+
updateAgentLocal(args.server, args.sender.did, { lastSenderSequence: nextSequence });
|
|
6620
|
+
return result;
|
|
6621
|
+
} catch (err) {
|
|
6622
|
+
if (err instanceof ApiError && POST_COMMIT_ERROR_CODES4.has(err.payload.code)) {
|
|
6623
|
+
updateAgentLocal(args.server, args.sender.did, { lastSenderSequence: nextSequence });
|
|
6624
|
+
}
|
|
6625
|
+
throw err;
|
|
6626
|
+
}
|
|
5632
6627
|
}
|
|
5633
6628
|
async function sendReceiptEnvelope(args) {
|
|
5634
6629
|
const nextSequence = (args.sender.lastSenderSequence ?? 0) + 1;
|
|
@@ -5653,7 +6648,7 @@ async function sendReceiptEnvelope(args) {
|
|
|
5653
6648
|
attachments: args.attachments
|
|
5654
6649
|
});
|
|
5655
6650
|
if (args.verbose) {
|
|
5656
|
-
console.log(
|
|
6651
|
+
console.log(import_chalk21.default.bold("\nEnvelope (pre-send):"));
|
|
5657
6652
|
console.log(formatJson(envelope));
|
|
5658
6653
|
}
|
|
5659
6654
|
try {
|
|
@@ -5703,15 +6698,21 @@ async function resolveCosignTargets(cmdName, api, signer, args) {
|
|
|
5703
6698
|
if (row.callerDid !== args.selfDid) {
|
|
5704
6699
|
throw new Error(`${cmdName}: this receipt's caller is ${row.callerDid}; only the caller can cosign. Switch with --from-did.`);
|
|
5705
6700
|
}
|
|
5706
|
-
return {
|
|
6701
|
+
return {
|
|
6702
|
+
payeeDid: row.payeeDid,
|
|
6703
|
+
receiptEventHash: row.receiptEventHash,
|
|
6704
|
+
verdictProposed: row.verdictProposed,
|
|
6705
|
+
notesHash: row.notesHash,
|
|
6706
|
+
payeeSettlement: row.payeeSettlement
|
|
6707
|
+
};
|
|
5707
6708
|
}
|
|
5708
6709
|
function printIngestResult3(result) {
|
|
5709
|
-
console.log(
|
|
5710
|
-
console.log(`${
|
|
5711
|
-
console.log(`${
|
|
5712
|
-
console.log(`${
|
|
5713
|
-
console.log(`${
|
|
5714
|
-
console.log(`${
|
|
6710
|
+
console.log(import_chalk21.default.green("\nDelivered."));
|
|
6711
|
+
console.log(`${import_chalk21.default.bold("Event id")}: ${import_chalk21.default.cyan(result.eventId)}`);
|
|
6712
|
+
console.log(`${import_chalk21.default.bold("Relationship id")}: ${import_chalk21.default.cyan(result.relationshipId)}`);
|
|
6713
|
+
console.log(`${import_chalk21.default.bold("Chain index")}: ${import_chalk21.default.cyan(String(result.relationshipEventIndex))}`);
|
|
6714
|
+
console.log(`${import_chalk21.default.bold("Server timestamp")}: ${import_chalk21.default.cyan(result.serverTimestamp)}`);
|
|
6715
|
+
console.log(`${import_chalk21.default.bold("Server event hash")}: ${import_chalk21.default.cyan(result.serverEventHash)}`);
|
|
5715
6716
|
}
|
|
5716
6717
|
function parseVerdict(cmdName, raw) {
|
|
5717
6718
|
if (raw === void 0 || raw === "") return "accepted";
|
|
@@ -5764,7 +6765,7 @@ function requireDid3(cmdName, did, label) {
|
|
|
5764
6765
|
}
|
|
5765
6766
|
|
|
5766
6767
|
// src/commands/receipts.ts
|
|
5767
|
-
var
|
|
6768
|
+
var import_chalk22 = __toESM(require("chalk"));
|
|
5768
6769
|
init_api();
|
|
5769
6770
|
var ALLOWED_STATES3 = /* @__PURE__ */ new Set(["proposed", "cosigned"]);
|
|
5770
6771
|
function registerReceiptsCommand(root) {
|
|
@@ -5793,9 +6794,9 @@ async function runReceipts(relationshipId, opts) {
|
|
|
5793
6794
|
const api = new ArpApiClient(opts.server);
|
|
5794
6795
|
const sender = resolveSenderAgent("receipts", opts.server, opts.fromDid);
|
|
5795
6796
|
if (!opts.json) {
|
|
5796
|
-
console.log(
|
|
5797
|
-
console.log(
|
|
5798
|
-
console.log(
|
|
6797
|
+
console.log(import_chalk22.default.dim(`Server: ${api.serverUrl}`));
|
|
6798
|
+
console.log(import_chalk22.default.dim(`Signer: ${sender.did}`));
|
|
6799
|
+
console.log(import_chalk22.default.dim(`Relationship: ${relationshipId}`));
|
|
5799
6800
|
}
|
|
5800
6801
|
const query = { limit };
|
|
5801
6802
|
if (state) query.state = state;
|
|
@@ -5808,7 +6809,7 @@ async function runReceipts(relationshipId, opts) {
|
|
|
5808
6809
|
return;
|
|
5809
6810
|
}
|
|
5810
6811
|
if (rows.length === 0) {
|
|
5811
|
-
console.log(
|
|
6812
|
+
console.log(import_chalk22.default.dim("\n(no receipts for this relationship)"));
|
|
5812
6813
|
return;
|
|
5813
6814
|
}
|
|
5814
6815
|
console.log("");
|
|
@@ -5826,28 +6827,28 @@ async function runReceipts(relationshipId, opts) {
|
|
|
5826
6827
|
}));
|
|
5827
6828
|
}
|
|
5828
6829
|
const lastId = rows[rows.length - 1].id;
|
|
5829
|
-
console.log(
|
|
6830
|
+
console.log(import_chalk22.default.dim(`
|
|
5830
6831
|
${rows.length} receipt row(s). Paginate with --after ${lastId}.`));
|
|
5831
6832
|
}
|
|
5832
6833
|
function formatReceiptLine(r, selfDid, opts = {}) {
|
|
5833
6834
|
const delegationPart = opts.fullIds ? r.delegationId : idHead3(r.delegationId);
|
|
5834
6835
|
const requestHashPart = opts.fullIds ? r.requestHash : hashHead3(r.requestHash);
|
|
5835
|
-
const id =
|
|
6836
|
+
const id = import_chalk22.default.bold(`${delegationPart}/${requestHashPart}`);
|
|
5836
6837
|
const state = colorState3(r.state).padEnd(stateColumnWidth3());
|
|
5837
6838
|
const callerHead = opts.fullIds ? r.callerDid : didHead4(r.callerDid);
|
|
5838
6839
|
const payeeHead = opts.fullIds ? r.payeeDid : didHead4(r.payeeDid);
|
|
5839
|
-
const direction = r.payeeDid === selfDid ? `${
|
|
6840
|
+
const direction = r.payeeDid === selfDid ? `${import_chalk22.default.bold("me")}(payee) \u2192 ${import_chalk22.default.dim(callerHead)}` : `${import_chalk22.default.dim(payeeHead)}(payee) \u2192 ${import_chalk22.default.bold("me")}`;
|
|
5840
6841
|
const verdict = formatVerdict(r);
|
|
5841
6842
|
const responseTail = opts.fullIds ? `
|
|
5842
|
-
${
|
|
6843
|
+
${import_chalk22.default.dim("responseHash:")} ${import_chalk22.default.cyan(r.responseHash)}` : "";
|
|
5843
6844
|
return `${id} ${state} ${direction} ${verdict}${responseTail}`;
|
|
5844
6845
|
}
|
|
5845
6846
|
function colorState3(s) {
|
|
5846
6847
|
switch (s) {
|
|
5847
6848
|
case "proposed":
|
|
5848
|
-
return
|
|
6849
|
+
return import_chalk22.default.yellow("proposed");
|
|
5849
6850
|
case "cosigned":
|
|
5850
|
-
return
|
|
6851
|
+
return import_chalk22.default.green("cosigned");
|
|
5851
6852
|
}
|
|
5852
6853
|
}
|
|
5853
6854
|
function stateColumnWidth3() {
|
|
@@ -5857,11 +6858,11 @@ function formatVerdict(r) {
|
|
|
5857
6858
|
const final = r.verdictFinal ?? r.verdictProposed;
|
|
5858
6859
|
switch (final) {
|
|
5859
6860
|
case "accepted":
|
|
5860
|
-
return
|
|
6861
|
+
return import_chalk22.default.green("accepted");
|
|
5861
6862
|
case "accepted_with_notes":
|
|
5862
|
-
return
|
|
6863
|
+
return import_chalk22.default.yellow("accepted_with_notes");
|
|
5863
6864
|
case "rejected":
|
|
5864
|
-
return
|
|
6865
|
+
return import_chalk22.default.red("rejected");
|
|
5865
6866
|
}
|
|
5866
6867
|
}
|
|
5867
6868
|
function idHead3(id) {
|
|
@@ -5894,9 +6895,9 @@ function parseLimit7(raw) {
|
|
|
5894
6895
|
|
|
5895
6896
|
// src/commands/register.ts
|
|
5896
6897
|
var import_node_crypto = require("crypto");
|
|
5897
|
-
var
|
|
6898
|
+
var import_node_fs9 = require("fs");
|
|
5898
6899
|
var import_sdk12 = require("@heyanon-arp/sdk");
|
|
5899
|
-
var
|
|
6900
|
+
var import_chalk23 = __toESM(require("chalk"));
|
|
5900
6901
|
var import_prompts2 = __toESM(require("prompts"));
|
|
5901
6902
|
init_api();
|
|
5902
6903
|
init_paths();
|
|
@@ -5956,17 +6957,17 @@ async function runRegister(opts) {
|
|
|
5956
6957
|
const publication = parsePublicationMode(opts.publication ?? "active");
|
|
5957
6958
|
assertJsonRequiresYes(opts);
|
|
5958
6959
|
const api = new ArpApiClient(opts.server);
|
|
5959
|
-
if (!opts.json) console.log(
|
|
6960
|
+
if (!opts.json) console.log(import_chalk23.default.dim(`Server: ${api.serverUrl}`));
|
|
5960
6961
|
if (!opts.json) {
|
|
5961
6962
|
warnIfAgentsAlreadyRegistered(opts.server);
|
|
5962
6963
|
warnIfOrphanHomesPresent();
|
|
5963
6964
|
}
|
|
5964
6965
|
const keys = opts.fromKeys ? loadKeysFromFile(opts.fromKeys) : freshKeys();
|
|
5965
6966
|
const did = (0, import_sdk12.formatDid)(keys.identityPublicKey);
|
|
5966
|
-
if (!opts.json) console.log(
|
|
6967
|
+
if (!opts.json) console.log(import_chalk23.default.dim(`DID will be: ${did}`));
|
|
5967
6968
|
const answers = await mergeAnswers(opts);
|
|
5968
6969
|
const scryptSalt = (0, import_node_crypto.randomBytes)(16);
|
|
5969
|
-
if (!opts.json) console.log(
|
|
6970
|
+
if (!opts.json) console.log(import_chalk23.default.dim("Deriving scrypt key, this may take a moment..."));
|
|
5970
6971
|
const scryptKey = (0, import_sdk12.deriveScryptKey)(answers.password, new Uint8Array(scryptSalt));
|
|
5971
6972
|
const challenge = await api.issueChallenge("register");
|
|
5972
6973
|
const challengeBytes = base64UrlNoPadDecode(challenge.challengeB64);
|
|
@@ -6045,13 +7046,13 @@ async function runRegister(opts) {
|
|
|
6045
7046
|
try {
|
|
6046
7047
|
recordHome(arpHomeDir());
|
|
6047
7048
|
} catch (registryErr) {
|
|
6048
|
-
if (!opts.json) console.log(
|
|
7049
|
+
if (!opts.json) console.log(import_chalk23.default.dim(`(homes registry write failed: ${registryErr.message})`));
|
|
6049
7050
|
}
|
|
6050
7051
|
if (!opts.json) {
|
|
6051
|
-
console.log(
|
|
6052
|
-
console.log(`${
|
|
6053
|
-
console.log(`${
|
|
6054
|
-
console.log(
|
|
7052
|
+
console.log(import_chalk23.default.green("\nRegistered."));
|
|
7053
|
+
console.log(`${import_chalk23.default.bold("DID")}: ${import_chalk23.default.cyan(result.did)}`);
|
|
7054
|
+
console.log(`${import_chalk23.default.bold("Settlement pubkey")} ${import_chalk23.default.dim("(fund with SOL)")}: ${import_chalk23.default.cyan(settlementPublicKeyB58)}`);
|
|
7055
|
+
console.log(import_chalk23.default.bold("DID document:"));
|
|
6055
7056
|
console.log(formatJson(result.didDocument));
|
|
6056
7057
|
}
|
|
6057
7058
|
const decision = decideAutoPublish({ publication, defaultEndpointUrl: answers.defaultEndpointUrl });
|
|
@@ -6061,30 +7062,33 @@ async function runRegister(opts) {
|
|
|
6061
7062
|
const signer = makeSignerFromSecret(keys.identitySecretKey, result.did);
|
|
6062
7063
|
await api.publishAgent(result.did, signer);
|
|
6063
7064
|
publicationOutcome = "published";
|
|
6064
|
-
if (!opts.json) console.log(
|
|
7065
|
+
if (!opts.json) console.log(import_chalk23.default.green(`
|
|
6065
7066
|
\u2713 Published \u2014 discoverable now via \`heyarp agents\`.`));
|
|
6066
7067
|
} catch (err) {
|
|
6067
7068
|
publicationOutcome = `failed: ${err.message}`;
|
|
6068
|
-
if (!opts.json)
|
|
6069
|
-
|
|
7069
|
+
if (!opts.json)
|
|
7070
|
+
console.log(
|
|
7071
|
+
import_chalk23.default.yellow(`
|
|
7072
|
+
\u26A0 Auto-publish failed (${err.message}). Agent saved as DRAFT \u2014 run \`heyarp publish ${result.did}\` to make it discoverable.`)
|
|
7073
|
+
);
|
|
6070
7074
|
}
|
|
6071
7075
|
} else if (decision === "skip-no-endpoint") {
|
|
6072
7076
|
publicationOutcome = "skip-no-endpoint";
|
|
6073
7077
|
if (!opts.json) {
|
|
6074
|
-
console.log(
|
|
7078
|
+
console.log(import_chalk23.default.dim(`
|
|
6075
7079
|
Auto-publish skipped: no --endpoint-url supplied (buyer-only registration). Agent is saved as DRAFT.`));
|
|
6076
|
-
console.log(
|
|
6077
|
-
console.log(
|
|
7080
|
+
console.log(import_chalk23.default.dim(`If this agent should also accept work, re-register with \`--endpoint-url <https://\u2026>\` OR run`));
|
|
7081
|
+
console.log(import_chalk23.default.dim(`\`heyarp rotate ${result.did} --endpoint-url <https://\u2026>\` then \`heyarp publish ${result.did}\`.`));
|
|
6078
7082
|
}
|
|
6079
7083
|
} else {
|
|
6080
7084
|
publicationOutcome = "draft";
|
|
6081
7085
|
if (!opts.json) {
|
|
6082
|
-
console.log(
|
|
7086
|
+
console.log(import_chalk23.default.dim(`
|
|
6083
7087
|
Publication mode: DRAFT. Agent is registered but NOT discoverable via \`heyarp agents\`.`));
|
|
6084
|
-
console.log(
|
|
7088
|
+
console.log(import_chalk23.default.dim(`Run \`heyarp publish ${result.did}\` when ready to appear in the catalog.`));
|
|
6085
7089
|
}
|
|
6086
7090
|
}
|
|
6087
|
-
if (!opts.json) console.log(
|
|
7091
|
+
if (!opts.json) console.log(import_chalk23.default.dim(`
|
|
6088
7092
|
Local state saved to ${arpHomeDir()}/agents.json (mode 0600).`));
|
|
6089
7093
|
if (opts.json) {
|
|
6090
7094
|
console.log(
|
|
@@ -6169,7 +7173,7 @@ async function mergeAnswers(opts) {
|
|
|
6169
7173
|
}
|
|
6170
7174
|
const prompted = promptDefs.length > 0 ? await (0, import_prompts2.default)(promptDefs, {
|
|
6171
7175
|
onCancel: () => {
|
|
6172
|
-
console.log(
|
|
7176
|
+
console.log(import_chalk23.default.yellow("\nAborted."));
|
|
6173
7177
|
process.exit(130);
|
|
6174
7178
|
}
|
|
6175
7179
|
}) : {};
|
|
@@ -6192,16 +7196,16 @@ function warnIfOrphanHomesPresent() {
|
|
|
6192
7196
|
try {
|
|
6193
7197
|
others = listHomes().filter((h) => h.path !== current);
|
|
6194
7198
|
} catch (registryErr) {
|
|
6195
|
-
console.log(
|
|
7199
|
+
console.log(import_chalk23.default.dim(`(homes registry unreadable, skipping orphan-home check: ${registryErr.message})`));
|
|
6196
7200
|
return;
|
|
6197
7201
|
}
|
|
6198
7202
|
if (others.length === 0) return;
|
|
6199
|
-
const list = others.map((h) => ` \u2022 ${
|
|
6200
|
-
console.log(
|
|
7203
|
+
const list = others.map((h) => ` \u2022 ${import_chalk23.default.cyan(h.path)} ${import_chalk23.default.dim(`(last seen ${h.lastSeenAt})`)}`).join("\n");
|
|
7204
|
+
console.log(import_chalk23.default.yellow(`
|
|
6201
7205
|
\u26A0 HEYARP_HOME is unset, but other agent homes are registered on this machine:`));
|
|
6202
7206
|
console.log(list);
|
|
6203
7207
|
console.log(
|
|
6204
|
-
|
|
7208
|
+
import_chalk23.default.dim(
|
|
6205
7209
|
` Registering will create a NEW agent under ${current}.
|
|
6206
7210
|
If you meant to add to an existing home, abort (Ctrl-C) and re-run with:
|
|
6207
7211
|
HEYARP_HOME=<path> heyarp register \u2026
|
|
@@ -6214,11 +7218,11 @@ function warnIfAgentsAlreadyRegistered(serverOverride) {
|
|
|
6214
7218
|
const targetServer = resolveServerUrl(serverOverride);
|
|
6215
7219
|
const existing = listAgents().filter((row) => row.serverUrl === targetServer);
|
|
6216
7220
|
if (existing.length === 0) return;
|
|
6217
|
-
const list = existing.map((row) => ` \u2022 ${
|
|
6218
|
-
console.log(
|
|
7221
|
+
const list = existing.map((row) => ` \u2022 ${import_chalk23.default.cyan(row.agent.did)}${row.agent.name ? import_chalk23.default.dim(` (${row.agent.name})`) : ""}`).join("\n");
|
|
7222
|
+
console.log(import_chalk23.default.yellow("\n\u26A0 ~/.arp/agents.json already has agent(s) for this server:"));
|
|
6219
7223
|
console.log(list);
|
|
6220
7224
|
console.log(
|
|
6221
|
-
|
|
7225
|
+
import_chalk23.default.dim(
|
|
6222
7226
|
" After this register completes, you will have multiple local DIDs sharing one state file.\n To keep their state isolated, run with HEYARP_HOME pointing at a per-agent dir, e.g.\n HEYARP_HOME=/tmp/agent-alice heyarp register \u2026\n Otherwise, pass --from-did <did> explicitly on every signed command.\n"
|
|
6223
7227
|
)
|
|
6224
7228
|
);
|
|
@@ -6238,12 +7242,12 @@ function freshKeys() {
|
|
|
6238
7242
|
};
|
|
6239
7243
|
}
|
|
6240
7244
|
function loadKeysFromFile(path) {
|
|
6241
|
-
if (!(0,
|
|
7245
|
+
if (!(0, import_node_fs9.existsSync)(path)) {
|
|
6242
7246
|
throw new Error(`--from-keys: file not found at ${path}`);
|
|
6243
7247
|
}
|
|
6244
7248
|
let parsed;
|
|
6245
7249
|
try {
|
|
6246
|
-
parsed = JSON.parse((0,
|
|
7250
|
+
parsed = JSON.parse((0, import_node_fs9.readFileSync)(path, "utf8"));
|
|
6247
7251
|
} catch (err) {
|
|
6248
7252
|
throw new Error(`--from-keys: ${path} is not valid JSON: ${err.message}`);
|
|
6249
7253
|
}
|
|
@@ -6285,7 +7289,7 @@ function makeSignerFromSecret(identitySecretKey, did) {
|
|
|
6285
7289
|
}
|
|
6286
7290
|
|
|
6287
7291
|
// src/commands/relationships.ts
|
|
6288
|
-
var
|
|
7292
|
+
var import_chalk24 = __toESM(require("chalk"));
|
|
6289
7293
|
init_api();
|
|
6290
7294
|
var ALLOWED_STATES4 = /* @__PURE__ */ new Set(["pending", "active", "paused", "closed"]);
|
|
6291
7295
|
function registerRelationshipsCommand(root) {
|
|
@@ -6305,25 +7309,25 @@ async function runRelationships(positionalDid, opts) {
|
|
|
6305
7309
|
const local = explicitDid !== void 0 ? loadAgentOrThrow(opts.server, explicitDid) : resolveSenderAgent("relationships", opts.server, void 0);
|
|
6306
7310
|
const did = local.did;
|
|
6307
7311
|
const api = new ArpApiClient(opts.server);
|
|
6308
|
-
console.log(
|
|
6309
|
-
console.log(
|
|
7312
|
+
console.log(import_chalk24.default.dim(`Server: ${api.serverUrl}`));
|
|
7313
|
+
console.log(import_chalk24.default.dim(`Signer: ${local.did}`));
|
|
6310
7314
|
const query = { limit };
|
|
6311
7315
|
if (state) query.state = state;
|
|
6312
7316
|
const signer = makeSigner(local);
|
|
6313
7317
|
const rows = await api.listRelationships(did, signer, query);
|
|
6314
7318
|
if (rows.length === 0) {
|
|
6315
|
-
console.log(
|
|
7319
|
+
console.log(import_chalk24.default.dim("\n(no relationships)"));
|
|
6316
7320
|
return;
|
|
6317
7321
|
}
|
|
6318
7322
|
console.log("");
|
|
6319
7323
|
console.log(formatRelationshipsTable(rows, did));
|
|
6320
7324
|
if (opts.verbose) {
|
|
6321
|
-
console.log(
|
|
7325
|
+
console.log(import_chalk24.default.bold("\nFull relationships:"));
|
|
6322
7326
|
for (const r of rows) {
|
|
6323
7327
|
console.log(formatJson(r));
|
|
6324
7328
|
}
|
|
6325
7329
|
}
|
|
6326
|
-
console.log(
|
|
7330
|
+
console.log(import_chalk24.default.dim(`
|
|
6327
7331
|
${rows.length} relationship(s).`));
|
|
6328
7332
|
}
|
|
6329
7333
|
function formatRelationshipsTable(rows, selfDid) {
|
|
@@ -6331,7 +7335,7 @@ function formatRelationshipsTable(rows, selfDid) {
|
|
|
6331
7335
|
const data = rows.map((r) => [r.relationshipId, otherPair(r, selfDid), r.state, r.lastEventAt ?? "(none)", String(r.lastEventIndex)]);
|
|
6332
7336
|
const widths = header.map((h, i) => Math.max(h.length, ...data.map((row) => row[i].length)));
|
|
6333
7337
|
const pad = (cells) => cells.map((c, i) => c.padEnd(widths[i])).join(" ");
|
|
6334
|
-
return [
|
|
7338
|
+
return [import_chalk24.default.bold(pad(header)), import_chalk24.default.dim(pad(widths.map((w) => "-".repeat(w)))), ...data.map((row) => pad(row))].join("\n");
|
|
6335
7339
|
}
|
|
6336
7340
|
function otherPair(r, selfDid) {
|
|
6337
7341
|
if (r.pairDidA === selfDid) return r.pairDidB;
|
|
@@ -6356,7 +7360,7 @@ function parseLimit8(raw) {
|
|
|
6356
7360
|
|
|
6357
7361
|
// src/commands/rotate.ts
|
|
6358
7362
|
var import_sdk13 = require("@heyanon-arp/sdk");
|
|
6359
|
-
var
|
|
7363
|
+
var import_chalk25 = __toESM(require("chalk"));
|
|
6360
7364
|
var import_prompts3 = __toESM(require("prompts"));
|
|
6361
7365
|
init_api();
|
|
6362
7366
|
var ROTATION_REASONS = ["scheduled", "compromise", "lost_device", "other"];
|
|
@@ -6374,8 +7378,8 @@ async function runRotate(did, opts) {
|
|
|
6374
7378
|
throw new Error("rotate: local state is missing ownerId / currentAttestationId. State predates the rotation flow \u2014 please re-register.");
|
|
6375
7379
|
}
|
|
6376
7380
|
const api = new ArpApiClient(opts.server);
|
|
6377
|
-
console.log(
|
|
6378
|
-
console.log(
|
|
7381
|
+
console.log(import_chalk25.default.dim(`Server: ${api.serverUrl}`));
|
|
7382
|
+
console.log(import_chalk25.default.dim(`Rotating: ${local.did}`));
|
|
6379
7383
|
if (!opts.yes) {
|
|
6380
7384
|
const confirm = await (0, import_prompts3.default)({
|
|
6381
7385
|
type: "confirm",
|
|
@@ -6384,7 +7388,7 @@ async function runRotate(did, opts) {
|
|
|
6384
7388
|
initial: false
|
|
6385
7389
|
});
|
|
6386
7390
|
if (!confirm.go) {
|
|
6387
|
-
console.log(
|
|
7391
|
+
console.log(import_chalk25.default.yellow("Aborted."));
|
|
6388
7392
|
return;
|
|
6389
7393
|
}
|
|
6390
7394
|
}
|
|
@@ -6460,22 +7464,22 @@ async function runRotate(did, opts) {
|
|
|
6460
7464
|
pendingRotation: void 0
|
|
6461
7465
|
});
|
|
6462
7466
|
} catch (err) {
|
|
6463
|
-
console.error(
|
|
6464
|
-
console.error(
|
|
6465
|
-
console.error(` ${
|
|
6466
|
-
console.error(` ${
|
|
6467
|
-
console.error(` ${
|
|
6468
|
-
console.error(
|
|
6469
|
-
console.error(
|
|
7467
|
+
console.error(import_chalk25.default.red("\nServer rotation succeeded but local state write failed."));
|
|
7468
|
+
console.error(import_chalk25.default.red("Capture these values now \u2014 the new key is already live server-side:"));
|
|
7469
|
+
console.error(` ${import_chalk25.default.bold("identityPublicKeyB58")} : ${newIdentityPublicKeyB58}`);
|
|
7470
|
+
console.error(` ${import_chalk25.default.bold("identitySecretKeyB64")} : ${newIdentitySecretKeyB64}`);
|
|
7471
|
+
console.error(` ${import_chalk25.default.bold("currentAttestationId")} : ${updated.currentAttestationId}`);
|
|
7472
|
+
console.error(import_chalk25.default.dim(`(Also persisted in pendingRotation at ~/.arp/agents.json before the server call.)`));
|
|
7473
|
+
console.error(import_chalk25.default.dim(`Underlying error: ${err.message}`));
|
|
6470
7474
|
throw err;
|
|
6471
7475
|
}
|
|
6472
|
-
console.log(
|
|
6473
|
-
console.log(`${
|
|
6474
|
-
console.log(`${
|
|
6475
|
-
console.log(`${
|
|
6476
|
-
console.log(
|
|
7476
|
+
console.log(import_chalk25.default.green("\nRotated."));
|
|
7477
|
+
console.log(`${import_chalk25.default.bold("DID")}: ${import_chalk25.default.cyan(updated.did)} ${import_chalk25.default.dim("(unchanged)")}`);
|
|
7478
|
+
console.log(`${import_chalk25.default.bold("New identity public key")}: ${import_chalk25.default.cyan(newIdentityPublicKeyB58)}`);
|
|
7479
|
+
console.log(`${import_chalk25.default.bold("New attestation id")}: ${import_chalk25.default.cyan(updated.currentAttestationId)}`);
|
|
7480
|
+
console.log(import_chalk25.default.bold("\nAgent profile:"));
|
|
6477
7481
|
console.log(formatJson(updated));
|
|
6478
|
-
console.log(
|
|
7482
|
+
console.log(import_chalk25.default.dim("\nLocal state updated; old private key is no longer valid."));
|
|
6479
7483
|
}
|
|
6480
7484
|
function base64UrlNoPadDecode2(s) {
|
|
6481
7485
|
const replaced = s.replace(/-/g, "+").replace(/_/g, "/");
|
|
@@ -6485,7 +7489,7 @@ function base64UrlNoPadDecode2(s) {
|
|
|
6485
7489
|
|
|
6486
7490
|
// src/commands/send-handshake.ts
|
|
6487
7491
|
var import_sdk14 = require("@heyanon-arp/sdk");
|
|
6488
|
-
var
|
|
7492
|
+
var import_chalk26 = __toESM(require("chalk"));
|
|
6489
7493
|
init_api();
|
|
6490
7494
|
function registerSendHandshakeCommand(root) {
|
|
6491
7495
|
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", false).action(async (recipientDid, opts) => {
|
|
@@ -6498,10 +7502,10 @@ async function runSendHandshake(recipientDid, opts) {
|
|
|
6498
7502
|
}
|
|
6499
7503
|
const ttlSeconds = parseTtl5(opts.ttl);
|
|
6500
7504
|
const api = new ArpApiClient(opts.server);
|
|
6501
|
-
console.log(
|
|
7505
|
+
console.log(import_chalk26.default.dim(`Server: ${api.serverUrl}`));
|
|
6502
7506
|
const sender = resolveSenderAgent("send-handshake", opts.server, opts.fromDid);
|
|
6503
|
-
console.log(
|
|
6504
|
-
console.log(
|
|
7507
|
+
console.log(import_chalk26.default.dim(`Sender: ${sender.did}`));
|
|
7508
|
+
console.log(import_chalk26.default.dim(`Recipient: ${recipientDid}`));
|
|
6505
7509
|
const content = {};
|
|
6506
7510
|
if (opts.greeting) content.greeting = opts.greeting;
|
|
6507
7511
|
if (opts.intent) content.intent = opts.intent;
|
|
@@ -6527,28 +7531,28 @@ async function runSendHandshake(recipientDid, opts) {
|
|
|
6527
7531
|
identitySecretKey: signer.identitySecretKey
|
|
6528
7532
|
});
|
|
6529
7533
|
if (opts.verbose) {
|
|
6530
|
-
console.log(
|
|
7534
|
+
console.log(import_chalk26.default.bold("\nEnvelope (pre-send):"));
|
|
6531
7535
|
console.log(formatJson(envelope));
|
|
6532
7536
|
}
|
|
6533
7537
|
const result = await api.ingest(envelope);
|
|
6534
7538
|
updateAgentLocal(opts.server, sender.did, { lastSenderSequence: nextSequence });
|
|
6535
|
-
console.log(
|
|
6536
|
-
console.log(`${
|
|
6537
|
-
console.log(`${
|
|
6538
|
-
console.log(`${
|
|
6539
|
-
console.log(`${
|
|
6540
|
-
console.log(`${
|
|
6541
|
-
console.log(`${
|
|
7539
|
+
console.log(import_chalk26.default.green("\nDelivered."));
|
|
7540
|
+
console.log(`${import_chalk26.default.bold("Event id")}: ${import_chalk26.default.cyan(result.eventId)}`);
|
|
7541
|
+
console.log(`${import_chalk26.default.bold("Relationship id")}: ${import_chalk26.default.cyan(result.relationshipId)}`);
|
|
7542
|
+
console.log(`${import_chalk26.default.bold("Chain index")}: ${import_chalk26.default.cyan(String(result.relationshipEventIndex))}`);
|
|
7543
|
+
console.log(`${import_chalk26.default.bold("Server timestamp")}: ${import_chalk26.default.cyan(result.serverTimestamp)}`);
|
|
7544
|
+
console.log(`${import_chalk26.default.bold("Signed message hash")}: ${import_chalk26.default.cyan(result.signedMessageHash)}`);
|
|
7545
|
+
console.log(`${import_chalk26.default.bold("Server event hash")}: ${import_chalk26.default.cyan(result.serverEventHash)}`);
|
|
6542
7546
|
if (result.prevServerEventHash) {
|
|
6543
|
-
console.log(`${
|
|
7547
|
+
console.log(`${import_chalk26.default.bold("Prev server event hash")}: ${import_chalk26.default.cyan(result.prevServerEventHash)}`);
|
|
6544
7548
|
} else {
|
|
6545
|
-
console.log(`${
|
|
7549
|
+
console.log(`${import_chalk26.default.bold("Prev server event hash")}: ${import_chalk26.default.dim("(null \u2014 first event of this relationship)")}`);
|
|
6546
7550
|
}
|
|
6547
7551
|
if (opts.verbose) {
|
|
6548
|
-
console.log(
|
|
7552
|
+
console.log(import_chalk26.default.bold("\nFull server response:"));
|
|
6549
7553
|
console.log(formatJson(result));
|
|
6550
7554
|
}
|
|
6551
|
-
console.log(
|
|
7555
|
+
console.log(import_chalk26.default.dim(`
|
|
6552
7556
|
Local sender_sequence advanced to ${nextSequence}.`));
|
|
6553
7557
|
}
|
|
6554
7558
|
function parseTtl5(raw) {
|
|
@@ -6565,7 +7569,7 @@ function isDid(s) {
|
|
|
6565
7569
|
|
|
6566
7570
|
// src/commands/send-handshake-response.ts
|
|
6567
7571
|
var import_sdk15 = require("@heyanon-arp/sdk");
|
|
6568
|
-
var
|
|
7572
|
+
var import_chalk27 = __toESM(require("chalk"));
|
|
6569
7573
|
init_api();
|
|
6570
7574
|
var ALLOWED_DECISIONS = /* @__PURE__ */ new Set(["accept", "decline"]);
|
|
6571
7575
|
function registerSendHandshakeResponseCommand(root) {
|
|
@@ -6576,7 +7580,11 @@ function registerSendHandshakeResponseCommand(root) {
|
|
|
6576
7580
|
// would fire for the accept path too; validate manually
|
|
6577
7581
|
// after decision is parsed.
|
|
6578
7582
|
`When --decision=decline: required reason code (one of: ${import_sdk15.DECLINE_REASONS.join(", ")}). Carried in body.content.reason.`
|
|
6579
|
-
).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", false).option(
|
|
7583
|
+
).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(
|
|
7584
|
+
"--json",
|
|
7585
|
+
"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.",
|
|
7586
|
+
false
|
|
7587
|
+
).option(
|
|
6580
7588
|
"--force",
|
|
6581
7589
|
"Skip the pre-send relationship-state probe. Default: when the relationship with <recipient-did> is already 'active', the command short-circuits successfully (the previous response already landed). Pass --force to re-send anyway \u2014 the server still gates with DOM_INVALID_TRANSITION, so this is mostly useful for FSM-guard tests.",
|
|
6582
7590
|
false
|
|
@@ -6585,6 +7593,11 @@ function registerSendHandshakeResponseCommand(root) {
|
|
|
6585
7593
|
});
|
|
6586
7594
|
}
|
|
6587
7595
|
async function runSendHandshakeResponse(recipientDid, opts) {
|
|
7596
|
+
if (opts.verbose && opts.json) {
|
|
7597
|
+
throw new Error(
|
|
7598
|
+
"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`."
|
|
7599
|
+
);
|
|
7600
|
+
}
|
|
6588
7601
|
if (!isDid2(recipientDid)) {
|
|
6589
7602
|
throw new Error(`send-handshake-response: <recipient-did> must look like 'did:arp:...' (got '${recipientDid}')`);
|
|
6590
7603
|
}
|
|
@@ -6597,11 +7610,11 @@ async function runSendHandshakeResponse(recipientDid, opts) {
|
|
|
6597
7610
|
declinePayload = detail ? { reason, reasonDetail: detail } : { reason };
|
|
6598
7611
|
}
|
|
6599
7612
|
const api = new ArpApiClient(opts.server);
|
|
6600
|
-
|
|
7613
|
+
progress(opts.json, import_chalk27.default.dim(`Server: ${api.serverUrl}`));
|
|
6601
7614
|
const sender = resolveSenderAgent("send-handshake-response", opts.server, opts.fromDid);
|
|
6602
|
-
|
|
6603
|
-
|
|
6604
|
-
|
|
7615
|
+
progress(opts.json, import_chalk27.default.dim(`Sender: ${sender.did}`));
|
|
7616
|
+
progress(opts.json, import_chalk27.default.dim(`Recipient: ${recipientDid}`));
|
|
7617
|
+
progress(opts.json, import_chalk27.default.dim(`Decision: ${decision}`));
|
|
6605
7618
|
const signer = makeSigner(sender);
|
|
6606
7619
|
if (!opts.force) {
|
|
6607
7620
|
try {
|
|
@@ -6614,10 +7627,29 @@ async function runSendHandshakeResponse(recipientDid, opts) {
|
|
|
6614
7627
|
const events = await api.listEvents(existing.relationshipId, signer);
|
|
6615
7628
|
const previousResponseFromMe = events.find((e) => e.senderDid === sender.did && e.type === "handshake_response");
|
|
6616
7629
|
if (previousResponseFromMe) {
|
|
6617
|
-
|
|
7630
|
+
if (opts.json) {
|
|
7631
|
+
jsonOut({
|
|
7632
|
+
ok: true,
|
|
7633
|
+
idempotent: true,
|
|
7634
|
+
decision,
|
|
7635
|
+
eventId: previousResponseFromMe.eventId,
|
|
7636
|
+
relationshipId: existing.relationshipId,
|
|
7637
|
+
relationshipEventIndex: previousResponseFromMe.relationshipEventIndex,
|
|
7638
|
+
serverTimestamp: previousResponseFromMe.serverTimestamp,
|
|
7639
|
+
signedMessageHash: previousResponseFromMe.signedMessageHash,
|
|
7640
|
+
serverEventHash: previousResponseFromMe.serverEventHash,
|
|
7641
|
+
prevServerEventHash: previousResponseFromMe.prevServerEventHash ?? null,
|
|
7642
|
+
senderSequence: previousResponseFromMe.senderSequence
|
|
7643
|
+
});
|
|
7644
|
+
return;
|
|
7645
|
+
}
|
|
7646
|
+
progress(opts.json, import_chalk27.default.yellow(`
|
|
6618
7647
|
[--idempotency] Relationship ${existing.relationshipId} with ${recipientDid} is already 'active'.`));
|
|
6619
|
-
|
|
6620
|
-
|
|
7648
|
+
progress(
|
|
7649
|
+
opts.json,
|
|
7650
|
+
import_chalk27.default.dim(`A previous accept from this signer (event ${previousResponseFromMe.eventId}) landed successfully. Skipping re-send (use --force to override).`)
|
|
7651
|
+
);
|
|
7652
|
+
progress(opts.json, import_chalk27.default.dim(`Last event index: ${existing.lastEventIndex}, last server event hash: ${existing.lastServerEventHash ?? "(none)"}`));
|
|
6621
7653
|
return;
|
|
6622
7654
|
}
|
|
6623
7655
|
throw new Error(
|
|
@@ -6628,7 +7660,7 @@ async function runSendHandshakeResponse(recipientDid, opts) {
|
|
|
6628
7660
|
if (probeErr instanceof Error && /CLOSED|terminated|already 'active' from a previous ACCEPT|original initiator/i.test(probeErr.message)) {
|
|
6629
7661
|
throw probeErr;
|
|
6630
7662
|
}
|
|
6631
|
-
|
|
7663
|
+
progress(opts.json, import_chalk27.default.dim(`(idempotency probe failed; proceeding anyway: ${probeErr.message})`));
|
|
6632
7664
|
}
|
|
6633
7665
|
}
|
|
6634
7666
|
const content = { decision };
|
|
@@ -6658,28 +7690,43 @@ async function runSendHandshakeResponse(recipientDid, opts) {
|
|
|
6658
7690
|
identitySecretKey: signer.identitySecretKey
|
|
6659
7691
|
});
|
|
6660
7692
|
if (opts.verbose) {
|
|
6661
|
-
console.log(
|
|
7693
|
+
console.log(import_chalk27.default.bold("\nEnvelope (pre-send):"));
|
|
6662
7694
|
console.log(formatJson(envelope));
|
|
6663
7695
|
}
|
|
6664
7696
|
const result = await api.ingest(envelope);
|
|
6665
7697
|
updateAgentLocal(opts.server, sender.did, { lastSenderSequence: nextSequence });
|
|
6666
|
-
|
|
6667
|
-
|
|
6668
|
-
|
|
6669
|
-
|
|
6670
|
-
|
|
6671
|
-
|
|
6672
|
-
|
|
7698
|
+
if (opts.json) {
|
|
7699
|
+
jsonOut({
|
|
7700
|
+
ok: true,
|
|
7701
|
+
decision,
|
|
7702
|
+
eventId: result.eventId,
|
|
7703
|
+
relationshipId: result.relationshipId,
|
|
7704
|
+
relationshipEventIndex: result.relationshipEventIndex,
|
|
7705
|
+
serverTimestamp: result.serverTimestamp,
|
|
7706
|
+
signedMessageHash: result.signedMessageHash,
|
|
7707
|
+
serverEventHash: result.serverEventHash,
|
|
7708
|
+
prevServerEventHash: result.prevServerEventHash ?? null,
|
|
7709
|
+
senderSequence: nextSequence
|
|
7710
|
+
});
|
|
7711
|
+
return;
|
|
7712
|
+
}
|
|
7713
|
+
console.log(import_chalk27.default.green("\nDelivered."));
|
|
7714
|
+
console.log(`${import_chalk27.default.bold("Event id")}: ${import_chalk27.default.cyan(result.eventId)}`);
|
|
7715
|
+
console.log(`${import_chalk27.default.bold("Relationship id")}: ${import_chalk27.default.cyan(result.relationshipId)}`);
|
|
7716
|
+
console.log(`${import_chalk27.default.bold("Chain index")}: ${import_chalk27.default.cyan(String(result.relationshipEventIndex))}`);
|
|
7717
|
+
console.log(`${import_chalk27.default.bold("Server timestamp")}: ${import_chalk27.default.cyan(result.serverTimestamp)}`);
|
|
7718
|
+
console.log(`${import_chalk27.default.bold("Signed message hash")}: ${import_chalk27.default.cyan(result.signedMessageHash)}`);
|
|
7719
|
+
console.log(`${import_chalk27.default.bold("Server event hash")}: ${import_chalk27.default.cyan(result.serverEventHash)}`);
|
|
6673
7720
|
if (result.prevServerEventHash) {
|
|
6674
|
-
console.log(`${
|
|
7721
|
+
console.log(`${import_chalk27.default.bold("Prev server event hash")}: ${import_chalk27.default.cyan(result.prevServerEventHash)}`);
|
|
6675
7722
|
} else {
|
|
6676
|
-
console.log(`${
|
|
7723
|
+
console.log(`${import_chalk27.default.bold("Prev server event hash")}: ${import_chalk27.default.dim("(null \u2014 first event of this relationship)")}`);
|
|
6677
7724
|
}
|
|
6678
7725
|
if (opts.verbose) {
|
|
6679
|
-
console.log(
|
|
7726
|
+
console.log(import_chalk27.default.bold("\nFull server response:"));
|
|
6680
7727
|
console.log(formatJson(result));
|
|
6681
7728
|
}
|
|
6682
|
-
console.log(
|
|
7729
|
+
console.log(import_chalk27.default.dim(`
|
|
6683
7730
|
Local sender_sequence advanced to ${nextSequence}.`));
|
|
6684
7731
|
}
|
|
6685
7732
|
function parseDecision(raw) {
|
|
@@ -6732,8 +7779,356 @@ async function findExistingRelationship(api, signer, senderDid, recipientDid) {
|
|
|
6732
7779
|
return void 0;
|
|
6733
7780
|
}
|
|
6734
7781
|
|
|
7782
|
+
// src/commands/settlement.ts
|
|
7783
|
+
var import_sdk16 = require("@heyanon-arp/sdk");
|
|
7784
|
+
var import_utils3 = require("@noble/hashes/utils");
|
|
7785
|
+
var import_chalk28 = __toESM(require("chalk"));
|
|
7786
|
+
init_api();
|
|
7787
|
+
var NATIVE_SOL_MINT2 = "11111111111111111111111111111111";
|
|
7788
|
+
var SETTLEMENT_MIN_EXPIRY_HEADROOM_SECS = 120;
|
|
7789
|
+
var SETTLEMENT_REFRESH_HEADROOM_SECS = 600;
|
|
7790
|
+
function registerSettlementCommands(root) {
|
|
7791
|
+
const cmd = root.command("settlement").description("Settlement helpers \u2014 sign + deliver escrow-release signatures in one shot.");
|
|
7792
|
+
cmd.command("auto-sign-and-deliver").description(
|
|
7793
|
+
"As the PAYEE, sign the escrow-release digest for a delegation and deliver the signature to the buyer via a settlement_signature envelope \u2014 one command instead of the did-doc \u2192 derive-condition-hash \u2192 delegations \u2192 wallet sign-settlement-release \u2192 memory ritual. Run it after `heyarp receipt propose`."
|
|
7794
|
+
).requiredOption("--delegation-id <id>", "Delegation UUID whose receipt is being settled.").option("--rel-id <id>", "Relationship UUID. Auto-resolved from --delegation-id when omitted (the delegation must live in exactly one of your relationships).").option("--mint-pubkey <base58>", `Lock mint. Default native SOL (${NATIVE_SOL_MINT2}); set to the SPL token mint for token-denominated locks.`, NATIVE_SOL_MINT2).option(
|
|
7795
|
+
"--cluster-tag <int>",
|
|
7796
|
+
"Solana cluster the lock lives on: 0 = devnet, 1 = mainnet-beta. MUST match the create_lock cluster or the server-reconstructed release digest will not verify. REQUIRED \u2014 there is no default (a defaulted/wrong cluster silently signs an invalid settlement digest that fails at cosign)."
|
|
7797
|
+
).option(
|
|
7798
|
+
"--settlement-buffer-secs <int>",
|
|
7799
|
+
"Seconds added to the delegation deadline for the settlement digest expires_at (dispute buffer). Default 86400 (1 day). Ignored when --expires-at is given.",
|
|
7800
|
+
"86400"
|
|
7801
|
+
).option(
|
|
7802
|
+
"--expires-at <unix>",
|
|
7803
|
+
"Explicit settlement digest expires_at (unix seconds). Overrides deadline+buffer. REQUIRED when the delegation has no deadline (a guessed value risks exceeding the on-chain lock expiry \u2192 server reject). Must be \u2264 the lock's on-chain expiry."
|
|
7804
|
+
).option(
|
|
7805
|
+
"--partial-payee-amount <int>",
|
|
7806
|
+
"Base-unit payee_amount for a PARTIAL release (usage_based). Default: the receipt's usage.computed_amount when set. Omit for a full release of a flat contract."
|
|
7807
|
+
).option(
|
|
7808
|
+
"--fee-bps-at-lock <int>",
|
|
7809
|
+
"fee_bps_at_lock denormalised on the Lock at create_lock time (default 0 \u2014 no protocol fee). Pass the lock's actual value on fee-enabled deployments."
|
|
7810
|
+
).option("--fee-recipient-at-lock <base58>", "fee_recipient_at_lock denormalised on the Lock (default native = 1...1).").option("--server <url>", "Override ARP server base URL.").option("--from-did <did>", "Sender DID (= the payee) \u2014 required only if multiple agents are registered against this server.").option(
|
|
7811
|
+
"--receipt-event-hash <sha256:hex>",
|
|
7812
|
+
"Disambiguate when a delegation has more than one PROPOSED receipt (e.g. multiple work requests). Selects the exact receipt to settle."
|
|
7813
|
+
).option(
|
|
7814
|
+
"--force",
|
|
7815
|
+
"Re-sign and re-deliver even when a payee settlement signature already exists on the receipt. Use to REPAIR a previously-delivered sig built with wrong digest inputs (fee/mint/cluster); the server replaces the stored payee sig with the later envelope. Default is an idempotent skip.",
|
|
7816
|
+
false
|
|
7817
|
+
).option(
|
|
7818
|
+
"--json",
|
|
7819
|
+
"Machine-readable mode \u2014 emit a single JSON object on stdout {ok, delegationId, relationshipId, buyerDid, purpose, settlementPubkey, payeeAmount?, expiresAt, eventId, serverEventHash, ...}; idempotent re-run adds {idempotent:true}. Prelude moves to stderr; failures emit {code, message}.",
|
|
7820
|
+
false
|
|
7821
|
+
).action(async (opts, cmd2) => {
|
|
7822
|
+
try {
|
|
7823
|
+
await runAutoSignAndDeliver(opts);
|
|
7824
|
+
} catch (err) {
|
|
7825
|
+
emitActionError(err, cmd2);
|
|
7826
|
+
process.exitCode = 1;
|
|
7827
|
+
}
|
|
7828
|
+
});
|
|
7829
|
+
}
|
|
7830
|
+
function toBaseUnits(amountDecimal, decimals) {
|
|
7831
|
+
if (!/^[0-9]+(\.[0-9]+)?$/.test(amountDecimal)) {
|
|
7832
|
+
throw new Error(`settlement: amount '${amountDecimal}' is not a non-negative decimal number`);
|
|
7833
|
+
}
|
|
7834
|
+
if (!Number.isInteger(decimals) || decimals < 0 || decimals > 18) {
|
|
7835
|
+
throw new Error(`settlement: currency decimals must be an integer 0..18 (got ${decimals})`);
|
|
7836
|
+
}
|
|
7837
|
+
const [intPart, fracPart = ""] = amountDecimal.split(".");
|
|
7838
|
+
if (fracPart.length > decimals) {
|
|
7839
|
+
throw new Error(
|
|
7840
|
+
`settlement: amount '${amountDecimal}' has ${fracPart.length} fractional digits but the currency only has ${decimals} decimals \u2014 refusing to truncate a money value`
|
|
7841
|
+
);
|
|
7842
|
+
}
|
|
7843
|
+
const fracPadded = fracPart.padEnd(decimals, "0");
|
|
7844
|
+
const combined = `${intPart}${fracPadded}`.replace(/^0+/, "");
|
|
7845
|
+
return combined === "" ? "0" : combined;
|
|
7846
|
+
}
|
|
7847
|
+
async function findDelegationRow(api, signer, relationshipId, delegationId) {
|
|
7848
|
+
let after;
|
|
7849
|
+
for (let page = 0; page < 50; page++) {
|
|
7850
|
+
const rows = await api.listDelegations(relationshipId, signer, { limit: 100, after });
|
|
7851
|
+
if (rows.length === 0) return void 0;
|
|
7852
|
+
const match = rows.find((d) => d.delegationId === delegationId);
|
|
7853
|
+
if (match) return match;
|
|
7854
|
+
if (rows.length < 100) return void 0;
|
|
7855
|
+
after = rows[rows.length - 1].id;
|
|
7856
|
+
}
|
|
7857
|
+
return void 0;
|
|
7858
|
+
}
|
|
7859
|
+
function selectReceipt(matches, receiptEventHash, cmdName) {
|
|
7860
|
+
if (matches.length === 0) return void 0;
|
|
7861
|
+
if (receiptEventHash !== void 0 && receiptEventHash !== "") {
|
|
7862
|
+
const exact = matches.find((r) => r.receiptEventHash === receiptEventHash);
|
|
7863
|
+
if (!exact) {
|
|
7864
|
+
throw new Error(
|
|
7865
|
+
`${cmdName}: no receipt with receiptEventHash=${receiptEventHash} for this delegation (available: ${matches.map((r) => r.receiptEventHash).join(", ")})`
|
|
7866
|
+
);
|
|
7867
|
+
}
|
|
7868
|
+
return exact;
|
|
7869
|
+
}
|
|
7870
|
+
const proposed = matches.filter((r) => r.state === "proposed");
|
|
7871
|
+
if (proposed.length > 1) {
|
|
7872
|
+
throw new Error(
|
|
7873
|
+
`${cmdName}: ${proposed.length} PROPOSED receipts exist for this delegation \u2014 disambiguate with --receipt-event-hash <sha256:hex> (one of: ${proposed.map((r) => r.receiptEventHash).join(", ")})`
|
|
7874
|
+
);
|
|
7875
|
+
}
|
|
7876
|
+
if (proposed.length === 1) return proposed[0];
|
|
7877
|
+
return matches.reduce((latest, cur) => cur.createdAt > latest.createdAt ? cur : latest);
|
|
7878
|
+
}
|
|
7879
|
+
async function collectReceiptRows(api, signer, relationshipId, delegationId) {
|
|
7880
|
+
const matches = [];
|
|
7881
|
+
let after;
|
|
7882
|
+
for (let page = 0; page < 50; page++) {
|
|
7883
|
+
const rows = await api.listReceipts(relationshipId, signer, { limit: 100, after, delegationId });
|
|
7884
|
+
if (rows.length === 0) break;
|
|
7885
|
+
for (const r of rows) {
|
|
7886
|
+
if (r.delegationId === delegationId) matches.push(r);
|
|
7887
|
+
}
|
|
7888
|
+
if (rows.length < 100) break;
|
|
7889
|
+
after = rows[rows.length - 1].id;
|
|
7890
|
+
}
|
|
7891
|
+
return matches;
|
|
7892
|
+
}
|
|
7893
|
+
function settlementKeyFromDidDoc(didDoc, buyerDid) {
|
|
7894
|
+
const mb = extractField(didDoc, "verificationMethod.#settlement.publicKeyMultibase");
|
|
7895
|
+
if (typeof mb !== "string" || mb.length === 0) {
|
|
7896
|
+
throw new Error(`settlement: buyer ${buyerDid} DID document has no usable #settlement verification key`);
|
|
7897
|
+
}
|
|
7898
|
+
return mb.startsWith("z") ? mb.slice(1) : mb;
|
|
7899
|
+
}
|
|
7900
|
+
async function runAutoSignAndDeliver(opts) {
|
|
7901
|
+
const cmdName = "settlement auto-sign-and-deliver";
|
|
7902
|
+
const delegationId = requireUuidNormalised(cmdName, opts.delegationId, "--delegation-id");
|
|
7903
|
+
if (opts.relId) opts.relId = requireUuidNormalised(cmdName, opts.relId, "--rel-id");
|
|
7904
|
+
const clusterTag = opts.clusterTag;
|
|
7905
|
+
if (clusterTag !== void 0 && clusterTag !== "0" && clusterTag !== "1") {
|
|
7906
|
+
throw new Error(`${cmdName}: --cluster-tag must be '0' (devnet) or '1' (mainnet-beta), got '${clusterTag}'`);
|
|
7907
|
+
}
|
|
7908
|
+
const bufferRaw = opts.settlementBufferSecs ?? "86400";
|
|
7909
|
+
if (!/^[0-9]+$/.test(bufferRaw)) {
|
|
7910
|
+
throw new Error(`${cmdName}: --settlement-buffer-secs must be a non-negative integer (got '${opts.settlementBufferSecs}')`);
|
|
7911
|
+
}
|
|
7912
|
+
const bufferSecs = Number.parseInt(bufferRaw, 10);
|
|
7913
|
+
const api = new ArpApiClient(opts.server);
|
|
7914
|
+
const sender = resolveSenderAgent(cmdName, opts.server, opts.fromDid);
|
|
7915
|
+
const signer = makeSigner(sender);
|
|
7916
|
+
progress(opts.json, import_chalk28.default.dim(`Server: ${api.serverUrl}`));
|
|
7917
|
+
progress(opts.json, import_chalk28.default.dim(`Signer (payee): ${sender.did}`));
|
|
7918
|
+
const relId = opts.relId ?? await resolveAutoRelId(api, sender, delegationId);
|
|
7919
|
+
progress(opts.json, import_chalk28.default.dim(`Relationship: ${relId}`));
|
|
7920
|
+
const receiptMatches = await collectReceiptRows(api, signer, relId, delegationId);
|
|
7921
|
+
const receipt = selectReceipt(receiptMatches, opts.receiptEventHash, cmdName);
|
|
7922
|
+
if (!receipt) {
|
|
7923
|
+
throw new Error(`${cmdName}: no receipt found for delegation ${delegationId} under relationship ${relId}. Run \`heyarp receipt propose\` first.`);
|
|
7924
|
+
}
|
|
7925
|
+
if (receipt.payeeDid !== sender.did) {
|
|
7926
|
+
throw new Error(
|
|
7927
|
+
`${cmdName}: signer ${sender.did} is not the payee on this receipt (payee is ${receipt.payeeDid}). The settlement signature must be signed by the PAYEE \u2014 pass --from-did <payee-did> or run as the payee agent.`
|
|
7928
|
+
);
|
|
7929
|
+
}
|
|
7930
|
+
if (receipt.verdictProposed === "rejected" || receipt.verdictFinal === "rejected") {
|
|
7931
|
+
throw new Error(
|
|
7932
|
+
`${cmdName}: receipt verdict is 'rejected' \u2014 a rejected receipt is not payable; there is no settlement signature to send. (If this is wrong, the payee should re-propose the receipt with an accepting verdict.)`
|
|
7933
|
+
);
|
|
7934
|
+
}
|
|
7935
|
+
if (receipt.state === "cosigned") {
|
|
7936
|
+
if (opts.json) {
|
|
7937
|
+
jsonOut({ ok: true, idempotent: true, alreadyCosigned: true, delegationId, relationshipId: relId, buyerDid: receipt.callerDid });
|
|
7938
|
+
return;
|
|
7939
|
+
}
|
|
7940
|
+
progress(opts.json, import_chalk28.default.yellow("\n[idempotent] Receipt already cosigned \u2014 escrow release is settled. Nothing to send."));
|
|
7941
|
+
return;
|
|
7942
|
+
}
|
|
7943
|
+
if (receipt.payeeSettlement && !opts.force) {
|
|
7944
|
+
const ps = receipt.payeeSettlement;
|
|
7945
|
+
const nowSecs = Math.floor(Date.now() / 1e3);
|
|
7946
|
+
if (ps.expires_at > nowSecs + SETTLEMENT_REFRESH_HEADROOM_SECS) {
|
|
7947
|
+
if (opts.json) {
|
|
7948
|
+
jsonOut({
|
|
7949
|
+
ok: true,
|
|
7950
|
+
idempotent: true,
|
|
7951
|
+
delegationId,
|
|
7952
|
+
relationshipId: relId,
|
|
7953
|
+
buyerDid: receipt.callerDid,
|
|
7954
|
+
purpose: ps.purpose,
|
|
7955
|
+
settlementPubkey: ps.settlement_pubkey,
|
|
7956
|
+
...ps.payee_amount !== void 0 ? { payeeAmount: ps.payee_amount } : {},
|
|
7957
|
+
expiresAt: ps.expires_at
|
|
7958
|
+
});
|
|
7959
|
+
return;
|
|
7960
|
+
}
|
|
7961
|
+
progress(opts.json, import_chalk28.default.yellow("\n[idempotent] Payee settlement signature already delivered + still valid for this receipt \u2014 skipping re-send."));
|
|
7962
|
+
progress(opts.json, import_chalk28.default.dim(` purpose=${ps.purpose} settlement_pubkey=${ps.settlement_pubkey} expires_at=${ps.expires_at}`));
|
|
7963
|
+
return;
|
|
7964
|
+
}
|
|
7965
|
+
progress(
|
|
7966
|
+
opts.json,
|
|
7967
|
+
import_chalk28.default.yellow(`
|
|
7968
|
+
[refresh] Existing payee settlement sig expires at ${ps.expires_at} (\u2264 now+${SETTLEMENT_REFRESH_HEADROOM_SECS}s) \u2014 re-signing with a fresh expiry.`)
|
|
7969
|
+
);
|
|
7970
|
+
}
|
|
7971
|
+
const buyerDid = receipt.callerDid;
|
|
7972
|
+
const receiptEventHash = receipt.receiptEventHash;
|
|
7973
|
+
const deliverableHash = receipt.deliverableHash ?? receipt.responseHash;
|
|
7974
|
+
progress(opts.json, import_chalk28.default.dim(`Buyer (payer): ${buyerDid}`));
|
|
7975
|
+
progress(opts.json, import_chalk28.default.dim(`Receipt event hash: ${receiptEventHash}`));
|
|
7976
|
+
const delegation = await findDelegationRow(api, signer, relId, delegationId);
|
|
7977
|
+
if (!delegation) {
|
|
7978
|
+
throw new Error(`${cmdName}: delegation ${delegationId} not found under relationship ${relId} (paginated 5000 rows).`);
|
|
7979
|
+
}
|
|
7980
|
+
if (delegation.state !== "accepted") {
|
|
7981
|
+
throw new Error(
|
|
7982
|
+
`${cmdName}: delegation ${delegationId} is in state '${delegation.state ?? "unknown"}' \u2014 only an 'accepted' delegation is settleable (its escrow lock is still LOCKED). A non-accepted delegation's lock has been released / refunded / canceled, so the server would reject the settlement signature post-commit (SETTLEMENT_SIG_LOCK_INVALID_STATE). Nothing to settle.`
|
|
7983
|
+
);
|
|
7984
|
+
}
|
|
7985
|
+
if (!delegation.amount) {
|
|
7986
|
+
throw new Error(`${cmdName}: delegation ${delegationId} has no amount \u2014 cannot derive the lock amount for the release digest.`);
|
|
7987
|
+
}
|
|
7988
|
+
const decimals = delegation.currency?.decimals ?? 9;
|
|
7989
|
+
const lockAmount = toBaseUnits(delegation.amount, decimals);
|
|
7990
|
+
let expiresAt8;
|
|
7991
|
+
if (opts.expiresAt !== void 0 && opts.expiresAt !== "") {
|
|
7992
|
+
if (!/^[0-9]+$/.test(opts.expiresAt)) {
|
|
7993
|
+
throw new Error(`${cmdName}: --expires-at must be a positive unix-seconds integer (got '${opts.expiresAt}')`);
|
|
7994
|
+
}
|
|
7995
|
+
expiresAt8 = Number.parseInt(opts.expiresAt, 10);
|
|
7996
|
+
if (!Number.isInteger(expiresAt8) || expiresAt8 <= 0) {
|
|
7997
|
+
throw new Error(`${cmdName}: --expires-at must be a positive unix-seconds integer (got '${opts.expiresAt}')`);
|
|
7998
|
+
}
|
|
7999
|
+
} else if (delegation.deadline) {
|
|
8000
|
+
expiresAt8 = Math.floor(new Date(delegation.deadline).getTime() / 1e3) + bufferSecs;
|
|
8001
|
+
} else {
|
|
8002
|
+
throw new Error(
|
|
8003
|
+
`${cmdName}: delegation ${delegationId} has no deadline, so a safe settlement expires_at can't be derived (a guessed value risks exceeding the on-chain lock expiry \u2192 post-commit server reject). Pass --expires-at <unix-seconds> \u2264 the lock's expiry.`
|
|
8004
|
+
);
|
|
8005
|
+
}
|
|
8006
|
+
const nowSecsForExpiry = Math.floor(Date.now() / 1e3);
|
|
8007
|
+
const minSafeExpiry = nowSecsForExpiry + SETTLEMENT_MIN_EXPIRY_HEADROOM_SECS;
|
|
8008
|
+
if (!Number.isFinite(expiresAt8) || expiresAt8 <= minSafeExpiry) {
|
|
8009
|
+
const fromExplicit = opts.expiresAt !== void 0 && opts.expiresAt !== "";
|
|
8010
|
+
throw new Error(
|
|
8011
|
+
`${cmdName}: settlement expires_at (${Number.isFinite(expiresAt8) ? expiresAt8 : "NaN"}) is not far enough in the future \u2014 it must be > now+${SETTLEMENT_MIN_EXPIRY_HEADROOM_SECS}s (${minSafeExpiry}) or the server rejects it post-commit (SETTLEMENT_SIG_EXPIRES_AT_*), burning a sender_sequence. ${fromExplicit ? "Pass a later --expires-at <unix-seconds> (still \u2264 the lock's on-chain expiry)." : `Derived from deadline ${JSON.stringify(delegation.deadline)} + buffer ${bufferSecs}s \u2014 the deadline is too close or in the past. Increase --settlement-buffer-secs or pass --expires-at <unix-seconds> \u2264 the lock's expiry.`}`
|
|
8012
|
+
);
|
|
8013
|
+
}
|
|
8014
|
+
const contract = await findContractRow(api, signer, relId, delegation.contractId, void 0);
|
|
8015
|
+
if (contract.settlementModel === "prepaid") {
|
|
8016
|
+
throw new Error(
|
|
8017
|
+
`${cmdName}: contract ${contract.contractId} settlementModel is 'prepaid' (non-escrow). This command delivers an on-chain escrow-release signature; a prepaid contract has no lock to settle and the server would reject post-commit (SETTLEMENT_SIG_LOCK_NOT_FOUND). Nothing to settle on-chain.`
|
|
8018
|
+
);
|
|
8019
|
+
}
|
|
8020
|
+
const subset = projectContractForHash(contract);
|
|
8021
|
+
const conditionHash = (0, import_utils3.bytesToHex)((0, import_sdk16.deriveConditionHash)(subset));
|
|
8022
|
+
const buyerDidDoc = await api.getDidDocument(buyerDid);
|
|
8023
|
+
const payerSettlementPubkey = settlementKeyFromDidDoc(buyerDidDoc, buyerDid);
|
|
8024
|
+
const isUsageBased = contract.pricingModel === "usage_based";
|
|
8025
|
+
let partialPayeeAmount;
|
|
8026
|
+
if (isUsageBased) {
|
|
8027
|
+
const computed = receipt.usage?.computed_amount;
|
|
8028
|
+
if (computed === void 0 || computed === "" || !/^[0-9]+$/.test(computed)) {
|
|
8029
|
+
throw new Error(
|
|
8030
|
+
`${cmdName}: contract ${contract.contractId} is usage_based but the receipt has no usable usage.computed_amount (got ${computed === void 0 ? "unset" : `'${computed}'`}). The server binds the PARTIAL-RELEASE amount to that field; re-propose the receipt with a computed_amount.`
|
|
8031
|
+
);
|
|
8032
|
+
}
|
|
8033
|
+
if (opts.partialPayeeAmount !== void 0 && opts.partialPayeeAmount !== "") {
|
|
8034
|
+
if (!/^[0-9]+$/.test(opts.partialPayeeAmount)) {
|
|
8035
|
+
throw new Error(`${cmdName}: --partial-payee-amount must be a base-unit decimal-integer string (got '${opts.partialPayeeAmount}')`);
|
|
8036
|
+
}
|
|
8037
|
+
if (BigInt(opts.partialPayeeAmount) !== BigInt(computed)) {
|
|
8038
|
+
throw new Error(
|
|
8039
|
+
`${cmdName}: --partial-payee-amount ${opts.partialPayeeAmount} does not match the receipt's usage.computed_amount ${computed}. The server binds the settlement amount to the receipt \u2014 they must be equal; drop the flag or fix the value.`
|
|
8040
|
+
);
|
|
8041
|
+
}
|
|
8042
|
+
}
|
|
8043
|
+
partialPayeeAmount = computed;
|
|
8044
|
+
} else {
|
|
8045
|
+
if (opts.partialPayeeAmount !== void 0 && opts.partialPayeeAmount !== "") {
|
|
8046
|
+
throw new Error(
|
|
8047
|
+
`${cmdName}: contract ${contract.contractId} is ${contract.pricingModel ?? "flat"} (full-release only) \u2014 --partial-payee-amount is not allowed. A PARTIAL-RELEASE digest would be rejected by the server; drop the flag for a full release.`
|
|
8048
|
+
);
|
|
8049
|
+
}
|
|
8050
|
+
partialPayeeAmount = void 0;
|
|
8051
|
+
}
|
|
8052
|
+
if (clusterTag === void 0) {
|
|
8053
|
+
throw new Error(
|
|
8054
|
+
`${cmdName}: --cluster-tag is required (0 = devnet, 1 = mainnet-beta). It binds the cluster into the signed release digest; there is no safe default \u2014 pass the cluster where the create_lock lives.`
|
|
8055
|
+
);
|
|
8056
|
+
}
|
|
8057
|
+
if (opts.feeBpsAtLock !== void 0) {
|
|
8058
|
+
if (!/^[0-9]+$/.test(opts.feeBpsAtLock)) {
|
|
8059
|
+
throw new Error(`${cmdName}: --fee-bps-at-lock must be a non-negative integer (got '${opts.feeBpsAtLock}')`);
|
|
8060
|
+
}
|
|
8061
|
+
if (Number.parseInt(opts.feeBpsAtLock, 10) > 0 && (opts.feeRecipientAtLock === void 0 || opts.feeRecipientAtLock === "")) {
|
|
8062
|
+
throw new Error(
|
|
8063
|
+
`${cmdName}: --fee-bps-at-lock=${opts.feeBpsAtLock} is non-zero, so --fee-recipient-at-lock is also required. Both fee fields are bound into the release digest; defaulting the recipient to native would sign a digest the buyer's cosign rejects. Pass --fee-recipient-at-lock <the lock's fee_recipient_at_lock>.`
|
|
8064
|
+
);
|
|
8065
|
+
}
|
|
8066
|
+
}
|
|
8067
|
+
const signOpts = {
|
|
8068
|
+
server: opts.server,
|
|
8069
|
+
fromDid: opts.fromDid,
|
|
8070
|
+
delegationId,
|
|
8071
|
+
payerSettlementPubkey,
|
|
8072
|
+
payeeSettlementPubkey: sender.settlementPublicKeyB58,
|
|
8073
|
+
mintPubkey: opts.mintPubkey ?? NATIVE_SOL_MINT2,
|
|
8074
|
+
lockAmount,
|
|
8075
|
+
conditionHash,
|
|
8076
|
+
receiptEventHash,
|
|
8077
|
+
deliverableHash,
|
|
8078
|
+
expiresAt: String(expiresAt8),
|
|
8079
|
+
clusterTag,
|
|
8080
|
+
...opts.feeBpsAtLock !== void 0 ? { feeBpsAtLock: opts.feeBpsAtLock } : {},
|
|
8081
|
+
...opts.feeRecipientAtLock !== void 0 ? { feeRecipientAtLock: opts.feeRecipientAtLock } : {},
|
|
8082
|
+
...partialPayeeAmount !== void 0 ? { partialPayeeAmount } : {}
|
|
8083
|
+
};
|
|
8084
|
+
progress(
|
|
8085
|
+
opts.json,
|
|
8086
|
+
import_chalk28.default.dim(`Signing ${partialPayeeAmount !== void 0 ? "PARTIAL" : "full"} release: lock_amount=${lockAmount}, expires_at=${expiresAt8}, cluster_tag=${clusterTag}`)
|
|
8087
|
+
);
|
|
8088
|
+
const signed = await signSettlementHandler(signOpts);
|
|
8089
|
+
const content = {
|
|
8090
|
+
delegation_id: delegationId,
|
|
8091
|
+
receipt_event_hash: receiptEventHash,
|
|
8092
|
+
purpose: signed.purpose,
|
|
8093
|
+
payee_settlement_pubkey: signed.settlement_pubkey,
|
|
8094
|
+
sig: signed.sig,
|
|
8095
|
+
expires_at: expiresAt8,
|
|
8096
|
+
...partialPayeeAmount !== void 0 ? { payee_amount: partialPayeeAmount } : {}
|
|
8097
|
+
};
|
|
8098
|
+
const result = await sendSettlementSignatureEnvelope({ api, sender, recipientDid: buyerDid, content, ttlSeconds: 3600, verbose: false, server: opts.server });
|
|
8099
|
+
if (opts.json) {
|
|
8100
|
+
jsonOut({
|
|
8101
|
+
ok: true,
|
|
8102
|
+
delegationId,
|
|
8103
|
+
relationshipId: relId,
|
|
8104
|
+
buyerDid,
|
|
8105
|
+
purpose: signed.purpose,
|
|
8106
|
+
settlementPubkey: signed.settlement_pubkey,
|
|
8107
|
+
...partialPayeeAmount !== void 0 ? { payeeAmount: partialPayeeAmount } : {},
|
|
8108
|
+
lockAmount,
|
|
8109
|
+
expiresAt: expiresAt8,
|
|
8110
|
+
receiptEventHash,
|
|
8111
|
+
eventId: result.eventId,
|
|
8112
|
+
serverEventHash: result.serverEventHash,
|
|
8113
|
+
relationshipEventIndex: result.relationshipEventIndex,
|
|
8114
|
+
serverTimestamp: result.serverTimestamp
|
|
8115
|
+
});
|
|
8116
|
+
return;
|
|
8117
|
+
}
|
|
8118
|
+
console.log(import_chalk28.default.green("\nSettlement signature signed + delivered."));
|
|
8119
|
+
console.log(`${import_chalk28.default.bold("Delegation")}: ${import_chalk28.default.cyan(delegationId)}`);
|
|
8120
|
+
console.log(`${import_chalk28.default.bold("Buyer")}: ${import_chalk28.default.cyan(buyerDid)}`);
|
|
8121
|
+
console.log(`${import_chalk28.default.bold("Purpose")}: ${import_chalk28.default.cyan(signed.purpose)}`);
|
|
8122
|
+
if (partialPayeeAmount !== void 0) {
|
|
8123
|
+
console.log(`${import_chalk28.default.bold("Payee amount")}: ${import_chalk28.default.cyan(partialPayeeAmount)} (partial release)`);
|
|
8124
|
+
}
|
|
8125
|
+
console.log(`${import_chalk28.default.bold("Expires at")}: ${import_chalk28.default.cyan(String(expiresAt8))}`);
|
|
8126
|
+
console.log(`${import_chalk28.default.bold("Delivered event")}: ${import_chalk28.default.cyan(result.serverEventHash)}`);
|
|
8127
|
+
console.log(import_chalk28.default.dim("\nThe buyer cosigns with: heyarp receipt cosign <rel-id> <del-id> --auto-hashes --auto-resolve-payee-sig --payer-sig-from-file <path>"));
|
|
8128
|
+
}
|
|
8129
|
+
|
|
6735
8130
|
// src/commands/watch.ts
|
|
6736
|
-
var
|
|
8131
|
+
var import_chalk29 = __toESM(require("chalk"));
|
|
6737
8132
|
init_api();
|
|
6738
8133
|
function registerWatchCommand(root) {
|
|
6739
8134
|
root.command("watch").description("Live tail filtered to a single relationship (SSE). Server-side $match; only envelopes belonging to <rel-id> are streamed.").argument("<relationship-id>", "Relationship UUID to watch").option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Signer DID \u2014 required only if multiple agents are registered against this server").option("--verbose", "After each envelope, print the full JSON with a per-row label including eventId + serverEventHash", false).option("--json", "Machine-readable: one NDJSON object per line. Pipe-safe into `jq -c`.", false).option("--full-ids", "Print DIDs + serverEventHash in full (no truncation).", false).action(async (relationshipId, opts) => {
|
|
@@ -6744,9 +8139,9 @@ async function runWatch(relationshipId, opts) {
|
|
|
6744
8139
|
const local = resolveSenderAgent("watch", opts.server, opts.fromDid);
|
|
6745
8140
|
const api = new ArpApiClient(opts.server);
|
|
6746
8141
|
if (!opts.json) {
|
|
6747
|
-
console.log(
|
|
6748
|
-
console.log(
|
|
6749
|
-
console.log(
|
|
8142
|
+
console.log(import_chalk29.default.dim(`Server: ${api.serverUrl}`));
|
|
8143
|
+
console.log(import_chalk29.default.dim(`Signer: ${local.did}`));
|
|
8144
|
+
console.log(import_chalk29.default.dim(`Watching: ${relationshipId}`));
|
|
6750
8145
|
}
|
|
6751
8146
|
const controller = new AbortController();
|
|
6752
8147
|
let userAborted = false;
|
|
@@ -6765,7 +8160,7 @@ async function runWatch(relationshipId, opts) {
|
|
|
6765
8160
|
}
|
|
6766
8161
|
if (event.type === "heartbeat") continue;
|
|
6767
8162
|
if (event.type === "connected") {
|
|
6768
|
-
console.log(
|
|
8163
|
+
console.log(import_chalk29.default.green(`\u25CF stream open \u2014 watching ${relationshipId}`));
|
|
6769
8164
|
continue;
|
|
6770
8165
|
}
|
|
6771
8166
|
if (event.type === "envelope") {
|
|
@@ -6779,7 +8174,7 @@ async function runWatch(relationshipId, opts) {
|
|
|
6779
8174
|
}
|
|
6780
8175
|
continue;
|
|
6781
8176
|
}
|
|
6782
|
-
console.log(
|
|
8177
|
+
console.log(import_chalk29.default.dim(`(unknown event: ${event.type})`));
|
|
6783
8178
|
}
|
|
6784
8179
|
if (!userAborted) {
|
|
6785
8180
|
throw new Error(`watch ${relationshipId}: stream ended unexpectedly (server may have restarted, or the change stream errored). Re-run to reconnect.`);
|
|
@@ -6787,7 +8182,7 @@ async function runWatch(relationshipId, opts) {
|
|
|
6787
8182
|
} catch (err) {
|
|
6788
8183
|
const name = err.name;
|
|
6789
8184
|
if (name === "AbortError" || userAborted) {
|
|
6790
|
-
if (!opts.json) console.log(
|
|
8185
|
+
if (!opts.json) console.log(import_chalk29.default.dim("\nstream closed."));
|
|
6791
8186
|
return;
|
|
6792
8187
|
}
|
|
6793
8188
|
throw err;
|
|
@@ -6801,21 +8196,21 @@ function formatWatchLine(ev, selfDid, opts = {}) {
|
|
|
6801
8196
|
const type = ev.type.padEnd(20);
|
|
6802
8197
|
const direction = directionLabel2(ev, selfDid, opts);
|
|
6803
8198
|
const hash = opts.fullIds ? ev.serverEventHash : hashHead4(ev.serverEventHash);
|
|
6804
|
-
return `${
|
|
8199
|
+
return `${import_chalk29.default.dim(`[${ts}]`)} ${type} ${direction} ${import_chalk29.default.cyan(hash)}`;
|
|
6805
8200
|
}
|
|
6806
8201
|
function directionLabel2(ev, selfDid, opts = {}) {
|
|
6807
8202
|
const senderHead = opts.fullIds ? ev.senderDid : didHead5(ev.senderDid);
|
|
6808
8203
|
const recipientHead = opts.fullIds ? ev.recipientDid : didHead5(ev.recipientDid);
|
|
6809
|
-
if (ev.senderDid === selfDid) return `${
|
|
6810
|
-
if (ev.recipientDid === selfDid) return `${
|
|
6811
|
-
return `${
|
|
8204
|
+
if (ev.senderDid === selfDid) return `${import_chalk29.default.bold("me")} \u2192 ${import_chalk29.default.dim(recipientHead)}`;
|
|
8205
|
+
if (ev.recipientDid === selfDid) return `${import_chalk29.default.dim(senderHead)} \u2192 ${import_chalk29.default.bold("me")}`;
|
|
8206
|
+
return `${import_chalk29.default.dim(senderHead)} \u2192 ${import_chalk29.default.dim(recipientHead)}`;
|
|
6812
8207
|
}
|
|
6813
8208
|
function didHead5(did) {
|
|
6814
8209
|
if (did.length <= 20) return did;
|
|
6815
8210
|
return `${did.slice(0, 20)}...`;
|
|
6816
8211
|
}
|
|
6817
8212
|
function hashHead4(hash) {
|
|
6818
|
-
if (!hash) return
|
|
8213
|
+
if (!hash) return import_chalk29.default.dim("(none)");
|
|
6819
8214
|
if (hash.length <= 14) return hash;
|
|
6820
8215
|
return `${hash.slice(0, 14)}...`;
|
|
6821
8216
|
}
|
|
@@ -6826,8 +8221,283 @@ function formatClock(iso) {
|
|
|
6826
8221
|
return `${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())}`;
|
|
6827
8222
|
}
|
|
6828
8223
|
|
|
8224
|
+
// src/commands/webhook.ts
|
|
8225
|
+
var import_promises = require("timers/promises");
|
|
8226
|
+
var import_chalk30 = __toESM(require("chalk"));
|
|
8227
|
+
init_api();
|
|
8228
|
+
function registerWebhookCommand(root) {
|
|
8229
|
+
const webhook = root.command("webhook").description("Manage outbound webhook delivery \u2014 URL + HMAC secret lifecycle for the calling agent.");
|
|
8230
|
+
registerUrlSubcommands(webhook);
|
|
8231
|
+
registerSecretSubcommands(webhook);
|
|
8232
|
+
}
|
|
8233
|
+
function registerUrlSubcommands(parent) {
|
|
8234
|
+
const url = parent.command("url").description("Outbound webhook URL \u2014 the address the server POSTs deliveries to.");
|
|
8235
|
+
url.command("set").description(
|
|
8236
|
+
"Set / replace the outbound webhook URL. Server validates the URL (scheme + non-private IP) and fires an unsigned probe POST (X-ARP-Probe: 1, 5s timeout) before persisting \u2014 non-2xx response \u2192 400 WEBHOOK_PROBE_FAILED, no persistence."
|
|
8237
|
+
).argument("<url>", "HTTPS URL the server should POST webhook deliveries to").option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Explicit sender DID \u2014 required when more than one local agent exists").option("--json", "JSON output (jq-pipeable)", false).action(async (webhookUrl, opts, cmd) => {
|
|
8238
|
+
try {
|
|
8239
|
+
const local = resolveSenderAgent("webhook url set", opts.server, opts.fromDid);
|
|
8240
|
+
const api = new ArpApiClient(opts.server);
|
|
8241
|
+
const signer = makeSigner(local);
|
|
8242
|
+
const out = await api.setMyWebhookConfig({ webhookUrl }, signer);
|
|
8243
|
+
if (opts.json) {
|
|
8244
|
+
console.log(formatJson(out));
|
|
8245
|
+
return;
|
|
8246
|
+
}
|
|
8247
|
+
console.log(import_chalk30.default.dim(`Server: ${api.serverUrl}`));
|
|
8248
|
+
console.log(import_chalk30.default.dim(`Signer: ${local.did}`));
|
|
8249
|
+
console.log(`${import_chalk30.default.green("Webhook URL set:")} ${import_chalk30.default.cyan(out.webhookUrl ?? "(cleared)")}`);
|
|
8250
|
+
if (!out.webhookSecretRegistered) {
|
|
8251
|
+
console.log(import_chalk30.default.yellow("\nNo HMAC secret registered yet. Run: heyarp webhook secret init"));
|
|
8252
|
+
}
|
|
8253
|
+
} catch (err) {
|
|
8254
|
+
emitActionError(err, cmd);
|
|
8255
|
+
process.exitCode = 1;
|
|
8256
|
+
}
|
|
8257
|
+
});
|
|
8258
|
+
url.command("show").description("Show the calling agent's outbound webhook URL + whether an HMAC secret is registered. Read-only.").option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Explicit sender DID \u2014 required when more than one local agent exists").option("--json", "JSON output (jq-pipeable)", false).action(async (opts, cmd) => {
|
|
8259
|
+
try {
|
|
8260
|
+
const local = resolveSenderAgent("webhook url show", opts.server, opts.fromDid);
|
|
8261
|
+
const api = new ArpApiClient(opts.server);
|
|
8262
|
+
const signer = makeSigner(local);
|
|
8263
|
+
const out = await api.getMyWebhookConfig(signer);
|
|
8264
|
+
if (opts.json) {
|
|
8265
|
+
console.log(formatJson(out));
|
|
8266
|
+
return;
|
|
8267
|
+
}
|
|
8268
|
+
console.log(import_chalk30.default.dim(`Server: ${api.serverUrl}`));
|
|
8269
|
+
console.log(import_chalk30.default.dim(`Signer: ${local.did}`));
|
|
8270
|
+
console.log(`${import_chalk30.default.bold("Webhook URL:")} ${out.webhookUrl ? import_chalk30.default.cyan(out.webhookUrl) : import_chalk30.default.dim("(unset \u2014 poll/SSE only)")}`);
|
|
8271
|
+
console.log(`${import_chalk30.default.bold("Secret registered:")} ${out.webhookSecretRegistered ? import_chalk30.default.green("yes") : import_chalk30.default.yellow("no")}`);
|
|
8272
|
+
} catch (err) {
|
|
8273
|
+
emitActionError(err, cmd);
|
|
8274
|
+
process.exitCode = 1;
|
|
8275
|
+
}
|
|
8276
|
+
});
|
|
8277
|
+
url.command("clear").description(
|
|
8278
|
+
"Unset the outbound webhook URL. Disables webhook delivery on the next outbox enqueue; in-flight deliveries finish naturally. Agent reverts to poll / SSE without losing data."
|
|
8279
|
+
).option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Explicit sender DID \u2014 required when more than one local agent exists").option("--yes", "Skip the destructive-action confirmation prompt", false).option("--json", "JSON output (jq-pipeable)", false).action(async (opts, cmd) => {
|
|
8280
|
+
try {
|
|
8281
|
+
const local = resolveSenderAgent("webhook url clear", opts.server, opts.fromDid);
|
|
8282
|
+
const api = new ArpApiClient(opts.server);
|
|
8283
|
+
const signer = makeSigner(local);
|
|
8284
|
+
if (!opts.yes) {
|
|
8285
|
+
const answer = await promptYesNo("Clear the webhook URL? Outbound delivery will stop on next outbox enqueue.");
|
|
8286
|
+
if (!answer) {
|
|
8287
|
+
console.log(import_chalk30.default.dim("Aborted."));
|
|
8288
|
+
return;
|
|
8289
|
+
}
|
|
8290
|
+
}
|
|
8291
|
+
const out = await api.setMyWebhookConfig({ webhookUrl: null }, signer);
|
|
8292
|
+
if (opts.json) {
|
|
8293
|
+
console.log(formatJson(out));
|
|
8294
|
+
return;
|
|
8295
|
+
}
|
|
8296
|
+
console.log(`${import_chalk30.default.green("Webhook URL cleared.")} Agent reverts to poll / SSE for new events.`);
|
|
8297
|
+
} catch (err) {
|
|
8298
|
+
emitActionError(err, cmd);
|
|
8299
|
+
process.exitCode = 1;
|
|
8300
|
+
}
|
|
8301
|
+
});
|
|
8302
|
+
}
|
|
8303
|
+
var REDACTED_SECRET_MARKER = "<REDACTED \u2014 read ~/.arp/agents.json>";
|
|
8304
|
+
function parseNonNegativeInt(raw, fieldName) {
|
|
8305
|
+
if (!/^\d+$/.test(raw)) {
|
|
8306
|
+
throw new Error(`${fieldName}: must be a non-negative integer (got '${raw}')`);
|
|
8307
|
+
}
|
|
8308
|
+
const n = Number.parseInt(raw, 10);
|
|
8309
|
+
if (!Number.isSafeInteger(n) || n > 86400) {
|
|
8310
|
+
throw new Error(`${fieldName}: out of range (got '${raw}', max 86400)`);
|
|
8311
|
+
}
|
|
8312
|
+
return n;
|
|
8313
|
+
}
|
|
8314
|
+
function warnIfChmodFailed(result) {
|
|
8315
|
+
if (!result.chmodOk) {
|
|
8316
|
+
console.error(import_chalk30.default.red("WARNING: failed to chmod 0600 on ~/.arp/agents.json \u2014 the file may be world-readable. Check filesystem permissions and re-run."));
|
|
8317
|
+
}
|
|
8318
|
+
}
|
|
8319
|
+
function registerSecretSubcommands(parent) {
|
|
8320
|
+
const secret = parent.command("secret").description("HMAC secret used to sign outbound webhook deliveries (X-ARP-Signature header).");
|
|
8321
|
+
secret.command("init").description(
|
|
8322
|
+
"Generate the calling agent's first HMAC secret. Server returns the plaintext value ONCE; the CLI persists it to ~/.arp/agents.json under `webhookSecretB64` (chmod 600). 409 WEBHOOK_SECRET_ALREADY_REGISTERED if a secret already exists \u2014 use `webhook secret rotate` to change."
|
|
8323
|
+
).option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Explicit sender DID \u2014 required when more than one local agent exists").option(
|
|
8324
|
+
"--json",
|
|
8325
|
+
"JSON output (jq-pipeable). Plaintext secret is REDACTED from --json output to keep it out of shell history / CI logs \u2014 the persisted copy in ~/.arp/agents.json is the only retained source.",
|
|
8326
|
+
false
|
|
8327
|
+
).action(async (opts, cmd) => {
|
|
8328
|
+
try {
|
|
8329
|
+
const local = resolveSenderAgent("webhook secret init", opts.server, opts.fromDid);
|
|
8330
|
+
const api = new ArpApiClient(opts.server);
|
|
8331
|
+
const signer = makeSigner(local);
|
|
8332
|
+
const out = await api.initMyWebhookSecret(signer);
|
|
8333
|
+
const writeResult = updateAgentLocal(opts.server, local.did, { webhookSecretB64: out.webhookSecretB64 });
|
|
8334
|
+
warnIfChmodFailed(writeResult);
|
|
8335
|
+
if (opts.json) {
|
|
8336
|
+
console.log(formatJson({ ...out, webhookSecretB64: REDACTED_SECRET_MARKER, persisted: true }));
|
|
8337
|
+
return;
|
|
8338
|
+
}
|
|
8339
|
+
console.log(import_chalk30.default.dim(`Server: ${api.serverUrl}`));
|
|
8340
|
+
console.log(import_chalk30.default.dim(`Signer: ${local.did}`));
|
|
8341
|
+
console.log(import_chalk30.default.green("Webhook HMAC secret registered + persisted locally."));
|
|
8342
|
+
console.log(import_chalk30.default.dim(" Stored in ~/.arp/agents.json under `webhookSecretB64` (chmod 600)."));
|
|
8343
|
+
console.log(import_chalk30.default.dim(" Your handler reads this on startup to verify X-ARP-Signature."));
|
|
8344
|
+
} catch (err) {
|
|
8345
|
+
emitActionError(err, cmd);
|
|
8346
|
+
process.exitCode = 1;
|
|
8347
|
+
}
|
|
8348
|
+
});
|
|
8349
|
+
secret.command("status").description(
|
|
8350
|
+
"Show the calling agent's server-side webhook-secret lifecycle: which slots are populated (`current`, `pending`, `previous`) and the post-commit grace expiry. Read-only \u2014 does NOT return any secret material. Use during rotation to verify the recipient handler picks up the new secret before commit."
|
|
8351
|
+
).option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Explicit sender DID \u2014 required when more than one local agent exists").option("--json", "JSON output (jq-pipeable)", false).action(async (opts, cmd) => {
|
|
8352
|
+
try {
|
|
8353
|
+
const local = resolveSenderAgent("webhook secret status", opts.server, opts.fromDid);
|
|
8354
|
+
const api = new ArpApiClient(opts.server);
|
|
8355
|
+
const signer = makeSigner(local);
|
|
8356
|
+
const out = await api.getMyWebhookSecretStatus(signer);
|
|
8357
|
+
if (opts.json) {
|
|
8358
|
+
console.log(formatJson(out));
|
|
8359
|
+
return;
|
|
8360
|
+
}
|
|
8361
|
+
console.log(import_chalk30.default.dim(`Server: ${api.serverUrl}`));
|
|
8362
|
+
console.log(import_chalk30.default.dim(`Signer: ${local.did}`));
|
|
8363
|
+
console.log(`${import_chalk30.default.bold("Current:")} ${out.currentRegistered ? import_chalk30.default.green("registered") : import_chalk30.default.yellow("NONE \u2014 run `heyarp webhook secret init`")}`);
|
|
8364
|
+
console.log(`${import_chalk30.default.bold("Pending:")} ${out.pendingStaged ? import_chalk30.default.cyan("staged \u2014 awaiting --commit") : import_chalk30.default.dim("\u2014")}`);
|
|
8365
|
+
if (out.previousActive) {
|
|
8366
|
+
console.log(
|
|
8367
|
+
`${import_chalk30.default.bold("Previous:")} ${import_chalk30.default.cyan("grace-active")} (handler should keep verifying against it until ${import_chalk30.default.cyan(out.previousSecretExpiresAt ?? "?")})`
|
|
8368
|
+
);
|
|
8369
|
+
} else {
|
|
8370
|
+
console.log(`${import_chalk30.default.bold("Previous:")} ${import_chalk30.default.dim("\u2014")}`);
|
|
8371
|
+
}
|
|
8372
|
+
} catch (err) {
|
|
8373
|
+
emitActionError(err, cmd);
|
|
8374
|
+
process.exitCode = 1;
|
|
8375
|
+
}
|
|
8376
|
+
});
|
|
8377
|
+
secret.command("rotate").description(
|
|
8378
|
+
"Two-phase HMAC secret rotation with a recipient-grace window. Phase 1 (--stage) primes the recipient handler with the new secret while the server keeps signing with the old one; phase 2 (--commit) flips the server-side signing key and starts a 1h grace window where the old secret is still valid. Run with no sub-flag to do both phases with --wait-before-commit between them."
|
|
8379
|
+
).option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Explicit sender DID \u2014 required when more than one local agent exists").option(
|
|
8380
|
+
"--stage",
|
|
8381
|
+
"Phase 1 only \u2014 stage a new secret without flipping the server signing key. CLI writes the new secret to `webhookSecretB64Next` for the handler to pre-load.",
|
|
8382
|
+
false
|
|
8383
|
+
).option("--commit", "Phase 2 only \u2014 flip the server signing key. Requires a prior --stage (rotationId comes from local state).", false).option(
|
|
8384
|
+
"--wait-before-commit <s>",
|
|
8385
|
+
"Auto-mode only: seconds to wait between phase 1 (stage) and phase 2 (commit). Default 30 \u2014 enough for a pm2 / systemd handler restart to pick up `webhookSecretB64Next`. Pass 0 to commit immediately (UNSAFE \u2014 guarantees a brief HMAC-verification gap unless the handler is already running in dual-key mode).",
|
|
8386
|
+
"30"
|
|
8387
|
+
).option("--json", "JSON output for the commit-phase response", false).action(async (opts, cmd) => {
|
|
8388
|
+
try {
|
|
8389
|
+
if (opts.stage && opts.commit) {
|
|
8390
|
+
throw new Error("webhook secret rotate: --stage and --commit are mutually exclusive");
|
|
8391
|
+
}
|
|
8392
|
+
const local = resolveSenderAgent("webhook secret rotate", opts.server, opts.fromDid);
|
|
8393
|
+
const api = new ArpApiClient(opts.server);
|
|
8394
|
+
const signer = makeSigner(local);
|
|
8395
|
+
if (opts.stage) {
|
|
8396
|
+
await doStage(api, signer, local.did, opts.server, !!opts.json);
|
|
8397
|
+
return;
|
|
8398
|
+
}
|
|
8399
|
+
if (opts.commit) {
|
|
8400
|
+
await doCommitFromLocalState(api, signer, local.did, opts.server, !!opts.json);
|
|
8401
|
+
return;
|
|
8402
|
+
}
|
|
8403
|
+
const waitSeconds = parseNonNegativeInt(opts.waitBeforeCommit ?? "30", "webhook secret rotate --wait-before-commit");
|
|
8404
|
+
const staged = await doStage(api, signer, local.did, opts.server, !!opts.json);
|
|
8405
|
+
if (waitSeconds > 0) {
|
|
8406
|
+
console.log(import_chalk30.default.dim(`Waiting ${waitSeconds}s for the recipient handler to pick up the new secret before commit\u2026`));
|
|
8407
|
+
await (0, import_promises.setTimeout)(waitSeconds * 1e3);
|
|
8408
|
+
} else {
|
|
8409
|
+
console.log(
|
|
8410
|
+
import_chalk30.default.yellow(
|
|
8411
|
+
"--wait-before-commit=0: committing immediately. Recipient must already be running in dual-key mode (verifying against [current, next]) or HMAC verification will gap until the handler restarts."
|
|
8412
|
+
)
|
|
8413
|
+
);
|
|
8414
|
+
}
|
|
8415
|
+
await doCommitWithStaged(api, signer, local.did, opts.server, staged, !!opts.json);
|
|
8416
|
+
} catch (err) {
|
|
8417
|
+
emitActionError(err, cmd);
|
|
8418
|
+
process.exitCode = 1;
|
|
8419
|
+
}
|
|
8420
|
+
});
|
|
8421
|
+
}
|
|
8422
|
+
async function doStage(api, signer, did, server, asJson) {
|
|
8423
|
+
const out = await api.rotateStageMyWebhookSecret(signer);
|
|
8424
|
+
if (out.slot !== "pending" || !out.rotationId) {
|
|
8425
|
+
throw new Error(
|
|
8426
|
+
`webhook secret rotate-stage: unexpected server response \u2014 expected slot='pending' + rotationId, got slot='${out.slot}' rotationId='${out.rotationId ?? ""}'`
|
|
8427
|
+
);
|
|
8428
|
+
}
|
|
8429
|
+
const writeResult = updateAgentLocal(server, did, {
|
|
8430
|
+
webhookSecretB64Next: out.webhookSecretB64,
|
|
8431
|
+
webhookRotationId: out.rotationId
|
|
8432
|
+
});
|
|
8433
|
+
warnIfChmodFailed(writeResult);
|
|
8434
|
+
if (asJson) {
|
|
8435
|
+
console.log(formatJson({ ...out, webhookSecretB64: REDACTED_SECRET_MARKER, persisted: true }));
|
|
8436
|
+
} else {
|
|
8437
|
+
console.log(import_chalk30.default.green("Phase 1 (stage) complete:"));
|
|
8438
|
+
console.log(import_chalk30.default.dim(" New secret staged server-side; server still signs with the CURRENT secret."));
|
|
8439
|
+
console.log(import_chalk30.default.dim(" Stored locally under `webhookSecretB64Next` \u2014 your handler should now verify against BOTH current + next."));
|
|
8440
|
+
console.log(import_chalk30.default.dim(` rotationId: ${out.rotationId} (threaded into the matching commit call).`));
|
|
8441
|
+
}
|
|
8442
|
+
return { rotationId: out.rotationId, pendingSecretB64: out.webhookSecretB64 };
|
|
8443
|
+
}
|
|
8444
|
+
async function doCommitWithStaged(api, signer, did, server, staged, asJson) {
|
|
8445
|
+
const fresh = resolveSenderAgent("webhook secret rotate --commit", server, did);
|
|
8446
|
+
await runCommitAndSwap(api, signer, fresh, server, did, staged.rotationId, staged.pendingSecretB64, asJson);
|
|
8447
|
+
}
|
|
8448
|
+
async function doCommitFromLocalState(api, signer, did, server, asJson) {
|
|
8449
|
+
const fresh = resolveSenderAgent("webhook secret rotate --commit", server, did);
|
|
8450
|
+
if (!fresh.webhookSecretB64Next || !fresh.webhookRotationId) {
|
|
8451
|
+
throw new Error(
|
|
8452
|
+
"webhook secret rotate --commit: no staged secret found in local state. Run `webhook secret rotate --stage` first, or use the no-flag form to do both phases in one invocation."
|
|
8453
|
+
);
|
|
8454
|
+
}
|
|
8455
|
+
await runCommitAndSwap(api, signer, fresh, server, did, fresh.webhookRotationId, fresh.webhookSecretB64Next, asJson);
|
|
8456
|
+
}
|
|
8457
|
+
async function runCommitAndSwap(api, signer, fresh, server, did, rotationId, pendingSecretB64, asJson) {
|
|
8458
|
+
let out;
|
|
8459
|
+
try {
|
|
8460
|
+
out = await api.rotateCommitMyWebhookSecret({ rotationId }, signer);
|
|
8461
|
+
} catch (err) {
|
|
8462
|
+
if (err instanceof ApiError && err.payload.code === "WEBHOOK_SECRET_ROTATION_ID_MISMATCH") {
|
|
8463
|
+
throw new Error(
|
|
8464
|
+
`webhook secret rotate --commit: rotationId mismatch (a concurrent --stage ran). Run \`webhook secret rotate --stage\` again, then commit with the fresh token. Local state PRESERVED \u2014 your current secret still works.`
|
|
8465
|
+
);
|
|
8466
|
+
}
|
|
8467
|
+
throw err;
|
|
8468
|
+
}
|
|
8469
|
+
const writeResult = updateAgentLocal(server, did, {
|
|
8470
|
+
webhookSecretB64Previous: fresh.webhookSecretB64,
|
|
8471
|
+
webhookSecretB64: pendingSecretB64,
|
|
8472
|
+
webhookSecretB64Next: void 0,
|
|
8473
|
+
webhookRotationId: void 0,
|
|
8474
|
+
webhookSecretPreviousExpiresAt: out.previousSecretExpiresAt
|
|
8475
|
+
});
|
|
8476
|
+
warnIfChmodFailed(writeResult);
|
|
8477
|
+
if (asJson) {
|
|
8478
|
+
console.log(formatJson(out));
|
|
8479
|
+
return;
|
|
8480
|
+
}
|
|
8481
|
+
console.log(import_chalk30.default.green("Phase 2 (commit) complete:"));
|
|
8482
|
+
console.log(import_chalk30.default.dim(" Server now signs with the NEW secret. Old secret kept in `webhookSecretB64Previous` for the grace window."));
|
|
8483
|
+
console.log(` ${import_chalk30.default.bold("Grace expires:")} ${import_chalk30.default.cyan(out.previousSecretExpiresAt)} \u2014 after this, your handler can drop the previous secret on next restart.`);
|
|
8484
|
+
}
|
|
8485
|
+
async function promptYesNo(question) {
|
|
8486
|
+
process.stdout.write(`${question} (y/N): `);
|
|
8487
|
+
return new Promise((resolve2) => {
|
|
8488
|
+
const onData = (chunk) => {
|
|
8489
|
+
const answer = chunk.toString("utf8").trim().toLowerCase();
|
|
8490
|
+
process.stdin.pause();
|
|
8491
|
+
process.stdin.off("data", onData);
|
|
8492
|
+
resolve2(answer === "y" || answer === "yes");
|
|
8493
|
+
};
|
|
8494
|
+
process.stdin.resume();
|
|
8495
|
+
process.stdin.on("data", onData);
|
|
8496
|
+
});
|
|
8497
|
+
}
|
|
8498
|
+
|
|
6829
8499
|
// src/commands/whoami.ts
|
|
6830
|
-
var
|
|
8500
|
+
var import_chalk31 = __toESM(require("chalk"));
|
|
6831
8501
|
init_api();
|
|
6832
8502
|
function registerWhoamiCommand(root) {
|
|
6833
8503
|
root.command("whoami").description(
|
|
@@ -6861,10 +8531,10 @@ function registerWhoamiCommand(root) {
|
|
|
6861
8531
|
if (opts.json) {
|
|
6862
8532
|
console.log(formatJson(localJson));
|
|
6863
8533
|
} else {
|
|
6864
|
-
console.log(
|
|
6865
|
-
console.log(` DID: ${
|
|
6866
|
-
console.log(` Settlement pubkey: ${
|
|
6867
|
-
console.log(` Identity pubkey: ${
|
|
8534
|
+
console.log(import_chalk31.default.bold("Local agent:"));
|
|
8535
|
+
console.log(` DID: ${import_chalk31.default.cyan(local.did)}`);
|
|
8536
|
+
console.log(` Settlement pubkey: ${import_chalk31.default.cyan(local.settlementPublicKeyB58)}`);
|
|
8537
|
+
console.log(` Identity pubkey: ${import_chalk31.default.cyan(local.identityPublicKeyB58)}`);
|
|
6868
8538
|
console.log(` Key mode: ${local.keyMode}`);
|
|
6869
8539
|
if (local.name) console.log(` Name: ${local.name}`);
|
|
6870
8540
|
}
|
|
@@ -6876,24 +8546,24 @@ function registerWhoamiCommand(root) {
|
|
|
6876
8546
|
if (opts.json) {
|
|
6877
8547
|
console.log(formatJson({ local: localJson, server: agent }));
|
|
6878
8548
|
} else {
|
|
6879
|
-
console.log(
|
|
6880
|
-
console.log(
|
|
6881
|
-
console.log(` DID: ${
|
|
6882
|
-
console.log(` Settlement pubkey: ${
|
|
6883
|
-
console.log(` Identity pubkey: ${
|
|
6884
|
-
console.log(
|
|
8549
|
+
console.log(import_chalk31.default.dim(`Server: ${api.serverUrl}`));
|
|
8550
|
+
console.log(import_chalk31.default.bold("\nLocal agent:"));
|
|
8551
|
+
console.log(` DID: ${import_chalk31.default.cyan(local.did)}`);
|
|
8552
|
+
console.log(` Settlement pubkey: ${import_chalk31.default.cyan(local.settlementPublicKeyB58)}`);
|
|
8553
|
+
console.log(` Identity pubkey: ${import_chalk31.default.cyan(local.identityPublicKeyB58)}`);
|
|
8554
|
+
console.log(import_chalk31.default.bold("\nServer profile:"));
|
|
6885
8555
|
console.log(formatJson(agent));
|
|
6886
8556
|
}
|
|
6887
8557
|
} catch (err) {
|
|
6888
|
-
|
|
8558
|
+
emitActionError(err, cmd);
|
|
6889
8559
|
process.exitCode = 1;
|
|
6890
8560
|
}
|
|
6891
8561
|
});
|
|
6892
8562
|
}
|
|
6893
8563
|
|
|
6894
8564
|
// src/commands/work.ts
|
|
6895
|
-
var
|
|
6896
|
-
var
|
|
8565
|
+
var import_sdk17 = require("@heyanon-arp/sdk");
|
|
8566
|
+
var import_chalk32 = __toESM(require("chalk"));
|
|
6897
8567
|
init_api();
|
|
6898
8568
|
function registerWorkCommands(root) {
|
|
6899
8569
|
const cmd = root.command("work").description("Work envelopes inside an ACCEPTED delegation: request / respond");
|
|
@@ -6935,17 +8605,17 @@ async function runRequest(recipientDid, delegationId, opts) {
|
|
|
6935
8605
|
params
|
|
6936
8606
|
};
|
|
6937
8607
|
const body = { type: "work_request", content };
|
|
6938
|
-
console.log(
|
|
6939
|
-
console.log(
|
|
6940
|
-
console.log(
|
|
6941
|
-
console.log(
|
|
6942
|
-
console.log(
|
|
8608
|
+
console.log(import_chalk32.default.dim(`Server: ${api.serverUrl}`));
|
|
8609
|
+
console.log(import_chalk32.default.dim(`Sender: ${sender.did}`));
|
|
8610
|
+
console.log(import_chalk32.default.dim(`Recipient: ${recipientDid}`));
|
|
8611
|
+
console.log(import_chalk32.default.dim(`Delegation: ${delegationId}`));
|
|
8612
|
+
console.log(import_chalk32.default.dim(`Request id: ${requestId}`));
|
|
6943
8613
|
const result = await sendWorkEnvelope({ api, sender, recipientDid, body, ttlSeconds, verbose: opts.verbose, server: opts.server });
|
|
6944
8614
|
printIngestResult4(result);
|
|
6945
|
-
console.log(
|
|
8615
|
+
console.log(import_chalk32.default.dim(`
|
|
6946
8616
|
The payee can reply with:`));
|
|
6947
|
-
console.log(
|
|
6948
|
-
console.log(
|
|
8617
|
+
console.log(import_chalk32.default.dim(` heyarp work respond ${result.relationshipId} ${delegationId} ${requestId} --output '<json>'`));
|
|
8618
|
+
console.log(import_chalk32.default.dim(` heyarp work respond ${result.relationshipId} ${delegationId} ${requestId} --error CODE:message`));
|
|
6949
8619
|
}
|
|
6950
8620
|
function registerRespond(parent) {
|
|
6951
8621
|
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(
|
|
@@ -6970,13 +8640,13 @@ async function runRespond(relationshipId, delegationId, requestId, opts) {
|
|
|
6970
8640
|
...responsePayload
|
|
6971
8641
|
};
|
|
6972
8642
|
const body = { type: "work_response", content };
|
|
6973
|
-
console.log(
|
|
6974
|
-
console.log(
|
|
6975
|
-
console.log(
|
|
6976
|
-
console.log(
|
|
6977
|
-
console.log(
|
|
6978
|
-
console.log(
|
|
6979
|
-
console.log(
|
|
8643
|
+
console.log(import_chalk32.default.dim(`Server: ${api.serverUrl}`));
|
|
8644
|
+
console.log(import_chalk32.default.dim(`Sender: ${sender.did}`));
|
|
8645
|
+
console.log(import_chalk32.default.dim(`Recipient: ${recipientDid}`));
|
|
8646
|
+
console.log(import_chalk32.default.dim(`Relationship: ${relationshipId}`));
|
|
8647
|
+
console.log(import_chalk32.default.dim(`Delegation: ${delegationId}`));
|
|
8648
|
+
console.log(import_chalk32.default.dim(`Request id: ${requestId}`));
|
|
8649
|
+
console.log(import_chalk32.default.dim(`Outcome: ${responsePayload.output ? "success" : "error"}`));
|
|
6980
8650
|
const result = await sendWorkEnvelope({ api, sender, recipientDid, body, ttlSeconds, verbose: opts.verbose, server: opts.server });
|
|
6981
8651
|
printIngestResult4(result);
|
|
6982
8652
|
}
|
|
@@ -6984,25 +8654,25 @@ async function sendWorkEnvelope(args) {
|
|
|
6984
8654
|
const nextSequence = (args.sender.lastSenderSequence ?? 0) + 1;
|
|
6985
8655
|
const protectedBlock = {
|
|
6986
8656
|
protocol_version: "arp/0.1",
|
|
6987
|
-
purpose:
|
|
6988
|
-
message_id: (0,
|
|
8657
|
+
purpose: import_sdk17.Purpose.ENVELOPE,
|
|
8658
|
+
message_id: (0, import_sdk17.uuidV4)(),
|
|
6989
8659
|
sender_did: args.sender.did,
|
|
6990
8660
|
recipient_did: args.recipientDid,
|
|
6991
8661
|
relationship_id: null,
|
|
6992
8662
|
sender_sequence: nextSequence,
|
|
6993
|
-
sender_nonce: (0,
|
|
6994
|
-
timestamp: (0,
|
|
6995
|
-
expires_at: (0,
|
|
8663
|
+
sender_nonce: (0, import_sdk17.senderNonce)(),
|
|
8664
|
+
timestamp: (0, import_sdk17.rfc3339)(),
|
|
8665
|
+
expires_at: (0, import_sdk17.expiresAt)(args.ttlSeconds),
|
|
6996
8666
|
delivery_id: null
|
|
6997
8667
|
};
|
|
6998
8668
|
const signer = makeSigner(args.sender);
|
|
6999
|
-
const envelope = (0,
|
|
8669
|
+
const envelope = (0, import_sdk17.signEnvelope)({
|
|
7000
8670
|
protected: protectedBlock,
|
|
7001
8671
|
body: args.body,
|
|
7002
8672
|
identitySecretKey: signer.identitySecretKey
|
|
7003
8673
|
});
|
|
7004
8674
|
if (args.verbose) {
|
|
7005
|
-
console.log(
|
|
8675
|
+
console.log(import_chalk32.default.bold("\nEnvelope (pre-send):"));
|
|
7006
8676
|
console.log(formatJson(envelope));
|
|
7007
8677
|
}
|
|
7008
8678
|
try {
|
|
@@ -7042,12 +8712,12 @@ async function resolveResponseRecipient(cmdName, api, signer, args) {
|
|
|
7042
8712
|
);
|
|
7043
8713
|
}
|
|
7044
8714
|
function printIngestResult4(result) {
|
|
7045
|
-
console.log(
|
|
7046
|
-
console.log(`${
|
|
7047
|
-
console.log(`${
|
|
7048
|
-
console.log(`${
|
|
7049
|
-
console.log(`${
|
|
7050
|
-
console.log(`${
|
|
8715
|
+
console.log(import_chalk32.default.green("\nDelivered."));
|
|
8716
|
+
console.log(`${import_chalk32.default.bold("Event id")}: ${import_chalk32.default.cyan(result.eventId)}`);
|
|
8717
|
+
console.log(`${import_chalk32.default.bold("Relationship id")}: ${import_chalk32.default.cyan(result.relationshipId)}`);
|
|
8718
|
+
console.log(`${import_chalk32.default.bold("Chain index")}: ${import_chalk32.default.cyan(String(result.relationshipEventIndex))}`);
|
|
8719
|
+
console.log(`${import_chalk32.default.bold("Server timestamp")}: ${import_chalk32.default.cyan(result.serverTimestamp)}`);
|
|
8720
|
+
console.log(`${import_chalk32.default.bold("Server event hash")}: ${import_chalk32.default.cyan(result.serverEventHash)}`);
|
|
7051
8721
|
}
|
|
7052
8722
|
function parseJsonObject(cmdName, flagName, raw) {
|
|
7053
8723
|
let parsed;
|
|
@@ -7085,13 +8755,13 @@ function parseParamsInput(cmdName, opts) {
|
|
|
7085
8755
|
return parseJsonObject(cmdName, "--params", opts.params ?? "{}");
|
|
7086
8756
|
}
|
|
7087
8757
|
function readJsonObjectFile(cmdName, flagName, path) {
|
|
7088
|
-
const { existsSync:
|
|
7089
|
-
if (!
|
|
8758
|
+
const { existsSync: existsSync7, readFileSync: readFileSync9 } = require("fs");
|
|
8759
|
+
if (!existsSync7(path)) {
|
|
7090
8760
|
throw new Error(`${cmdName}: ${flagName} file not found at ${path}`);
|
|
7091
8761
|
}
|
|
7092
8762
|
let raw;
|
|
7093
8763
|
try {
|
|
7094
|
-
raw =
|
|
8764
|
+
raw = readFileSync9(path, "utf8");
|
|
7095
8765
|
} catch (err) {
|
|
7096
8766
|
const detail = err instanceof Error ? err.message : String(err);
|
|
7097
8767
|
throw new Error(`${cmdName}: failed to read ${flagName} (${path}): ${detail}`);
|
|
@@ -7139,7 +8809,7 @@ function parseTtl7(cmdName, raw) {
|
|
|
7139
8809
|
}
|
|
7140
8810
|
function parseRequestId(cmdName, raw) {
|
|
7141
8811
|
if (raw === void 0 || raw === "") {
|
|
7142
|
-
return (0,
|
|
8812
|
+
return (0, import_sdk17.uuidV4)();
|
|
7143
8813
|
}
|
|
7144
8814
|
if (raw.length === 0) {
|
|
7145
8815
|
throw new Error(`${cmdName}: --request-id must be a non-empty string`);
|
|
@@ -7160,7 +8830,7 @@ function requireDid4(cmdName, did, label) {
|
|
|
7160
8830
|
}
|
|
7161
8831
|
|
|
7162
8832
|
// src/commands/work-list.ts
|
|
7163
|
-
var
|
|
8833
|
+
var import_chalk33 = __toESM(require("chalk"));
|
|
7164
8834
|
init_api();
|
|
7165
8835
|
var ALLOWED_STATES5 = /* @__PURE__ */ new Set(["requested", "responded"]);
|
|
7166
8836
|
function registerWorkListCommand(root) {
|
|
@@ -7187,9 +8857,9 @@ async function runWorkList(relationshipId, opts) {
|
|
|
7187
8857
|
const api = new ArpApiClient(opts.server);
|
|
7188
8858
|
const sender = resolveSenderAgent("work-list", opts.server, opts.fromDid);
|
|
7189
8859
|
if (!opts.json) {
|
|
7190
|
-
console.log(
|
|
7191
|
-
console.log(
|
|
7192
|
-
console.log(
|
|
8860
|
+
console.log(import_chalk33.default.dim(`Server: ${api.serverUrl}`));
|
|
8861
|
+
console.log(import_chalk33.default.dim(`Signer: ${sender.did}`));
|
|
8862
|
+
console.log(import_chalk33.default.dim(`Relationship: ${relationshipId}`));
|
|
7193
8863
|
}
|
|
7194
8864
|
const query = { limit };
|
|
7195
8865
|
if (state) query.state = state;
|
|
@@ -7202,7 +8872,7 @@ async function runWorkList(relationshipId, opts) {
|
|
|
7202
8872
|
return;
|
|
7203
8873
|
}
|
|
7204
8874
|
if (rows.length === 0) {
|
|
7205
|
-
console.log(
|
|
8875
|
+
console.log(import_chalk33.default.dim("\n(no work-logs for this relationship)"));
|
|
7206
8876
|
return;
|
|
7207
8877
|
}
|
|
7208
8878
|
console.log("");
|
|
@@ -7219,36 +8889,36 @@ async function runWorkList(relationshipId, opts) {
|
|
|
7219
8889
|
}));
|
|
7220
8890
|
}
|
|
7221
8891
|
const lastId = rows[rows.length - 1].id;
|
|
7222
|
-
console.log(
|
|
8892
|
+
console.log(import_chalk33.default.dim(`
|
|
7223
8893
|
${rows.length} work-log row(s). Paginate with --after ${lastId}.`));
|
|
7224
8894
|
}
|
|
7225
8895
|
function formatWorkLogLine(w, selfDid, opts = {}) {
|
|
7226
8896
|
const delegationPart = opts.fullIds ? w.delegationId : idHead4(w.delegationId);
|
|
7227
8897
|
const requestPart = opts.fullIds ? w.requestId : truncate4(w.requestId, 16);
|
|
7228
|
-
const id =
|
|
8898
|
+
const id = import_chalk33.default.bold(`${delegationPart}/${requestPart}`);
|
|
7229
8899
|
const state = colorState4(w.state).padEnd(stateColumnWidth4());
|
|
7230
8900
|
const peerCallerHead = opts.fullIds ? w.callerDid : didHead6(w.callerDid);
|
|
7231
8901
|
const peerPayeeHead = opts.fullIds ? w.payeeDid : didHead6(w.payeeDid);
|
|
7232
|
-
const direction = w.callerDid === selfDid ? `${
|
|
8902
|
+
const direction = w.callerDid === selfDid ? `${import_chalk33.default.bold("me")} \u2192 ${import_chalk33.default.dim(peerPayeeHead)}` : `${import_chalk33.default.dim(peerCallerHead)} \u2192 ${import_chalk33.default.bold("me")}`;
|
|
7233
8903
|
const outcome = formatOutcome(w);
|
|
7234
8904
|
return `${id} ${state} ${direction} ${outcome}`;
|
|
7235
8905
|
}
|
|
7236
8906
|
function colorState4(s) {
|
|
7237
8907
|
switch (s) {
|
|
7238
8908
|
case "requested":
|
|
7239
|
-
return
|
|
8909
|
+
return import_chalk33.default.yellow("requested");
|
|
7240
8910
|
case "responded":
|
|
7241
|
-
return
|
|
8911
|
+
return import_chalk33.default.green("responded");
|
|
7242
8912
|
}
|
|
7243
8913
|
}
|
|
7244
8914
|
function stateColumnWidth4() {
|
|
7245
8915
|
return 9;
|
|
7246
8916
|
}
|
|
7247
8917
|
function formatOutcome(w) {
|
|
7248
|
-
if (w.state === "requested") return
|
|
7249
|
-
if (w.responseError) return
|
|
7250
|
-
if (w.responseOutput) return
|
|
7251
|
-
return
|
|
8918
|
+
if (w.state === "requested") return import_chalk33.default.dim("(in flight)");
|
|
8919
|
+
if (w.responseError) return import_chalk33.default.red(`error ${w.responseError.code}: ${truncate4(w.responseError.message, 32)}`);
|
|
8920
|
+
if (w.responseOutput) return import_chalk33.default.cyan("ok");
|
|
8921
|
+
return import_chalk33.default.dim("(empty response)");
|
|
7252
8922
|
}
|
|
7253
8923
|
function idHead4(id) {
|
|
7254
8924
|
if (id.length <= 12) return id;
|
|
@@ -7292,11 +8962,14 @@ async function checkForUpdates() {
|
|
|
7292
8962
|
async function main() {
|
|
7293
8963
|
void checkForUpdates();
|
|
7294
8964
|
const program = new import_commander.Command();
|
|
7295
|
-
program.name("heyarp").description("ARP \u2014 Agent Relationship Protocol CLI (talks to apps/arp-server)").version(package_default.version).option(
|
|
7296
|
-
|
|
7297
|
-
|
|
7298
|
-
|
|
7299
|
-
|
|
8965
|
+
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);
|
|
8966
|
+
program.exitOverride();
|
|
8967
|
+
program.configureOutput({
|
|
8968
|
+
writeErr: (str) => {
|
|
8969
|
+
if (process.argv.includes("--json")) return;
|
|
8970
|
+
process.stderr.write(str);
|
|
8971
|
+
}
|
|
8972
|
+
});
|
|
7300
8973
|
registerConfigCommand(program);
|
|
7301
8974
|
registerGuideCommand(program);
|
|
7302
8975
|
registerHomesCommand(program);
|
|
@@ -7307,6 +8980,7 @@ async function main() {
|
|
|
7307
8980
|
registerDidDocCommand(program);
|
|
7308
8981
|
registerDoctorCommand(program);
|
|
7309
8982
|
registerEscrowCommands(program);
|
|
8983
|
+
registerExamplesCommand(program);
|
|
7310
8984
|
registerWhoamiCommand(program);
|
|
7311
8985
|
registerLifecycleCommands(program);
|
|
7312
8986
|
registerRotateCommand(program);
|
|
@@ -7328,16 +9002,24 @@ async function main() {
|
|
|
7328
9002
|
registerReceiptsCommand(program);
|
|
7329
9003
|
registerMemoryCommands(program);
|
|
7330
9004
|
registerWalletCommands(program);
|
|
9005
|
+
registerWebhookCommand(program);
|
|
9006
|
+
registerSettlementCommands(program);
|
|
7331
9007
|
try {
|
|
7332
9008
|
await program.parseAsync(process.argv);
|
|
7333
9009
|
} catch (err) {
|
|
7334
|
-
const
|
|
7335
|
-
|
|
7336
|
-
|
|
7337
|
-
|
|
7338
|
-
|
|
7339
|
-
|
|
7340
|
-
process.
|
|
9010
|
+
const cerr = err;
|
|
9011
|
+
const isCommanderError = typeof cerr.code === "string" && cerr.code.startsWith("commander.");
|
|
9012
|
+
if (isCommanderError && cerr.exitCode === 0) {
|
|
9013
|
+
process.exit(0);
|
|
9014
|
+
}
|
|
9015
|
+
const json = process.argv.includes("--json");
|
|
9016
|
+
const verbose = process.argv.includes("--trace");
|
|
9017
|
+
const exitCode = typeof cerr.exitCode === "number" && cerr.exitCode !== 0 ? cerr.exitCode : 1;
|
|
9018
|
+
if (isCommanderError && !json) {
|
|
9019
|
+
process.exit(exitCode);
|
|
9020
|
+
}
|
|
9021
|
+
emitError(err, { json, verbose });
|
|
9022
|
+
process.exit(exitCode);
|
|
7341
9023
|
}
|
|
7342
9024
|
}
|
|
7343
9025
|
process.on("unhandledRejection", (reason) => {
|