@heyanon-arp/cli 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1814 -533
- 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
|
|
@@ -710,7 +735,7 @@ var import_simple_update_notifier = __toESM(require("simple-update-notifier"));
|
|
|
710
735
|
// package.json
|
|
711
736
|
var package_default = {
|
|
712
737
|
name: "@heyanon-arp/cli",
|
|
713
|
-
version: "0.0.
|
|
738
|
+
version: "0.0.5",
|
|
714
739
|
description: "Command-line client for the Agent Relationship Protocol \u2014 register agents, sign envelopes, run escrowed work cycles on Solana.",
|
|
715
740
|
license: "MIT",
|
|
716
741
|
keywords: ["arp", "agent-relationship-protocol", "did", "solana", "escrow", "ed25519", "agents", "a2a", "cli"],
|
|
@@ -720,7 +745,7 @@ var package_default = {
|
|
|
720
745
|
publishConfig: {
|
|
721
746
|
access: "public"
|
|
722
747
|
},
|
|
723
|
-
files: ["dist", "LICENSE", "README.md"],
|
|
748
|
+
files: ["dist", "examples", "LICENSE", "README.md"],
|
|
724
749
|
engines: {
|
|
725
750
|
node: ">=22"
|
|
726
751
|
},
|
|
@@ -753,9 +778,6 @@ var package_default = {
|
|
|
753
778
|
}
|
|
754
779
|
};
|
|
755
780
|
|
|
756
|
-
// src/cli.ts
|
|
757
|
-
init_api();
|
|
758
|
-
|
|
759
781
|
// src/commands/agents.ts
|
|
760
782
|
var import_chalk2 = __toESM(require("chalk"));
|
|
761
783
|
init_api();
|
|
@@ -777,15 +799,39 @@ function formatGenericError(err, verbose = false) {
|
|
|
777
799
|
return `${import_chalk.default.red("Error")} ${message}
|
|
778
800
|
${import_chalk.default.gray(err.stack)}`;
|
|
779
801
|
}
|
|
780
|
-
function
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
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
|
+
}
|
|
784
822
|
const { ApiError: ApiError2 } = (init_api(), __toCommonJS(api_exports));
|
|
785
823
|
if (err instanceof ApiError2) {
|
|
786
|
-
|
|
824
|
+
console.error(formatApiError(err.payload, opts.verbose));
|
|
825
|
+
} else {
|
|
826
|
+
console.error(formatGenericError(err, opts.verbose));
|
|
787
827
|
}
|
|
788
|
-
|
|
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 });
|
|
789
835
|
}
|
|
790
836
|
function formatJson(value) {
|
|
791
837
|
return JSON.stringify(value, null, 2);
|
|
@@ -825,7 +871,18 @@ function supportsUnicodeFrame() {
|
|
|
825
871
|
return probe2.includes("UTF-8") || probe2.includes("UTF8");
|
|
826
872
|
}
|
|
827
873
|
function printJsonArray(rows) {
|
|
828
|
-
|
|
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);
|
|
829
886
|
}
|
|
830
887
|
function formatAgentsTable(rows) {
|
|
831
888
|
if (rows.length === 0) return import_chalk.default.dim("(no agents registered locally)");
|
|
@@ -864,11 +921,7 @@ function registerAgentsCommand(root) {
|
|
|
864
921
|
async function runAgents(opts) {
|
|
865
922
|
const limit = parseLimit(opts.limit);
|
|
866
923
|
const api = new ArpApiClient(opts.server);
|
|
867
|
-
|
|
868
|
-
console.error(import_chalk2.default.dim(`Server: ${api.serverUrl}`));
|
|
869
|
-
} else {
|
|
870
|
-
console.log(import_chalk2.default.dim(`Server: ${api.serverUrl}`));
|
|
871
|
-
}
|
|
924
|
+
progress(opts.json, import_chalk2.default.dim(`Server: ${api.serverUrl}`));
|
|
872
925
|
const query = { limit };
|
|
873
926
|
if (opts.tag && opts.tag.length > 0) query.tag = opts.tag.map((t) => t.trim().toLowerCase());
|
|
874
927
|
if (opts.query) query.q = opts.query;
|
|
@@ -1099,18 +1152,42 @@ function writeStateFile(state) {
|
|
|
1099
1152
|
(0, import_node_fs2.mkdirSync)(dir, { recursive: true, mode: 448 });
|
|
1100
1153
|
}
|
|
1101
1154
|
const body = JSON.stringify(state, null, 2);
|
|
1102
|
-
|
|
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
|
+
}
|
|
1103
1178
|
try {
|
|
1104
1179
|
(0, import_node_fs2.chmodSync)(path, 384);
|
|
1105
1180
|
} catch {
|
|
1181
|
+
chmodOk = false;
|
|
1106
1182
|
}
|
|
1183
|
+
return { chmodOk };
|
|
1107
1184
|
}
|
|
1108
1185
|
function saveAgent(serverOverride, agent) {
|
|
1109
1186
|
const key = resolveServerUrl(serverOverride);
|
|
1110
1187
|
const state = readStateFile();
|
|
1111
1188
|
if (!state.servers[key]) state.servers[key] = { agents: {} };
|
|
1112
1189
|
state.servers[key].agents[agent.did] = agent;
|
|
1113
|
-
writeStateFile(state);
|
|
1190
|
+
return writeStateFile(state);
|
|
1114
1191
|
}
|
|
1115
1192
|
function loadAgent(serverOverride, did) {
|
|
1116
1193
|
const key = resolveServerUrl(serverOverride);
|
|
@@ -1133,7 +1210,7 @@ function updateAgentLocal(serverOverride, did, patch) {
|
|
|
1133
1210
|
throw new Error(`Cannot update local state \u2014 no record for ${did} on ${key}.`);
|
|
1134
1211
|
}
|
|
1135
1212
|
server.agents[did] = { ...server.agents[did], ...patch };
|
|
1136
|
-
writeStateFile(state);
|
|
1213
|
+
return writeStateFile(state);
|
|
1137
1214
|
}
|
|
1138
1215
|
function resolveSenderAgent(cmdName, serverOverride, explicitFromDid) {
|
|
1139
1216
|
const resolvedServerUrl = resolveServerUrl(serverOverride);
|
|
@@ -1546,7 +1623,7 @@ function parseWaitInterval(raw) {
|
|
|
1546
1623
|
return n;
|
|
1547
1624
|
}
|
|
1548
1625
|
function sleep(ms) {
|
|
1549
|
-
return new Promise((
|
|
1626
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1550
1627
|
}
|
|
1551
1628
|
async function composeStatus(api, signerDid, relationshipId, signer) {
|
|
1552
1629
|
const [relationshipsOrNull, contractsOrError] = await Promise.all([
|
|
@@ -1996,7 +2073,7 @@ function registerDerivePdas(cmd) {
|
|
|
1996
2073
|
console.log(`EventAuthPDA: ${out.event_authority_pda}`);
|
|
1997
2074
|
}
|
|
1998
2075
|
} catch (err) {
|
|
1999
|
-
|
|
2076
|
+
emitError(err, { json: opts.json, verbose: process.argv.includes("--trace") });
|
|
2000
2077
|
process.exit(1);
|
|
2001
2078
|
}
|
|
2002
2079
|
});
|
|
@@ -2058,7 +2135,7 @@ function registerVerifyRelease(cmd) {
|
|
|
2058
2135
|
process.exitCode = 1;
|
|
2059
2136
|
}
|
|
2060
2137
|
} catch (err) {
|
|
2061
|
-
|
|
2138
|
+
emitError(err, { json: opts.json, verbose: process.argv.includes("--trace") });
|
|
2062
2139
|
process.exit(1);
|
|
2063
2140
|
}
|
|
2064
2141
|
});
|
|
@@ -2099,7 +2176,7 @@ async function verifyReleaseHandler(opts) {
|
|
|
2099
2176
|
if (shouldRetrySigCheck) {
|
|
2100
2177
|
const retryDelaysMs = [3e3, 3e3];
|
|
2101
2178
|
for (const delay of retryDelaysMs) {
|
|
2102
|
-
await new Promise((
|
|
2179
|
+
await new Promise((resolve2) => setTimeout(resolve2, delay));
|
|
2103
2180
|
const retriedSigs = await conn.getSignaturesForAddress(lockPda, { limit: 5 });
|
|
2104
2181
|
if (retriedSigs.length > 0) {
|
|
2105
2182
|
lockSeenInSignatures = true;
|
|
@@ -2239,7 +2316,7 @@ function registerCreateLock(cmd) {
|
|
|
2239
2316
|
const out = await createLockHandler(opts);
|
|
2240
2317
|
console.log(JSON.stringify(out, null, 2));
|
|
2241
2318
|
} catch (err) {
|
|
2242
|
-
|
|
2319
|
+
emitError(err, { json: opts.json, verbose: process.argv.includes("--trace") });
|
|
2243
2320
|
process.exit(1);
|
|
2244
2321
|
}
|
|
2245
2322
|
});
|
|
@@ -2454,7 +2531,7 @@ function registerSignSettlement(cmd) {
|
|
|
2454
2531
|
const out = await signSettlementHandler(opts);
|
|
2455
2532
|
console.log(JSON.stringify(out, null, 2));
|
|
2456
2533
|
} catch (err) {
|
|
2457
|
-
|
|
2534
|
+
emitError(err, { json: opts.json, verbose: process.argv.includes("--trace") });
|
|
2458
2535
|
process.exit(1);
|
|
2459
2536
|
}
|
|
2460
2537
|
});
|
|
@@ -2834,7 +2911,11 @@ Reference this delegation on subsequent calls with:`));
|
|
|
2834
2911
|
}
|
|
2835
2912
|
}
|
|
2836
2913
|
function registerAccept(parent) {
|
|
2837
|
-
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).option(
|
|
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(
|
|
2838
2919
|
"--no-wait-for-lock",
|
|
2839
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)."
|
|
2840
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) => {
|
|
@@ -2864,6 +2945,11 @@ function registerCancel(parent) {
|
|
|
2864
2945
|
}
|
|
2865
2946
|
async function runFollowupAction(relationshipId, delegationId, action, opts) {
|
|
2866
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
|
+
}
|
|
2867
2953
|
relationshipId = requireUuidNormalised(cmdName, relationshipId, "<relationship-id>");
|
|
2868
2954
|
delegationId = requireUuidNormalised(cmdName, delegationId, "<delegation-id>");
|
|
2869
2955
|
const ttlSeconds = parseTtl(cmdName, opts.ttl);
|
|
@@ -2885,7 +2971,11 @@ async function runFollowupAction(relationshipId, delegationId, action, opts) {
|
|
|
2885
2971
|
delegationId,
|
|
2886
2972
|
action,
|
|
2887
2973
|
timeoutSec: lockWaitTimeoutSec,
|
|
2888
|
-
intervalSec: lockWaitIntervalSec
|
|
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)
|
|
2889
2979
|
});
|
|
2890
2980
|
}
|
|
2891
2981
|
const content = {
|
|
@@ -2898,10 +2988,10 @@ async function runFollowupAction(relationshipId, delegationId, action, opts) {
|
|
|
2898
2988
|
if (declinePayload.reasonDetail) content.reason_detail = declinePayload.reasonDetail;
|
|
2899
2989
|
}
|
|
2900
2990
|
const body = { type: "delegation", content };
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
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}` : ""})`));
|
|
2905
2995
|
const result = await sendDelegationEnvelope({
|
|
2906
2996
|
api,
|
|
2907
2997
|
sender,
|
|
@@ -2911,6 +3001,21 @@ async function runFollowupAction(relationshipId, delegationId, action, opts) {
|
|
|
2911
3001
|
verbose: opts.verbose,
|
|
2912
3002
|
server: opts.server
|
|
2913
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
|
+
}
|
|
2914
3019
|
printIngestResult(result);
|
|
2915
3020
|
}
|
|
2916
3021
|
async function sendDelegationEnvelope(args) {
|
|
@@ -3311,7 +3416,11 @@ async function runCounter(relationshipId, contractId, opts) {
|
|
|
3311
3416
|
printIngestResult2(result);
|
|
3312
3417
|
}
|
|
3313
3418
|
function registerSign(parent) {
|
|
3314
|
-
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) => {
|
|
3315
3424
|
await runSignOrDecline(relationshipId, contractId, "sign", opts);
|
|
3316
3425
|
});
|
|
3317
3426
|
}
|
|
@@ -3329,6 +3438,11 @@ function registerDecline2(parent) {
|
|
|
3329
3438
|
}
|
|
3330
3439
|
async function runSignOrDecline(relationshipId, contractId, action, opts) {
|
|
3331
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
|
+
}
|
|
3332
3446
|
relationshipId = requireUuidNormalised(cmdName, relationshipId, "<relationship-id>");
|
|
3333
3447
|
contractId = requireUuidNormalised(cmdName, contractId, "<contract-id>");
|
|
3334
3448
|
const ttlSeconds = parseTtl2(cmdName, opts.ttl);
|
|
@@ -3353,10 +3467,10 @@ async function runSignOrDecline(relationshipId, contractId, action, opts) {
|
|
|
3353
3467
|
if (declinePayload.reasonDetail) content.reason_detail = declinePayload.reasonDetail;
|
|
3354
3468
|
}
|
|
3355
3469
|
const body = { type: "contract", content };
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
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}` : ""})`));
|
|
3360
3474
|
const result = await sendContractEnvelope({
|
|
3361
3475
|
api,
|
|
3362
3476
|
sender,
|
|
@@ -3366,6 +3480,21 @@ async function runSignOrDecline(relationshipId, contractId, action, opts) {
|
|
|
3366
3480
|
verbose: opts.verbose,
|
|
3367
3481
|
server: opts.server
|
|
3368
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
|
+
}
|
|
3369
3498
|
printIngestResult2(result);
|
|
3370
3499
|
}
|
|
3371
3500
|
async function sendContractEnvelope(args) {
|
|
@@ -3716,11 +3845,9 @@ async function runDelegations(relationshipId, opts) {
|
|
|
3716
3845
|
const state = parseState2(opts.state);
|
|
3717
3846
|
const api = new ArpApiClient(opts.server);
|
|
3718
3847
|
const sender = resolveSenderAgent("delegations", opts.server, opts.fromDid);
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
console.log(import_chalk9.default.dim(`Relationship: ${relationshipId}`));
|
|
3723
|
-
}
|
|
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}`));
|
|
3724
3851
|
const query = { limit };
|
|
3725
3852
|
if (state) query.state = state;
|
|
3726
3853
|
if (opts.contractId) query.contractId = opts.contractId;
|
|
@@ -3728,7 +3855,7 @@ async function runDelegations(relationshipId, opts) {
|
|
|
3728
3855
|
const signer = makeSigner(sender);
|
|
3729
3856
|
const rows = await api.listDelegations(relationshipId, signer, query);
|
|
3730
3857
|
if (opts.json) {
|
|
3731
|
-
|
|
3858
|
+
jsonOut(rows);
|
|
3732
3859
|
return;
|
|
3733
3860
|
}
|
|
3734
3861
|
if (rows.length === 0) {
|
|
@@ -3834,15 +3961,104 @@ function parseLimit3(raw) {
|
|
|
3834
3961
|
var import_sdk6 = require("@heyanon-arp/sdk");
|
|
3835
3962
|
init_api();
|
|
3836
3963
|
function registerDidDocCommand(root) {
|
|
3837
|
-
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) => {
|
|
3838
3970
|
if (!(0, import_sdk6.isValidDid)(did)) {
|
|
3839
3971
|
throw new Error(`'${did}' is not a syntactically valid did:arp identifier`);
|
|
3840
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
|
+
}
|
|
3841
3976
|
const api = new ArpApiClient(opts.server);
|
|
3842
3977
|
const doc = await api.getDidDocument(did);
|
|
3843
|
-
|
|
3978
|
+
if (opts.field !== void 0) {
|
|
3979
|
+
emitField(doc, opts.field);
|
|
3980
|
+
return;
|
|
3981
|
+
}
|
|
3982
|
+
jsonOut(doc);
|
|
3844
3983
|
});
|
|
3845
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
|
+
}
|
|
3846
4062
|
|
|
3847
4063
|
// src/commands/doctor.ts
|
|
3848
4064
|
var import_sdk7 = require("@heyanon-arp/sdk");
|
|
@@ -4083,7 +4299,8 @@ async function runEscrowInfo(opts) {
|
|
|
4083
4299
|
const api = new ArpApiClient(opts.server);
|
|
4084
4300
|
const status = await api.getProtocolFee();
|
|
4085
4301
|
if (opts.json) {
|
|
4086
|
-
|
|
4302
|
+
progress(true, import_chalk12.default.dim(`Server: ${api.serverUrl}`));
|
|
4303
|
+
jsonOut(status);
|
|
4087
4304
|
} else {
|
|
4088
4305
|
console.log(formatProtocolFeeStatus(api.serverUrl, status));
|
|
4089
4306
|
}
|
|
@@ -4174,6 +4391,8 @@ async function runDeriveConditionHash(relationshipId, contractId, opts) {
|
|
|
4174
4391
|
const api = new ArpApiClient(opts.server);
|
|
4175
4392
|
const sender = resolveSenderAgent("escrow derive-condition-hash", opts.server, opts.fromDid);
|
|
4176
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}`));
|
|
4177
4396
|
let contract;
|
|
4178
4397
|
try {
|
|
4179
4398
|
contract = await findContractRow(api, signer, relationshipId, contractId, versionPin);
|
|
@@ -4187,14 +4406,12 @@ async function runDeriveConditionHash(relationshipId, contractId, opts) {
|
|
|
4187
4406
|
const hashBytes = (0, import_sdk8.deriveConditionHash)(subset);
|
|
4188
4407
|
const hex = (0, import_utils2.bytesToHex)(hashBytes);
|
|
4189
4408
|
if (opts.json) {
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
})
|
|
4197
|
-
);
|
|
4409
|
+
jsonOut({
|
|
4410
|
+
contract_id: contract.contractId,
|
|
4411
|
+
version: contract.version,
|
|
4412
|
+
condition_hash_hex: hex,
|
|
4413
|
+
projected_subset: subset
|
|
4414
|
+
});
|
|
4198
4415
|
return;
|
|
4199
4416
|
}
|
|
4200
4417
|
console.log(import_chalk12.default.dim(`Relationship: ${relationshipId}`));
|
|
@@ -4234,16 +4451,14 @@ async function runRecoverSequence(opts) {
|
|
|
4234
4451
|
}
|
|
4235
4452
|
const outcome = classifyRecoverOutcome(local, server);
|
|
4236
4453
|
if (opts.json) {
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
})
|
|
4246
|
-
);
|
|
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
|
+
});
|
|
4247
4462
|
} else {
|
|
4248
4463
|
console.log(import_chalk12.default.dim(`DID: ${sender.did}`));
|
|
4249
4464
|
console.log(import_chalk12.default.dim(`Server: ${api.serverUrl}`));
|
|
@@ -4413,180 +4628,527 @@ function parseSince(raw) {
|
|
|
4413
4628
|
return n;
|
|
4414
4629
|
}
|
|
4415
4630
|
|
|
4416
|
-
// src/commands/
|
|
4631
|
+
// src/commands/examples.ts
|
|
4632
|
+
var import_node_fs5 = require("fs");
|
|
4633
|
+
var import_node_path4 = require("path");
|
|
4417
4634
|
var import_chalk14 = __toESM(require("chalk"));
|
|
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
|
+
}
|
|
4691
|
+
});
|
|
4692
|
+
}
|
|
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
|
+
}
|
|
4709
|
+
|
|
4710
|
+
// src/commands/guide.ts
|
|
4711
|
+
var import_chalk15 = __toESM(require("chalk"));
|
|
4712
|
+
|
|
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
|
|
4418
5000
|
function registerGuideCommand(root) {
|
|
4419
|
-
root.command("guide").description(
|
|
4420
|
-
|
|
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));
|
|
4421
5013
|
});
|
|
4422
5014
|
}
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
""
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
""
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
" (`sha256:<64 lowercase hex>`), it does NOT recompute the value",
|
|
4543
|
-
" against the work_log payload. So for smoke testing any",
|
|
4544
|
-
" well-shaped placeholder (e.g. `sha256:$(printf '%064d' 1)`) is",
|
|
4545
|
-
" accepted. Real binding-check lands when the validator gets",
|
|
4546
|
-
" payload-aware (V1.x).",
|
|
4547
|
-
"",
|
|
4548
|
-
import_chalk14.default.bold("10. Catalog vs live worker + autonomous worker latency"),
|
|
4549
|
-
" `heyarp agents` rows are LISTED (publicationStatus=active), not ONLINE.",
|
|
4550
|
-
" Probe with `heyarp doctor <did>` (LIVE / REACHABLE / DORMANT / UNKNOWN).",
|
|
4551
|
-
" Autonomous LLM workers respond in 30s\u20138min typically; treat silence",
|
|
4552
|
-
' > 15min as "try someone else". Parse inbox events as JSON:',
|
|
4553
|
-
" heyarp inbox --json | jq '.[0].body.content.contract_id' # paginated",
|
|
4554
|
-
" heyarp inbox --tail --json | jq '.data.body.content.contract_id?' # SSE",
|
|
4555
|
-
" --tail wraps each line as `{type, data, id?}` \u2014 body lives under `.data`.",
|
|
4556
|
-
" ID by body.type: contract\u2192contract_id; delegation\u2192delegation_id;",
|
|
4557
|
-
" work_request\u2192delegation_id+request_id; receipt\u2192delegation_id.",
|
|
4558
|
-
" Wire keys \u2260 human row labels \u2014 events: `.senderDid` (not `.signer`),",
|
|
4559
|
-
" `.type` (not `.payload.type`); receipts: `.receiptEventHash` (not",
|
|
4560
|
-
" `.serverEventHash` \u2014 null on receipt rows).",
|
|
4561
|
-
" `relationship.state` STAYS `active` after `cycle.complete`",
|
|
4562
|
-
" (relationships host multiple delegations sequentially). Read the",
|
|
4563
|
-
" delegation row's `state == completed` + the `Cycle: COMPLETE`",
|
|
4564
|
-
" status line for cycle-done \u2014 NOT the relationship row alone.",
|
|
4565
|
-
"",
|
|
4566
|
-
import_chalk14.default.bold("11. When you get stuck"),
|
|
4567
|
-
" Every command supports `--help` \u2014 read structured `code` + `message`",
|
|
4568
|
-
" error fields, they name the exact state-machine constraint violated.",
|
|
4569
|
-
" `heyarp doctor <did>` probes a peer agent's endpoint (LISTED vs LIVE).",
|
|
4570
|
-
" More: README at https://www.npmjs.com/package/@heyanon-arp/cli"
|
|
4571
|
-
].join("\n");
|
|
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
|
+
}
|
|
4572
5134
|
|
|
4573
5135
|
// src/commands/homes.ts
|
|
4574
|
-
var
|
|
4575
|
-
var
|
|
4576
|
-
var
|
|
5136
|
+
var import_node_fs7 = require("fs");
|
|
5137
|
+
var import_node_path6 = require("path");
|
|
5138
|
+
var import_chalk16 = __toESM(require("chalk"));
|
|
4577
5139
|
var import_prompts = __toESM(require("prompts"));
|
|
4578
5140
|
|
|
4579
5141
|
// src/homes.ts
|
|
4580
|
-
var
|
|
4581
|
-
var
|
|
5142
|
+
var import_node_fs6 = require("fs");
|
|
5143
|
+
var import_node_path5 = require("path");
|
|
4582
5144
|
init_paths();
|
|
4583
5145
|
var REGISTRY_WARNING = "DO NOT COMMIT \u2014 paths to home dirs may be sensitive (e.g. encrypted-volume mounts).";
|
|
4584
5146
|
function readRegistry() {
|
|
4585
5147
|
const path = homesRegistryPath();
|
|
4586
|
-
if (!(0,
|
|
5148
|
+
if (!(0, import_node_fs6.existsSync)(path)) return { homes: [] };
|
|
4587
5149
|
let raw;
|
|
4588
5150
|
try {
|
|
4589
|
-
raw = (0,
|
|
5151
|
+
raw = (0, import_node_fs6.readFileSync)(path, "utf8");
|
|
4590
5152
|
} catch (err) {
|
|
4591
5153
|
throw new Error(`Failed to read homes registry at ${path}: ${err.message}`);
|
|
4592
5154
|
}
|
|
@@ -4607,12 +5169,12 @@ function readRegistry() {
|
|
|
4607
5169
|
}
|
|
4608
5170
|
function writeRegistry(file) {
|
|
4609
5171
|
const path = homesRegistryPath();
|
|
4610
|
-
const dir = (0,
|
|
4611
|
-
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 });
|
|
4612
5174
|
const body = JSON.stringify({ _warning: REGISTRY_WARNING, homes: file.homes }, null, 2);
|
|
4613
|
-
(0,
|
|
5175
|
+
(0, import_node_fs6.writeFileSync)(path, body, { encoding: "utf8", mode: 384 });
|
|
4614
5176
|
try {
|
|
4615
|
-
(0,
|
|
5177
|
+
(0, import_node_fs6.chmodSync)(path, 384);
|
|
4616
5178
|
} catch {
|
|
4617
5179
|
}
|
|
4618
5180
|
}
|
|
@@ -4640,7 +5202,7 @@ function forgetHome(homePath) {
|
|
|
4640
5202
|
return true;
|
|
4641
5203
|
}
|
|
4642
5204
|
function homeStillExists(homePath) {
|
|
4643
|
-
return (0,
|
|
5205
|
+
return (0, import_node_fs6.existsSync)(`${homePath}/agents.json`);
|
|
4644
5206
|
}
|
|
4645
5207
|
|
|
4646
5208
|
// src/commands/homes.ts
|
|
@@ -4667,27 +5229,27 @@ function runHomes(opts) {
|
|
|
4667
5229
|
return;
|
|
4668
5230
|
}
|
|
4669
5231
|
if (rows.length === 0) {
|
|
4670
|
-
console.log(
|
|
4671
|
-
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()}`));
|
|
4672
5234
|
return;
|
|
4673
5235
|
}
|
|
4674
5236
|
const header = ["Path", "Agents", "Last seen", "Status"];
|
|
4675
5237
|
const data = rows.map((r) => [
|
|
4676
|
-
r.path + (r.isCurrent ?
|
|
5238
|
+
r.path + (r.isCurrent ? import_chalk16.default.green(" (current)") : ""),
|
|
4677
5239
|
String(r.agentCount),
|
|
4678
5240
|
formatRelativeTime(r.lastSeenAt),
|
|
4679
|
-
r.exists ?
|
|
5241
|
+
r.exists ? import_chalk16.default.green("ok") : import_chalk16.default.red("missing")
|
|
4680
5242
|
]);
|
|
4681
5243
|
console.log("");
|
|
4682
5244
|
console.log(formatTable(header, data));
|
|
4683
|
-
console.log(
|
|
5245
|
+
console.log(import_chalk16.default.dim(`
|
|
4684
5246
|
Registry path: ${homesRegistryPath()}`));
|
|
4685
|
-
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.`));
|
|
4686
5248
|
}
|
|
4687
5249
|
async function runForget(path, opts) {
|
|
4688
5250
|
if (!opts.yes) {
|
|
4689
|
-
console.log(
|
|
4690
|
-
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."));
|
|
4691
5253
|
const answer = await (0, import_prompts.default)(
|
|
4692
5254
|
{
|
|
4693
5255
|
type: "confirm",
|
|
@@ -4697,28 +5259,28 @@ async function runForget(path, opts) {
|
|
|
4697
5259
|
},
|
|
4698
5260
|
{
|
|
4699
5261
|
onCancel: () => {
|
|
4700
|
-
console.log(
|
|
5262
|
+
console.log(import_chalk16.default.yellow("Aborted."));
|
|
4701
5263
|
process.exit(130);
|
|
4702
5264
|
}
|
|
4703
5265
|
}
|
|
4704
5266
|
);
|
|
4705
5267
|
if (!answer.confirm) {
|
|
4706
|
-
console.log(
|
|
5268
|
+
console.log(import_chalk16.default.dim("Aborted (no changes)."));
|
|
4707
5269
|
return;
|
|
4708
5270
|
}
|
|
4709
5271
|
}
|
|
4710
5272
|
const removed = forgetHome(path);
|
|
4711
5273
|
if (removed) {
|
|
4712
|
-
console.log(
|
|
5274
|
+
console.log(import_chalk16.default.green(`\u2713 forgot ${path}`));
|
|
4713
5275
|
} else {
|
|
4714
|
-
console.log(
|
|
5276
|
+
console.log(import_chalk16.default.dim(`(no entry for ${path} in the registry \u2014 already absent)`));
|
|
4715
5277
|
}
|
|
4716
5278
|
}
|
|
4717
5279
|
function countAgents(homePath) {
|
|
4718
|
-
const file = (0,
|
|
4719
|
-
if (!(0,
|
|
5280
|
+
const file = (0, import_node_path6.join)(homePath, "agents.json");
|
|
5281
|
+
if (!(0, import_node_fs7.existsSync)(file)) return 0;
|
|
4720
5282
|
try {
|
|
4721
|
-
const parsed = JSON.parse((0,
|
|
5283
|
+
const parsed = JSON.parse((0, import_node_fs7.readFileSync)(file, "utf8"));
|
|
4722
5284
|
if (!parsed || typeof parsed !== "object" || !parsed.servers) return 0;
|
|
4723
5285
|
let total = 0;
|
|
4724
5286
|
for (const server of Object.values(parsed.servers)) {
|
|
@@ -4736,13 +5298,13 @@ function formatTable(header, data) {
|
|
|
4736
5298
|
const padding = " ".repeat(Math.max(0, widths[i] - lengths[i]));
|
|
4737
5299
|
return cell + padding;
|
|
4738
5300
|
}).join(" ");
|
|
4739
|
-
const headerLine =
|
|
5301
|
+
const headerLine = import_chalk16.default.bold(
|
|
4740
5302
|
pad(
|
|
4741
5303
|
header,
|
|
4742
5304
|
header.map((s) => s.length)
|
|
4743
5305
|
)
|
|
4744
5306
|
);
|
|
4745
|
-
const sepLine =
|
|
5307
|
+
const sepLine = import_chalk16.default.dim(
|
|
4746
5308
|
pad(
|
|
4747
5309
|
widths.map((w) => "-".repeat(w)),
|
|
4748
5310
|
widths
|
|
@@ -4769,7 +5331,7 @@ function formatRelativeTime(iso) {
|
|
|
4769
5331
|
}
|
|
4770
5332
|
|
|
4771
5333
|
// src/commands/inbox.ts
|
|
4772
|
-
var
|
|
5334
|
+
var import_chalk17 = __toESM(require("chalk"));
|
|
4773
5335
|
init_api();
|
|
4774
5336
|
function formatTailStartedPing(input) {
|
|
4775
5337
|
const ping = {
|
|
@@ -4816,8 +5378,8 @@ async function runInbox(positionalDid, opts) {
|
|
|
4816
5378
|
}
|
|
4817
5379
|
const api = new ArpApiClient(opts.server);
|
|
4818
5380
|
if (!opts.json) {
|
|
4819
|
-
console.log(
|
|
4820
|
-
console.log(
|
|
5381
|
+
console.log(import_chalk17.default.dim(`Server: ${api.serverUrl}`));
|
|
5382
|
+
console.log(import_chalk17.default.dim(`Signer: ${local.did}`));
|
|
4821
5383
|
}
|
|
4822
5384
|
const query = { limit };
|
|
4823
5385
|
if (opts.before) query.before = opts.before;
|
|
@@ -4831,7 +5393,7 @@ async function runInbox(positionalDid, opts) {
|
|
|
4831
5393
|
return;
|
|
4832
5394
|
}
|
|
4833
5395
|
if (events.length === 0) {
|
|
4834
|
-
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)"));
|
|
4835
5397
|
return;
|
|
4836
5398
|
}
|
|
4837
5399
|
console.log("");
|
|
@@ -4846,16 +5408,16 @@ async function runInbox(positionalDid, opts) {
|
|
|
4846
5408
|
secondary: `eventId=${ev.eventId} serverEventHash=${ev.serverEventHash}`
|
|
4847
5409
|
}));
|
|
4848
5410
|
}
|
|
4849
|
-
const addressedToMeHint =
|
|
5411
|
+
const addressedToMeHint = import_chalk17.default.dim(" (envelopes addressed to me \u2014 for the full chain see `heyarp events <relationship-id>`)");
|
|
4850
5412
|
if (opts.since && !opts.before) {
|
|
4851
5413
|
console.log(
|
|
4852
|
-
|
|
5414
|
+
import_chalk17.default.dim(
|
|
4853
5415
|
`
|
|
4854
5416
|
${events.length} event(s) (oldest-first).${addressedToMeHint} Advance the forward cursor with --since <serverTimestamp> --since-event-id <eventId> using the LAST row above.`
|
|
4855
5417
|
)
|
|
4856
5418
|
);
|
|
4857
5419
|
} else {
|
|
4858
|
-
console.log(
|
|
5420
|
+
console.log(import_chalk17.default.dim(`
|
|
4859
5421
|
${events.length} event(s).${addressedToMeHint} Paginate with --before <serverTimestamp> --before-event-id <eventId> using the LAST row above.`));
|
|
4860
5422
|
}
|
|
4861
5423
|
}
|
|
@@ -4868,8 +5430,9 @@ async function runInboxTail(did, local, opts) {
|
|
|
4868
5430
|
setBlocking.call(handle, true);
|
|
4869
5431
|
stdoutBlockingApplied = true;
|
|
4870
5432
|
} else {
|
|
4871
|
-
|
|
4872
|
-
|
|
5433
|
+
warn(
|
|
5434
|
+
opts.json,
|
|
5435
|
+
import_chalk17.default.yellow(
|
|
4873
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."
|
|
4874
5437
|
)
|
|
4875
5438
|
);
|
|
@@ -4879,9 +5442,9 @@ async function runInboxTail(did, local, opts) {
|
|
|
4879
5442
|
if (opts.json) {
|
|
4880
5443
|
console.log(formatTailStartedPing({ server: api.serverUrl, signer: local.did, stdoutBlockingApplied }));
|
|
4881
5444
|
} else {
|
|
4882
|
-
console.log(
|
|
4883
|
-
console.log(
|
|
4884
|
-
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)"));
|
|
4885
5448
|
}
|
|
4886
5449
|
const controller = new AbortController();
|
|
4887
5450
|
let userAborted = false;
|
|
@@ -4900,7 +5463,7 @@ async function runInboxTail(did, local, opts) {
|
|
|
4900
5463
|
}
|
|
4901
5464
|
if (event.type === "heartbeat") continue;
|
|
4902
5465
|
if (event.type === "connected") {
|
|
4903
|
-
console.log(
|
|
5466
|
+
console.log(import_chalk17.default.green("\u25CF stream open \u2014 listening for envelopes..."));
|
|
4904
5467
|
continue;
|
|
4905
5468
|
}
|
|
4906
5469
|
if (event.type === "envelope") {
|
|
@@ -4914,7 +5477,7 @@ async function runInboxTail(did, local, opts) {
|
|
|
4914
5477
|
}
|
|
4915
5478
|
continue;
|
|
4916
5479
|
}
|
|
4917
|
-
console.log(
|
|
5480
|
+
console.log(import_chalk17.default.dim(`(unknown event: ${event.type})`));
|
|
4918
5481
|
}
|
|
4919
5482
|
if (!userAborted) {
|
|
4920
5483
|
throw new Error("inbox --tail: stream ended unexpectedly (server may have restarted, or change stream errored). Re-run to reconnect.");
|
|
@@ -4922,7 +5485,7 @@ async function runInboxTail(did, local, opts) {
|
|
|
4922
5485
|
} catch (err) {
|
|
4923
5486
|
const name = err.name;
|
|
4924
5487
|
if (name === "AbortError" || userAborted) {
|
|
4925
|
-
if (!opts.json) console.log(
|
|
5488
|
+
if (!opts.json) console.log(import_chalk17.default.dim("\nstream closed."));
|
|
4926
5489
|
return;
|
|
4927
5490
|
}
|
|
4928
5491
|
throw err;
|
|
@@ -4942,15 +5505,15 @@ function formatInboxTable(events, opts = {}) {
|
|
|
4942
5505
|
]);
|
|
4943
5506
|
const widths = header.map((h, i) => Math.max(h.length, ...data.map((row) => row[i].length)));
|
|
4944
5507
|
const pad = (cells) => cells.map((c, i) => c.padEnd(widths[i])).join(" ");
|
|
4945
|
-
const lines = [
|
|
4946
|
-
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");
|
|
4947
5510
|
return `${lines.join("\n")}
|
|
4948
5511
|
|
|
4949
|
-
${
|
|
5512
|
+
${import_chalk17.default.bold("Pagination cursors")} (last \u2192 first):
|
|
4950
5513
|
${detail}`;
|
|
4951
5514
|
}
|
|
4952
5515
|
function hashHead2(hash) {
|
|
4953
|
-
if (!hash) return
|
|
5516
|
+
if (!hash) return import_chalk17.default.dim("(none)");
|
|
4954
5517
|
if (hash.length <= 14) return hash;
|
|
4955
5518
|
return `${hash.slice(0, 14)}...`;
|
|
4956
5519
|
}
|
|
@@ -4965,35 +5528,35 @@ function parseLimit5(raw) {
|
|
|
4965
5528
|
|
|
4966
5529
|
// src/commands/keys.ts
|
|
4967
5530
|
var import_sdk9 = require("@heyanon-arp/sdk");
|
|
4968
|
-
var
|
|
5531
|
+
var import_chalk18 = __toESM(require("chalk"));
|
|
4969
5532
|
function registerKeysCommand(root) {
|
|
4970
5533
|
const keys = root.command("keys").description("Local key utilities");
|
|
4971
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) => {
|
|
4972
5535
|
const identity = (0, import_sdk9.generateKeyPair)();
|
|
4973
5536
|
const settlement = (0, import_sdk9.generateKeyPair)();
|
|
4974
5537
|
const out = [
|
|
4975
|
-
|
|
4976
|
-
` public (base58btc): ${
|
|
4977
|
-
` 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"))}`,
|
|
4978
5541
|
"",
|
|
4979
|
-
|
|
4980
|
-
` public (base58btc): ${
|
|
4981
|
-
` 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"))}`,
|
|
4982
5545
|
"",
|
|
4983
|
-
|
|
4984
|
-
` ${
|
|
5546
|
+
import_chalk18.default.bold("Resulting DID"),
|
|
5547
|
+
` ${import_chalk18.default.cyan((0, import_sdk9.formatDid)(identity.publicKey))}`
|
|
4985
5548
|
];
|
|
4986
5549
|
console.log(out.join("\n"));
|
|
4987
5550
|
if (opts.save) {
|
|
4988
|
-
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."));
|
|
4989
5552
|
}
|
|
4990
5553
|
});
|
|
4991
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) => {
|
|
4992
5555
|
const seed = decodeSeed(secretKeyB64);
|
|
4993
5556
|
const pub = (0, import_sdk9.getPublicKey)(seed);
|
|
4994
5557
|
const did = (0, import_sdk9.formatDid)(pub);
|
|
4995
|
-
console.log(`${
|
|
4996
|
-
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))}`);
|
|
4997
5560
|
});
|
|
4998
5561
|
}
|
|
4999
5562
|
function decodeSeed(b64) {
|
|
@@ -5010,12 +5573,12 @@ function decodeSeed(b64) {
|
|
|
5010
5573
|
}
|
|
5011
5574
|
|
|
5012
5575
|
// src/commands/list.ts
|
|
5013
|
-
var
|
|
5576
|
+
var import_chalk19 = __toESM(require("chalk"));
|
|
5014
5577
|
function registerListCommand(root) {
|
|
5015
5578
|
root.command("list").description("List agents registered locally (~/.arp/agents.json)").action(() => {
|
|
5016
5579
|
const rows = listAgents();
|
|
5017
5580
|
if (rows.length === 0) {
|
|
5018
|
-
console.log(
|
|
5581
|
+
console.log(import_chalk19.default.dim(`No local agents. State file: ${stateFilePath()}`));
|
|
5019
5582
|
return;
|
|
5020
5583
|
}
|
|
5021
5584
|
const grouped = /* @__PURE__ */ new Map();
|
|
@@ -5027,7 +5590,7 @@ function registerListCommand(root) {
|
|
|
5027
5590
|
for (const [serverUrl, group] of grouped) {
|
|
5028
5591
|
if (!first) console.log("");
|
|
5029
5592
|
first = false;
|
|
5030
|
-
console.log(
|
|
5593
|
+
console.log(import_chalk19.default.bold(`Server: ${serverUrl}`));
|
|
5031
5594
|
console.log(formatAgentsTable(group.map(({ agent }) => ({ did: agent.did, name: agent.name, tags: agent.tags, registeredAt: agent.registeredAt }))));
|
|
5032
5595
|
}
|
|
5033
5596
|
});
|
|
@@ -5035,7 +5598,7 @@ function registerListCommand(root) {
|
|
|
5035
5598
|
|
|
5036
5599
|
// src/commands/memory.ts
|
|
5037
5600
|
var import_sdk10 = require("@heyanon-arp/sdk");
|
|
5038
|
-
var
|
|
5601
|
+
var import_chalk20 = __toESM(require("chalk"));
|
|
5039
5602
|
init_api();
|
|
5040
5603
|
function registerMemoryCommands(root) {
|
|
5041
5604
|
const cmd = root.command("memory").description("Memory deltas: write, list, fetch one.");
|
|
@@ -5088,7 +5651,7 @@ function parseAddOptions(cmdName, opts) {
|
|
|
5088
5651
|
throw new Error(`${cmdName}: --commit-after=${commitAfter} requires --delegation-id <uuid> (the delegation whose settlement gates the commit).`);
|
|
5089
5652
|
}
|
|
5090
5653
|
if (commitAfter === "immediate" && opts.delegationId !== void 0) {
|
|
5091
|
-
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.`));
|
|
5092
5655
|
}
|
|
5093
5656
|
const out = {
|
|
5094
5657
|
kind: opts.kind,
|
|
@@ -5123,13 +5686,13 @@ async function runAdd(recipientDid, opts) {
|
|
|
5123
5686
|
server: opts.server
|
|
5124
5687
|
});
|
|
5125
5688
|
if (opts.verbose) {
|
|
5126
|
-
console.log(
|
|
5689
|
+
console.log(import_chalk20.default.bold("\nServer response:"));
|
|
5127
5690
|
console.log(formatJson(result));
|
|
5128
5691
|
}
|
|
5129
|
-
console.log(
|
|
5692
|
+
console.log(import_chalk20.default.green(`
|
|
5130
5693
|
memory_delta event ${result.eventId} accepted (commit_after=${bodyContent.commit_after ?? "immediate"})`));
|
|
5131
|
-
console.log(
|
|
5132
|
-
console.log(
|
|
5694
|
+
console.log(import_chalk20.default.dim(`relationshipId: ${result.relationshipId}`));
|
|
5695
|
+
console.log(import_chalk20.default.dim(`serverEventHash: ${result.serverEventHash}`));
|
|
5133
5696
|
}
|
|
5134
5697
|
async function sendMemoryEnvelope(args) {
|
|
5135
5698
|
const nextSequence = (args.sender.lastSenderSequence ?? 0) + 1;
|
|
@@ -5153,7 +5716,7 @@ async function sendMemoryEnvelope(args) {
|
|
|
5153
5716
|
identitySecretKey: signer.identitySecretKey
|
|
5154
5717
|
});
|
|
5155
5718
|
if (args.verbose) {
|
|
5156
|
-
console.log(
|
|
5719
|
+
console.log(import_chalk20.default.bold("\nEnvelope (pre-send):"));
|
|
5157
5720
|
console.log(formatJson(envelope));
|
|
5158
5721
|
}
|
|
5159
5722
|
try {
|
|
@@ -5189,7 +5752,7 @@ async function runList(relationshipId, opts) {
|
|
|
5189
5752
|
return;
|
|
5190
5753
|
}
|
|
5191
5754
|
if (rows.length === 0) {
|
|
5192
|
-
console.log(
|
|
5755
|
+
console.log(import_chalk20.default.dim("(no memory entries)"));
|
|
5193
5756
|
return;
|
|
5194
5757
|
}
|
|
5195
5758
|
for (const r of rows) {
|
|
@@ -5197,7 +5760,7 @@ async function runList(relationshipId, opts) {
|
|
|
5197
5760
|
}
|
|
5198
5761
|
if (rows.length === limitN) {
|
|
5199
5762
|
const lastId = rows[rows.length - 1].id;
|
|
5200
|
-
console.log(
|
|
5763
|
+
console.log(import_chalk20.default.dim(`
|
|
5201
5764
|
Page may not be complete \u2014 paginate with --after ${lastId}`));
|
|
5202
5765
|
}
|
|
5203
5766
|
}
|
|
@@ -5205,9 +5768,9 @@ function formatMemoryLine(r) {
|
|
|
5205
5768
|
const idHead5 = `${r.id.slice(0, 8)}...${r.id.slice(-4)}`;
|
|
5206
5769
|
const authorTail = r.authorDid.length > 20 ? `${r.authorDid.slice(0, 14)}...${r.authorDid.slice(-4)}` : r.authorDid;
|
|
5207
5770
|
const contentPreview = r.content.length > 60 ? `${r.content.slice(0, 57)}...` : r.content;
|
|
5208
|
-
const cosignedTag = r.isCosigned ?
|
|
5209
|
-
const gatedTag = r.delegationId ?
|
|
5210
|
-
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}`;
|
|
5211
5774
|
}
|
|
5212
5775
|
function registerShow(parent) {
|
|
5213
5776
|
parent.command("show").description("Fetch one memory entry by its server `_id`.").argument(
|
|
@@ -5226,18 +5789,18 @@ async function runShow(entryId, opts) {
|
|
|
5226
5789
|
console.log(JSON.stringify(row));
|
|
5227
5790
|
return;
|
|
5228
5791
|
}
|
|
5229
|
-
console.log(
|
|
5792
|
+
console.log(import_chalk20.default.bold("Memory entry:"));
|
|
5230
5793
|
console.log(formatJson(row));
|
|
5231
5794
|
console.log("");
|
|
5232
|
-
console.log(`${
|
|
5233
|
-
console.log(`${
|
|
5234
|
-
console.log(`${
|
|
5235
|
-
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}`)}`);
|
|
5236
5799
|
if (row.delegationId) {
|
|
5237
|
-
console.log(`${
|
|
5800
|
+
console.log(`${import_chalk20.default.dim("delegationId:")} ${import_chalk20.default.cyan(row.delegationId)} (settlement-gated)`);
|
|
5238
5801
|
}
|
|
5239
5802
|
if (row.supersedesId) {
|
|
5240
|
-
console.log(`${
|
|
5803
|
+
console.log(`${import_chalk20.default.dim("supersedes:")} ${import_chalk20.default.cyan(row.supersedesId)}`);
|
|
5241
5804
|
}
|
|
5242
5805
|
}
|
|
5243
5806
|
function parseTtl3(cmdName, raw) {
|
|
@@ -5264,9 +5827,9 @@ function parseLimit6(cmdName, raw) {
|
|
|
5264
5827
|
}
|
|
5265
5828
|
|
|
5266
5829
|
// src/commands/receipt.ts
|
|
5267
|
-
var
|
|
5830
|
+
var import_node_fs8 = require("fs");
|
|
5268
5831
|
var import_sdk11 = require("@heyanon-arp/sdk");
|
|
5269
|
-
var
|
|
5832
|
+
var import_chalk21 = __toESM(require("chalk"));
|
|
5270
5833
|
init_api();
|
|
5271
5834
|
function registerReceiptCommands(root) {
|
|
5272
5835
|
const cmd = root.command("receipt").description("Receipt envelopes \u2014 payee proposes, caller cosigns");
|
|
@@ -5286,6 +5849,15 @@ var POST_COMMIT_ERROR_CODES4 = /* @__PURE__ */ new Set([
|
|
|
5286
5849
|
"RECEIPT_COSIGN_AGENT_MISMATCH",
|
|
5287
5850
|
"RECEIPT_COSIGN_PURPOSE_INVALID",
|
|
5288
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",
|
|
5289
5861
|
// Settlement-side rejections from the receipt cosign path.
|
|
5290
5862
|
// Receipt-handler invokes
|
|
5291
5863
|
// `ReceiptCosignValidatorService.enqueueReleaseOp` AFTER the
|
|
@@ -5366,16 +5938,25 @@ function registerPropose2(parent) {
|
|
|
5366
5938
|
).option(
|
|
5367
5939
|
"--request-id <r>",
|
|
5368
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.'
|
|
5369
|
-
).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) => {
|
|
5370
5946
|
try {
|
|
5371
5947
|
await runPropose2(recipientDid, delegationId, requestHash, responseHash, opts);
|
|
5372
5948
|
} catch (err) {
|
|
5373
|
-
|
|
5949
|
+
emitActionError(err, cmd);
|
|
5374
5950
|
process.exitCode = 1;
|
|
5375
5951
|
}
|
|
5376
5952
|
});
|
|
5377
5953
|
}
|
|
5378
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
|
+
}
|
|
5379
5960
|
requireDid3("receipt propose", recipientDid, "<recipient-did>");
|
|
5380
5961
|
delegationId = requireUuidNormalised2("receipt propose", delegationId, "<delegation-id>");
|
|
5381
5962
|
if (opts.relId) opts.relId = requireUuidNormalised2("receipt propose", opts.relId, "--rel-id");
|
|
@@ -5388,7 +5969,10 @@ async function runPropose2(recipientDid, delegationId, requestHashArg, responseH
|
|
|
5388
5969
|
const sender = resolveSenderAgent("receipt propose", opts.server, opts.fromDid);
|
|
5389
5970
|
if (opts.autoHashes && !opts.relId) {
|
|
5390
5971
|
opts.relId = await resolveAutoRelId(api, sender, delegationId);
|
|
5391
|
-
|
|
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
|
+
);
|
|
5392
5976
|
}
|
|
5393
5977
|
if (opts.relId) {
|
|
5394
5978
|
await assertSenderIsReceiptPayee(api, sender, opts.relId, delegationId);
|
|
@@ -5401,8 +5985,9 @@ async function runPropose2(recipientDid, delegationId, requestHashArg, responseH
|
|
|
5401
5985
|
}
|
|
5402
5986
|
if (!opts.requestId) {
|
|
5403
5987
|
opts.requestId = await resolveAutoRequestId(api, sender, opts.relId, delegationId);
|
|
5404
|
-
|
|
5405
|
-
|
|
5988
|
+
progress(
|
|
5989
|
+
opts.json,
|
|
5990
|
+
import_chalk21.default.dim(
|
|
5406
5991
|
`[auto-request-id] resolved --request-id=${opts.requestId} (unique 'responded' work_log under this delegation; pass --request-id explicitly to override)`
|
|
5407
5992
|
)
|
|
5408
5993
|
);
|
|
@@ -5421,8 +6006,8 @@ async function runPropose2(recipientDid, delegationId, requestHashArg, responseH
|
|
|
5421
6006
|
}
|
|
5422
6007
|
requestHash = computed.requestHash;
|
|
5423
6008
|
responseHash = computed.responseHash;
|
|
5424
|
-
|
|
5425
|
-
|
|
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}`));
|
|
5426
6011
|
} else {
|
|
5427
6012
|
if (requestHashArg === void 0 || responseHashArg === void 0) {
|
|
5428
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)");
|
|
@@ -5442,17 +6027,35 @@ async function runPropose2(recipientDid, delegationId, requestHashArg, responseH
|
|
|
5442
6027
|
if (opts.deliverableHash) content.deliverable_hash = opts.deliverableHash;
|
|
5443
6028
|
if (usage) content.usage = usage;
|
|
5444
6029
|
const body = { type: "receipt", content };
|
|
5445
|
-
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
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}`));
|
|
5450
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
|
+
}
|
|
5451
6054
|
printIngestResult3(result);
|
|
5452
|
-
console.log(
|
|
5453
|
-
Receipt event hash: ${
|
|
5454
|
-
console.log(
|
|
5455
|
-
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}`));
|
|
5456
6059
|
}
|
|
5457
6060
|
async function assertSenderIsReceiptPayee(api, sender, relationshipId, delegationId) {
|
|
5458
6061
|
const signer = makeSigner(sender);
|
|
@@ -5590,7 +6193,7 @@ function registerCosign(parent) {
|
|
|
5590
6193
|
try {
|
|
5591
6194
|
await runCosign(relationshipId, delegationId, requestHashArg, responseHashArg, opts);
|
|
5592
6195
|
} catch (err) {
|
|
5593
|
-
|
|
6196
|
+
emitActionError(err, cmd);
|
|
5594
6197
|
process.exitCode = 1;
|
|
5595
6198
|
}
|
|
5596
6199
|
}
|
|
@@ -5599,7 +6202,7 @@ function registerCosign(parent) {
|
|
|
5599
6202
|
function loadSettlementSigFromFile(path, flagPrefix) {
|
|
5600
6203
|
let raw;
|
|
5601
6204
|
try {
|
|
5602
|
-
raw = (0,
|
|
6205
|
+
raw = (0, import_node_fs8.readFileSync)(path, "utf8");
|
|
5603
6206
|
} catch (err) {
|
|
5604
6207
|
throw new Error(`receipt cosign: failed to read ${flagPrefix} '${path}': ${err.message}`);
|
|
5605
6208
|
}
|
|
@@ -5846,28 +6449,28 @@ async function runCosign(relationshipId, delegationId, requestHashArg, responseH
|
|
|
5846
6449
|
content.notes_hash = cosignNotesHash;
|
|
5847
6450
|
}
|
|
5848
6451
|
const body = { type: "receipt", content };
|
|
5849
|
-
console.log(
|
|
5850
|
-
console.log(
|
|
5851
|
-
console.log(
|
|
5852
|
-
console.log(
|
|
5853
|
-
console.log(
|
|
5854
|
-
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}`));
|
|
5855
6458
|
if (cosignNotesHash !== null) {
|
|
5856
|
-
console.log(
|
|
6459
|
+
console.log(import_chalk21.default.dim(`Notes hash bound: ${cosignNotesHash}`));
|
|
5857
6460
|
} else if (opts.clearNotes) {
|
|
5858
|
-
console.log(
|
|
6461
|
+
console.log(import_chalk21.default.dim("Notes binding: cleared (--clear-notes)"));
|
|
5859
6462
|
}
|
|
5860
6463
|
if (opts.autoResolvePayeeSig) {
|
|
5861
6464
|
applyAutoResolvePayeeSig("receipt cosign", opts, resolved.payeeSettlement);
|
|
5862
6465
|
console.log(
|
|
5863
|
-
|
|
6466
|
+
import_chalk21.default.dim(`Auto-resolved payee sig from receipt.payeeSettlement (purpose=${resolved.payeeSettlement?.purpose}, expires_at=${resolved.payeeSettlement?.expires_at})`)
|
|
5864
6467
|
);
|
|
5865
6468
|
}
|
|
5866
6469
|
const settlementSigs = assembleSettlementSignaturesAttachment(opts);
|
|
5867
6470
|
const attachments = { co_signature: cosignature };
|
|
5868
6471
|
if (settlementSigs) {
|
|
5869
6472
|
attachments.settlement_signatures = settlementSigs;
|
|
5870
|
-
console.log(
|
|
6473
|
+
console.log(import_chalk21.default.dim(`Settlement signatures attached: purpose=${settlementSigs.purpose}`));
|
|
5871
6474
|
}
|
|
5872
6475
|
const result = await sendReceiptEnvelope({
|
|
5873
6476
|
api,
|
|
@@ -5899,9 +6502,7 @@ async function runCosign(relationshipId, delegationId, requestHashArg, responseH
|
|
|
5899
6502
|
}
|
|
5900
6503
|
}
|
|
5901
6504
|
function registerSendPayeeSig(parent) {
|
|
5902
|
-
parent.command("send-payee-sig").description(
|
|
5903
|
-
"Send the payee's settlement signature to the buyer via a settlement_signature envelope. Replaces /tmp/*.json coordination for cross-host escrow cycles."
|
|
5904
|
-
).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(
|
|
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(
|
|
5905
6506
|
"--receipt-event-hash <sha256:hex>",
|
|
5906
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."
|
|
5907
6508
|
).requiredOption(
|
|
@@ -5917,7 +6518,7 @@ function registerSendPayeeSig(parent) {
|
|
|
5917
6518
|
try {
|
|
5918
6519
|
await runSendPayeeSig(recipientDid, opts);
|
|
5919
6520
|
} catch (err) {
|
|
5920
|
-
|
|
6521
|
+
emitActionError(err, cmd);
|
|
5921
6522
|
process.exitCode = 1;
|
|
5922
6523
|
}
|
|
5923
6524
|
});
|
|
@@ -5966,17 +6567,17 @@ async function runSendPayeeSig(recipientDid, opts) {
|
|
|
5966
6567
|
expires_at: expiresAtSeconds,
|
|
5967
6568
|
...isPartial ? { payee_amount: opts.payeeAmount } : {}
|
|
5968
6569
|
};
|
|
5969
|
-
console.log(
|
|
5970
|
-
console.log(
|
|
5971
|
-
console.log(
|
|
5972
|
-
console.log(
|
|
5973
|
-
console.log(
|
|
5974
|
-
console.log(
|
|
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}`));
|
|
5975
6576
|
if (isPartial) {
|
|
5976
|
-
console.log(
|
|
6577
|
+
console.log(import_chalk21.default.dim(`Payee amount: ${opts.payeeAmount}`));
|
|
5977
6578
|
}
|
|
5978
|
-
console.log(
|
|
5979
|
-
console.log(
|
|
6579
|
+
console.log(import_chalk21.default.dim(`Settlement pubkey: ${sigFile.settlement_pubkey}`));
|
|
6580
|
+
console.log(import_chalk21.default.dim(`Expires at: ${expiresAtSeconds}`));
|
|
5980
6581
|
const result = await sendSettlementSignatureEnvelope({
|
|
5981
6582
|
api,
|
|
5982
6583
|
sender,
|
|
@@ -6010,7 +6611,7 @@ async function sendSettlementSignatureEnvelope(args) {
|
|
|
6010
6611
|
identitySecretKey: signer.identitySecretKey
|
|
6011
6612
|
});
|
|
6012
6613
|
if (args.verbose) {
|
|
6013
|
-
console.log(
|
|
6614
|
+
console.log(import_chalk21.default.bold("\nEnvelope (pre-send):"));
|
|
6014
6615
|
console.log(formatJson(envelope));
|
|
6015
6616
|
}
|
|
6016
6617
|
try {
|
|
@@ -6047,7 +6648,7 @@ async function sendReceiptEnvelope(args) {
|
|
|
6047
6648
|
attachments: args.attachments
|
|
6048
6649
|
});
|
|
6049
6650
|
if (args.verbose) {
|
|
6050
|
-
console.log(
|
|
6651
|
+
console.log(import_chalk21.default.bold("\nEnvelope (pre-send):"));
|
|
6051
6652
|
console.log(formatJson(envelope));
|
|
6052
6653
|
}
|
|
6053
6654
|
try {
|
|
@@ -6106,12 +6707,12 @@ async function resolveCosignTargets(cmdName, api, signer, args) {
|
|
|
6106
6707
|
};
|
|
6107
6708
|
}
|
|
6108
6709
|
function printIngestResult3(result) {
|
|
6109
|
-
console.log(
|
|
6110
|
-
console.log(`${
|
|
6111
|
-
console.log(`${
|
|
6112
|
-
console.log(`${
|
|
6113
|
-
console.log(`${
|
|
6114
|
-
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)}`);
|
|
6115
6716
|
}
|
|
6116
6717
|
function parseVerdict(cmdName, raw) {
|
|
6117
6718
|
if (raw === void 0 || raw === "") return "accepted";
|
|
@@ -6164,7 +6765,7 @@ function requireDid3(cmdName, did, label) {
|
|
|
6164
6765
|
}
|
|
6165
6766
|
|
|
6166
6767
|
// src/commands/receipts.ts
|
|
6167
|
-
var
|
|
6768
|
+
var import_chalk22 = __toESM(require("chalk"));
|
|
6168
6769
|
init_api();
|
|
6169
6770
|
var ALLOWED_STATES3 = /* @__PURE__ */ new Set(["proposed", "cosigned"]);
|
|
6170
6771
|
function registerReceiptsCommand(root) {
|
|
@@ -6193,9 +6794,9 @@ async function runReceipts(relationshipId, opts) {
|
|
|
6193
6794
|
const api = new ArpApiClient(opts.server);
|
|
6194
6795
|
const sender = resolveSenderAgent("receipts", opts.server, opts.fromDid);
|
|
6195
6796
|
if (!opts.json) {
|
|
6196
|
-
console.log(
|
|
6197
|
-
console.log(
|
|
6198
|
-
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}`));
|
|
6199
6800
|
}
|
|
6200
6801
|
const query = { limit };
|
|
6201
6802
|
if (state) query.state = state;
|
|
@@ -6208,7 +6809,7 @@ async function runReceipts(relationshipId, opts) {
|
|
|
6208
6809
|
return;
|
|
6209
6810
|
}
|
|
6210
6811
|
if (rows.length === 0) {
|
|
6211
|
-
console.log(
|
|
6812
|
+
console.log(import_chalk22.default.dim("\n(no receipts for this relationship)"));
|
|
6212
6813
|
return;
|
|
6213
6814
|
}
|
|
6214
6815
|
console.log("");
|
|
@@ -6226,28 +6827,28 @@ async function runReceipts(relationshipId, opts) {
|
|
|
6226
6827
|
}));
|
|
6227
6828
|
}
|
|
6228
6829
|
const lastId = rows[rows.length - 1].id;
|
|
6229
|
-
console.log(
|
|
6830
|
+
console.log(import_chalk22.default.dim(`
|
|
6230
6831
|
${rows.length} receipt row(s). Paginate with --after ${lastId}.`));
|
|
6231
6832
|
}
|
|
6232
6833
|
function formatReceiptLine(r, selfDid, opts = {}) {
|
|
6233
6834
|
const delegationPart = opts.fullIds ? r.delegationId : idHead3(r.delegationId);
|
|
6234
6835
|
const requestHashPart = opts.fullIds ? r.requestHash : hashHead3(r.requestHash);
|
|
6235
|
-
const id =
|
|
6836
|
+
const id = import_chalk22.default.bold(`${delegationPart}/${requestHashPart}`);
|
|
6236
6837
|
const state = colorState3(r.state).padEnd(stateColumnWidth3());
|
|
6237
6838
|
const callerHead = opts.fullIds ? r.callerDid : didHead4(r.callerDid);
|
|
6238
6839
|
const payeeHead = opts.fullIds ? r.payeeDid : didHead4(r.payeeDid);
|
|
6239
|
-
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")}`;
|
|
6240
6841
|
const verdict = formatVerdict(r);
|
|
6241
6842
|
const responseTail = opts.fullIds ? `
|
|
6242
|
-
${
|
|
6843
|
+
${import_chalk22.default.dim("responseHash:")} ${import_chalk22.default.cyan(r.responseHash)}` : "";
|
|
6243
6844
|
return `${id} ${state} ${direction} ${verdict}${responseTail}`;
|
|
6244
6845
|
}
|
|
6245
6846
|
function colorState3(s) {
|
|
6246
6847
|
switch (s) {
|
|
6247
6848
|
case "proposed":
|
|
6248
|
-
return
|
|
6849
|
+
return import_chalk22.default.yellow("proposed");
|
|
6249
6850
|
case "cosigned":
|
|
6250
|
-
return
|
|
6851
|
+
return import_chalk22.default.green("cosigned");
|
|
6251
6852
|
}
|
|
6252
6853
|
}
|
|
6253
6854
|
function stateColumnWidth3() {
|
|
@@ -6257,11 +6858,11 @@ function formatVerdict(r) {
|
|
|
6257
6858
|
const final = r.verdictFinal ?? r.verdictProposed;
|
|
6258
6859
|
switch (final) {
|
|
6259
6860
|
case "accepted":
|
|
6260
|
-
return
|
|
6861
|
+
return import_chalk22.default.green("accepted");
|
|
6261
6862
|
case "accepted_with_notes":
|
|
6262
|
-
return
|
|
6863
|
+
return import_chalk22.default.yellow("accepted_with_notes");
|
|
6263
6864
|
case "rejected":
|
|
6264
|
-
return
|
|
6865
|
+
return import_chalk22.default.red("rejected");
|
|
6265
6866
|
}
|
|
6266
6867
|
}
|
|
6267
6868
|
function idHead3(id) {
|
|
@@ -6294,9 +6895,9 @@ function parseLimit7(raw) {
|
|
|
6294
6895
|
|
|
6295
6896
|
// src/commands/register.ts
|
|
6296
6897
|
var import_node_crypto = require("crypto");
|
|
6297
|
-
var
|
|
6898
|
+
var import_node_fs9 = require("fs");
|
|
6298
6899
|
var import_sdk12 = require("@heyanon-arp/sdk");
|
|
6299
|
-
var
|
|
6900
|
+
var import_chalk23 = __toESM(require("chalk"));
|
|
6300
6901
|
var import_prompts2 = __toESM(require("prompts"));
|
|
6301
6902
|
init_api();
|
|
6302
6903
|
init_paths();
|
|
@@ -6356,17 +6957,17 @@ async function runRegister(opts) {
|
|
|
6356
6957
|
const publication = parsePublicationMode(opts.publication ?? "active");
|
|
6357
6958
|
assertJsonRequiresYes(opts);
|
|
6358
6959
|
const api = new ArpApiClient(opts.server);
|
|
6359
|
-
if (!opts.json) console.log(
|
|
6960
|
+
if (!opts.json) console.log(import_chalk23.default.dim(`Server: ${api.serverUrl}`));
|
|
6360
6961
|
if (!opts.json) {
|
|
6361
6962
|
warnIfAgentsAlreadyRegistered(opts.server);
|
|
6362
6963
|
warnIfOrphanHomesPresent();
|
|
6363
6964
|
}
|
|
6364
6965
|
const keys = opts.fromKeys ? loadKeysFromFile(opts.fromKeys) : freshKeys();
|
|
6365
6966
|
const did = (0, import_sdk12.formatDid)(keys.identityPublicKey);
|
|
6366
|
-
if (!opts.json) console.log(
|
|
6967
|
+
if (!opts.json) console.log(import_chalk23.default.dim(`DID will be: ${did}`));
|
|
6367
6968
|
const answers = await mergeAnswers(opts);
|
|
6368
6969
|
const scryptSalt = (0, import_node_crypto.randomBytes)(16);
|
|
6369
|
-
if (!opts.json) console.log(
|
|
6970
|
+
if (!opts.json) console.log(import_chalk23.default.dim("Deriving scrypt key, this may take a moment..."));
|
|
6370
6971
|
const scryptKey = (0, import_sdk12.deriveScryptKey)(answers.password, new Uint8Array(scryptSalt));
|
|
6371
6972
|
const challenge = await api.issueChallenge("register");
|
|
6372
6973
|
const challengeBytes = base64UrlNoPadDecode(challenge.challengeB64);
|
|
@@ -6445,13 +7046,13 @@ async function runRegister(opts) {
|
|
|
6445
7046
|
try {
|
|
6446
7047
|
recordHome(arpHomeDir());
|
|
6447
7048
|
} catch (registryErr) {
|
|
6448
|
-
if (!opts.json) console.log(
|
|
7049
|
+
if (!opts.json) console.log(import_chalk23.default.dim(`(homes registry write failed: ${registryErr.message})`));
|
|
6449
7050
|
}
|
|
6450
7051
|
if (!opts.json) {
|
|
6451
|
-
console.log(
|
|
6452
|
-
console.log(`${
|
|
6453
|
-
console.log(`${
|
|
6454
|
-
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:"));
|
|
6455
7056
|
console.log(formatJson(result.didDocument));
|
|
6456
7057
|
}
|
|
6457
7058
|
const decision = decideAutoPublish({ publication, defaultEndpointUrl: answers.defaultEndpointUrl });
|
|
@@ -6461,33 +7062,33 @@ async function runRegister(opts) {
|
|
|
6461
7062
|
const signer = makeSignerFromSecret(keys.identitySecretKey, result.did);
|
|
6462
7063
|
await api.publishAgent(result.did, signer);
|
|
6463
7064
|
publicationOutcome = "published";
|
|
6464
|
-
if (!opts.json) console.log(
|
|
7065
|
+
if (!opts.json) console.log(import_chalk23.default.green(`
|
|
6465
7066
|
\u2713 Published \u2014 discoverable now via \`heyarp agents\`.`));
|
|
6466
7067
|
} catch (err) {
|
|
6467
7068
|
publicationOutcome = `failed: ${err.message}`;
|
|
6468
7069
|
if (!opts.json)
|
|
6469
7070
|
console.log(
|
|
6470
|
-
|
|
7071
|
+
import_chalk23.default.yellow(`
|
|
6471
7072
|
\u26A0 Auto-publish failed (${err.message}). Agent saved as DRAFT \u2014 run \`heyarp publish ${result.did}\` to make it discoverable.`)
|
|
6472
7073
|
);
|
|
6473
7074
|
}
|
|
6474
7075
|
} else if (decision === "skip-no-endpoint") {
|
|
6475
7076
|
publicationOutcome = "skip-no-endpoint";
|
|
6476
7077
|
if (!opts.json) {
|
|
6477
|
-
console.log(
|
|
7078
|
+
console.log(import_chalk23.default.dim(`
|
|
6478
7079
|
Auto-publish skipped: no --endpoint-url supplied (buyer-only registration). Agent is saved as DRAFT.`));
|
|
6479
|
-
console.log(
|
|
6480
|
-
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}\`.`));
|
|
6481
7082
|
}
|
|
6482
7083
|
} else {
|
|
6483
7084
|
publicationOutcome = "draft";
|
|
6484
7085
|
if (!opts.json) {
|
|
6485
|
-
console.log(
|
|
7086
|
+
console.log(import_chalk23.default.dim(`
|
|
6486
7087
|
Publication mode: DRAFT. Agent is registered but NOT discoverable via \`heyarp agents\`.`));
|
|
6487
|
-
console.log(
|
|
7088
|
+
console.log(import_chalk23.default.dim(`Run \`heyarp publish ${result.did}\` when ready to appear in the catalog.`));
|
|
6488
7089
|
}
|
|
6489
7090
|
}
|
|
6490
|
-
if (!opts.json) console.log(
|
|
7091
|
+
if (!opts.json) console.log(import_chalk23.default.dim(`
|
|
6491
7092
|
Local state saved to ${arpHomeDir()}/agents.json (mode 0600).`));
|
|
6492
7093
|
if (opts.json) {
|
|
6493
7094
|
console.log(
|
|
@@ -6572,7 +7173,7 @@ async function mergeAnswers(opts) {
|
|
|
6572
7173
|
}
|
|
6573
7174
|
const prompted = promptDefs.length > 0 ? await (0, import_prompts2.default)(promptDefs, {
|
|
6574
7175
|
onCancel: () => {
|
|
6575
|
-
console.log(
|
|
7176
|
+
console.log(import_chalk23.default.yellow("\nAborted."));
|
|
6576
7177
|
process.exit(130);
|
|
6577
7178
|
}
|
|
6578
7179
|
}) : {};
|
|
@@ -6595,16 +7196,16 @@ function warnIfOrphanHomesPresent() {
|
|
|
6595
7196
|
try {
|
|
6596
7197
|
others = listHomes().filter((h) => h.path !== current);
|
|
6597
7198
|
} catch (registryErr) {
|
|
6598
|
-
console.log(
|
|
7199
|
+
console.log(import_chalk23.default.dim(`(homes registry unreadable, skipping orphan-home check: ${registryErr.message})`));
|
|
6599
7200
|
return;
|
|
6600
7201
|
}
|
|
6601
7202
|
if (others.length === 0) return;
|
|
6602
|
-
const list = others.map((h) => ` \u2022 ${
|
|
6603
|
-
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(`
|
|
6604
7205
|
\u26A0 HEYARP_HOME is unset, but other agent homes are registered on this machine:`));
|
|
6605
7206
|
console.log(list);
|
|
6606
7207
|
console.log(
|
|
6607
|
-
|
|
7208
|
+
import_chalk23.default.dim(
|
|
6608
7209
|
` Registering will create a NEW agent under ${current}.
|
|
6609
7210
|
If you meant to add to an existing home, abort (Ctrl-C) and re-run with:
|
|
6610
7211
|
HEYARP_HOME=<path> heyarp register \u2026
|
|
@@ -6617,11 +7218,11 @@ function warnIfAgentsAlreadyRegistered(serverOverride) {
|
|
|
6617
7218
|
const targetServer = resolveServerUrl(serverOverride);
|
|
6618
7219
|
const existing = listAgents().filter((row) => row.serverUrl === targetServer);
|
|
6619
7220
|
if (existing.length === 0) return;
|
|
6620
|
-
const list = existing.map((row) => ` \u2022 ${
|
|
6621
|
-
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:"));
|
|
6622
7223
|
console.log(list);
|
|
6623
7224
|
console.log(
|
|
6624
|
-
|
|
7225
|
+
import_chalk23.default.dim(
|
|
6625
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"
|
|
6626
7227
|
)
|
|
6627
7228
|
);
|
|
@@ -6641,12 +7242,12 @@ function freshKeys() {
|
|
|
6641
7242
|
};
|
|
6642
7243
|
}
|
|
6643
7244
|
function loadKeysFromFile(path) {
|
|
6644
|
-
if (!(0,
|
|
7245
|
+
if (!(0, import_node_fs9.existsSync)(path)) {
|
|
6645
7246
|
throw new Error(`--from-keys: file not found at ${path}`);
|
|
6646
7247
|
}
|
|
6647
7248
|
let parsed;
|
|
6648
7249
|
try {
|
|
6649
|
-
parsed = JSON.parse((0,
|
|
7250
|
+
parsed = JSON.parse((0, import_node_fs9.readFileSync)(path, "utf8"));
|
|
6650
7251
|
} catch (err) {
|
|
6651
7252
|
throw new Error(`--from-keys: ${path} is not valid JSON: ${err.message}`);
|
|
6652
7253
|
}
|
|
@@ -6688,7 +7289,7 @@ function makeSignerFromSecret(identitySecretKey, did) {
|
|
|
6688
7289
|
}
|
|
6689
7290
|
|
|
6690
7291
|
// src/commands/relationships.ts
|
|
6691
|
-
var
|
|
7292
|
+
var import_chalk24 = __toESM(require("chalk"));
|
|
6692
7293
|
init_api();
|
|
6693
7294
|
var ALLOWED_STATES4 = /* @__PURE__ */ new Set(["pending", "active", "paused", "closed"]);
|
|
6694
7295
|
function registerRelationshipsCommand(root) {
|
|
@@ -6708,25 +7309,25 @@ async function runRelationships(positionalDid, opts) {
|
|
|
6708
7309
|
const local = explicitDid !== void 0 ? loadAgentOrThrow(opts.server, explicitDid) : resolveSenderAgent("relationships", opts.server, void 0);
|
|
6709
7310
|
const did = local.did;
|
|
6710
7311
|
const api = new ArpApiClient(opts.server);
|
|
6711
|
-
console.log(
|
|
6712
|
-
console.log(
|
|
7312
|
+
console.log(import_chalk24.default.dim(`Server: ${api.serverUrl}`));
|
|
7313
|
+
console.log(import_chalk24.default.dim(`Signer: ${local.did}`));
|
|
6713
7314
|
const query = { limit };
|
|
6714
7315
|
if (state) query.state = state;
|
|
6715
7316
|
const signer = makeSigner(local);
|
|
6716
7317
|
const rows = await api.listRelationships(did, signer, query);
|
|
6717
7318
|
if (rows.length === 0) {
|
|
6718
|
-
console.log(
|
|
7319
|
+
console.log(import_chalk24.default.dim("\n(no relationships)"));
|
|
6719
7320
|
return;
|
|
6720
7321
|
}
|
|
6721
7322
|
console.log("");
|
|
6722
7323
|
console.log(formatRelationshipsTable(rows, did));
|
|
6723
7324
|
if (opts.verbose) {
|
|
6724
|
-
console.log(
|
|
7325
|
+
console.log(import_chalk24.default.bold("\nFull relationships:"));
|
|
6725
7326
|
for (const r of rows) {
|
|
6726
7327
|
console.log(formatJson(r));
|
|
6727
7328
|
}
|
|
6728
7329
|
}
|
|
6729
|
-
console.log(
|
|
7330
|
+
console.log(import_chalk24.default.dim(`
|
|
6730
7331
|
${rows.length} relationship(s).`));
|
|
6731
7332
|
}
|
|
6732
7333
|
function formatRelationshipsTable(rows, selfDid) {
|
|
@@ -6734,7 +7335,7 @@ function formatRelationshipsTable(rows, selfDid) {
|
|
|
6734
7335
|
const data = rows.map((r) => [r.relationshipId, otherPair(r, selfDid), r.state, r.lastEventAt ?? "(none)", String(r.lastEventIndex)]);
|
|
6735
7336
|
const widths = header.map((h, i) => Math.max(h.length, ...data.map((row) => row[i].length)));
|
|
6736
7337
|
const pad = (cells) => cells.map((c, i) => c.padEnd(widths[i])).join(" ");
|
|
6737
|
-
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");
|
|
6738
7339
|
}
|
|
6739
7340
|
function otherPair(r, selfDid) {
|
|
6740
7341
|
if (r.pairDidA === selfDid) return r.pairDidB;
|
|
@@ -6759,7 +7360,7 @@ function parseLimit8(raw) {
|
|
|
6759
7360
|
|
|
6760
7361
|
// src/commands/rotate.ts
|
|
6761
7362
|
var import_sdk13 = require("@heyanon-arp/sdk");
|
|
6762
|
-
var
|
|
7363
|
+
var import_chalk25 = __toESM(require("chalk"));
|
|
6763
7364
|
var import_prompts3 = __toESM(require("prompts"));
|
|
6764
7365
|
init_api();
|
|
6765
7366
|
var ROTATION_REASONS = ["scheduled", "compromise", "lost_device", "other"];
|
|
@@ -6777,8 +7378,8 @@ async function runRotate(did, opts) {
|
|
|
6777
7378
|
throw new Error("rotate: local state is missing ownerId / currentAttestationId. State predates the rotation flow \u2014 please re-register.");
|
|
6778
7379
|
}
|
|
6779
7380
|
const api = new ArpApiClient(opts.server);
|
|
6780
|
-
console.log(
|
|
6781
|
-
console.log(
|
|
7381
|
+
console.log(import_chalk25.default.dim(`Server: ${api.serverUrl}`));
|
|
7382
|
+
console.log(import_chalk25.default.dim(`Rotating: ${local.did}`));
|
|
6782
7383
|
if (!opts.yes) {
|
|
6783
7384
|
const confirm = await (0, import_prompts3.default)({
|
|
6784
7385
|
type: "confirm",
|
|
@@ -6787,7 +7388,7 @@ async function runRotate(did, opts) {
|
|
|
6787
7388
|
initial: false
|
|
6788
7389
|
});
|
|
6789
7390
|
if (!confirm.go) {
|
|
6790
|
-
console.log(
|
|
7391
|
+
console.log(import_chalk25.default.yellow("Aborted."));
|
|
6791
7392
|
return;
|
|
6792
7393
|
}
|
|
6793
7394
|
}
|
|
@@ -6863,22 +7464,22 @@ async function runRotate(did, opts) {
|
|
|
6863
7464
|
pendingRotation: void 0
|
|
6864
7465
|
});
|
|
6865
7466
|
} catch (err) {
|
|
6866
|
-
console.error(
|
|
6867
|
-
console.error(
|
|
6868
|
-
console.error(` ${
|
|
6869
|
-
console.error(` ${
|
|
6870
|
-
console.error(` ${
|
|
6871
|
-
console.error(
|
|
6872
|
-
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}`));
|
|
6873
7474
|
throw err;
|
|
6874
7475
|
}
|
|
6875
|
-
console.log(
|
|
6876
|
-
console.log(`${
|
|
6877
|
-
console.log(`${
|
|
6878
|
-
console.log(`${
|
|
6879
|
-
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:"));
|
|
6880
7481
|
console.log(formatJson(updated));
|
|
6881
|
-
console.log(
|
|
7482
|
+
console.log(import_chalk25.default.dim("\nLocal state updated; old private key is no longer valid."));
|
|
6882
7483
|
}
|
|
6883
7484
|
function base64UrlNoPadDecode2(s) {
|
|
6884
7485
|
const replaced = s.replace(/-/g, "+").replace(/_/g, "/");
|
|
@@ -6888,7 +7489,7 @@ function base64UrlNoPadDecode2(s) {
|
|
|
6888
7489
|
|
|
6889
7490
|
// src/commands/send-handshake.ts
|
|
6890
7491
|
var import_sdk14 = require("@heyanon-arp/sdk");
|
|
6891
|
-
var
|
|
7492
|
+
var import_chalk26 = __toESM(require("chalk"));
|
|
6892
7493
|
init_api();
|
|
6893
7494
|
function registerSendHandshakeCommand(root) {
|
|
6894
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) => {
|
|
@@ -6901,10 +7502,10 @@ async function runSendHandshake(recipientDid, opts) {
|
|
|
6901
7502
|
}
|
|
6902
7503
|
const ttlSeconds = parseTtl5(opts.ttl);
|
|
6903
7504
|
const api = new ArpApiClient(opts.server);
|
|
6904
|
-
console.log(
|
|
7505
|
+
console.log(import_chalk26.default.dim(`Server: ${api.serverUrl}`));
|
|
6905
7506
|
const sender = resolveSenderAgent("send-handshake", opts.server, opts.fromDid);
|
|
6906
|
-
console.log(
|
|
6907
|
-
console.log(
|
|
7507
|
+
console.log(import_chalk26.default.dim(`Sender: ${sender.did}`));
|
|
7508
|
+
console.log(import_chalk26.default.dim(`Recipient: ${recipientDid}`));
|
|
6908
7509
|
const content = {};
|
|
6909
7510
|
if (opts.greeting) content.greeting = opts.greeting;
|
|
6910
7511
|
if (opts.intent) content.intent = opts.intent;
|
|
@@ -6930,28 +7531,28 @@ async function runSendHandshake(recipientDid, opts) {
|
|
|
6930
7531
|
identitySecretKey: signer.identitySecretKey
|
|
6931
7532
|
});
|
|
6932
7533
|
if (opts.verbose) {
|
|
6933
|
-
console.log(
|
|
7534
|
+
console.log(import_chalk26.default.bold("\nEnvelope (pre-send):"));
|
|
6934
7535
|
console.log(formatJson(envelope));
|
|
6935
7536
|
}
|
|
6936
7537
|
const result = await api.ingest(envelope);
|
|
6937
7538
|
updateAgentLocal(opts.server, sender.did, { lastSenderSequence: nextSequence });
|
|
6938
|
-
console.log(
|
|
6939
|
-
console.log(`${
|
|
6940
|
-
console.log(`${
|
|
6941
|
-
console.log(`${
|
|
6942
|
-
console.log(`${
|
|
6943
|
-
console.log(`${
|
|
6944
|
-
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)}`);
|
|
6945
7546
|
if (result.prevServerEventHash) {
|
|
6946
|
-
console.log(`${
|
|
7547
|
+
console.log(`${import_chalk26.default.bold("Prev server event hash")}: ${import_chalk26.default.cyan(result.prevServerEventHash)}`);
|
|
6947
7548
|
} else {
|
|
6948
|
-
console.log(`${
|
|
7549
|
+
console.log(`${import_chalk26.default.bold("Prev server event hash")}: ${import_chalk26.default.dim("(null \u2014 first event of this relationship)")}`);
|
|
6949
7550
|
}
|
|
6950
7551
|
if (opts.verbose) {
|
|
6951
|
-
console.log(
|
|
7552
|
+
console.log(import_chalk26.default.bold("\nFull server response:"));
|
|
6952
7553
|
console.log(formatJson(result));
|
|
6953
7554
|
}
|
|
6954
|
-
console.log(
|
|
7555
|
+
console.log(import_chalk26.default.dim(`
|
|
6955
7556
|
Local sender_sequence advanced to ${nextSequence}.`));
|
|
6956
7557
|
}
|
|
6957
7558
|
function parseTtl5(raw) {
|
|
@@ -6968,7 +7569,7 @@ function isDid(s) {
|
|
|
6968
7569
|
|
|
6969
7570
|
// src/commands/send-handshake-response.ts
|
|
6970
7571
|
var import_sdk15 = require("@heyanon-arp/sdk");
|
|
6971
|
-
var
|
|
7572
|
+
var import_chalk27 = __toESM(require("chalk"));
|
|
6972
7573
|
init_api();
|
|
6973
7574
|
var ALLOWED_DECISIONS = /* @__PURE__ */ new Set(["accept", "decline"]);
|
|
6974
7575
|
function registerSendHandshakeResponseCommand(root) {
|
|
@@ -6979,7 +7580,11 @@ function registerSendHandshakeResponseCommand(root) {
|
|
|
6979
7580
|
// would fire for the accept path too; validate manually
|
|
6980
7581
|
// after decision is parsed.
|
|
6981
7582
|
`When --decision=decline: required reason code (one of: ${import_sdk15.DECLINE_REASONS.join(", ")}). Carried in body.content.reason.`
|
|
6982
|
-
).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(
|
|
6983
7588
|
"--force",
|
|
6984
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.",
|
|
6985
7590
|
false
|
|
@@ -6988,6 +7593,11 @@ function registerSendHandshakeResponseCommand(root) {
|
|
|
6988
7593
|
});
|
|
6989
7594
|
}
|
|
6990
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
|
+
}
|
|
6991
7601
|
if (!isDid2(recipientDid)) {
|
|
6992
7602
|
throw new Error(`send-handshake-response: <recipient-did> must look like 'did:arp:...' (got '${recipientDid}')`);
|
|
6993
7603
|
}
|
|
@@ -7000,11 +7610,11 @@ async function runSendHandshakeResponse(recipientDid, opts) {
|
|
|
7000
7610
|
declinePayload = detail ? { reason, reasonDetail: detail } : { reason };
|
|
7001
7611
|
}
|
|
7002
7612
|
const api = new ArpApiClient(opts.server);
|
|
7003
|
-
|
|
7613
|
+
progress(opts.json, import_chalk27.default.dim(`Server: ${api.serverUrl}`));
|
|
7004
7614
|
const sender = resolveSenderAgent("send-handshake-response", opts.server, opts.fromDid);
|
|
7005
|
-
|
|
7006
|
-
|
|
7007
|
-
|
|
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}`));
|
|
7008
7618
|
const signer = makeSigner(sender);
|
|
7009
7619
|
if (!opts.force) {
|
|
7010
7620
|
try {
|
|
@@ -7017,12 +7627,29 @@ async function runSendHandshakeResponse(recipientDid, opts) {
|
|
|
7017
7627
|
const events = await api.listEvents(existing.relationshipId, signer);
|
|
7018
7628
|
const previousResponseFromMe = events.find((e) => e.senderDid === sender.did && e.type === "handshake_response");
|
|
7019
7629
|
if (previousResponseFromMe) {
|
|
7020
|
-
|
|
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(`
|
|
7021
7647
|
[--idempotency] Relationship ${existing.relationshipId} with ${recipientDid} is already 'active'.`));
|
|
7022
|
-
|
|
7023
|
-
|
|
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).`)
|
|
7024
7651
|
);
|
|
7025
|
-
|
|
7652
|
+
progress(opts.json, import_chalk27.default.dim(`Last event index: ${existing.lastEventIndex}, last server event hash: ${existing.lastServerEventHash ?? "(none)"}`));
|
|
7026
7653
|
return;
|
|
7027
7654
|
}
|
|
7028
7655
|
throw new Error(
|
|
@@ -7033,7 +7660,7 @@ async function runSendHandshakeResponse(recipientDid, opts) {
|
|
|
7033
7660
|
if (probeErr instanceof Error && /CLOSED|terminated|already 'active' from a previous ACCEPT|original initiator/i.test(probeErr.message)) {
|
|
7034
7661
|
throw probeErr;
|
|
7035
7662
|
}
|
|
7036
|
-
|
|
7663
|
+
progress(opts.json, import_chalk27.default.dim(`(idempotency probe failed; proceeding anyway: ${probeErr.message})`));
|
|
7037
7664
|
}
|
|
7038
7665
|
}
|
|
7039
7666
|
const content = { decision };
|
|
@@ -7063,28 +7690,43 @@ async function runSendHandshakeResponse(recipientDid, opts) {
|
|
|
7063
7690
|
identitySecretKey: signer.identitySecretKey
|
|
7064
7691
|
});
|
|
7065
7692
|
if (opts.verbose) {
|
|
7066
|
-
console.log(
|
|
7693
|
+
console.log(import_chalk27.default.bold("\nEnvelope (pre-send):"));
|
|
7067
7694
|
console.log(formatJson(envelope));
|
|
7068
7695
|
}
|
|
7069
7696
|
const result = await api.ingest(envelope);
|
|
7070
7697
|
updateAgentLocal(opts.server, sender.did, { lastSenderSequence: nextSequence });
|
|
7071
|
-
|
|
7072
|
-
|
|
7073
|
-
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
|
|
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)}`);
|
|
7078
7720
|
if (result.prevServerEventHash) {
|
|
7079
|
-
console.log(`${
|
|
7721
|
+
console.log(`${import_chalk27.default.bold("Prev server event hash")}: ${import_chalk27.default.cyan(result.prevServerEventHash)}`);
|
|
7080
7722
|
} else {
|
|
7081
|
-
console.log(`${
|
|
7723
|
+
console.log(`${import_chalk27.default.bold("Prev server event hash")}: ${import_chalk27.default.dim("(null \u2014 first event of this relationship)")}`);
|
|
7082
7724
|
}
|
|
7083
7725
|
if (opts.verbose) {
|
|
7084
|
-
console.log(
|
|
7726
|
+
console.log(import_chalk27.default.bold("\nFull server response:"));
|
|
7085
7727
|
console.log(formatJson(result));
|
|
7086
7728
|
}
|
|
7087
|
-
console.log(
|
|
7729
|
+
console.log(import_chalk27.default.dim(`
|
|
7088
7730
|
Local sender_sequence advanced to ${nextSequence}.`));
|
|
7089
7731
|
}
|
|
7090
7732
|
function parseDecision(raw) {
|
|
@@ -7137,8 +7779,356 @@ async function findExistingRelationship(api, signer, senderDid, recipientDid) {
|
|
|
7137
7779
|
return void 0;
|
|
7138
7780
|
}
|
|
7139
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
|
+
|
|
7140
8130
|
// src/commands/watch.ts
|
|
7141
|
-
var
|
|
8131
|
+
var import_chalk29 = __toESM(require("chalk"));
|
|
7142
8132
|
init_api();
|
|
7143
8133
|
function registerWatchCommand(root) {
|
|
7144
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) => {
|
|
@@ -7149,9 +8139,9 @@ async function runWatch(relationshipId, opts) {
|
|
|
7149
8139
|
const local = resolveSenderAgent("watch", opts.server, opts.fromDid);
|
|
7150
8140
|
const api = new ArpApiClient(opts.server);
|
|
7151
8141
|
if (!opts.json) {
|
|
7152
|
-
console.log(
|
|
7153
|
-
console.log(
|
|
7154
|
-
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}`));
|
|
7155
8145
|
}
|
|
7156
8146
|
const controller = new AbortController();
|
|
7157
8147
|
let userAborted = false;
|
|
@@ -7170,7 +8160,7 @@ async function runWatch(relationshipId, opts) {
|
|
|
7170
8160
|
}
|
|
7171
8161
|
if (event.type === "heartbeat") continue;
|
|
7172
8162
|
if (event.type === "connected") {
|
|
7173
|
-
console.log(
|
|
8163
|
+
console.log(import_chalk29.default.green(`\u25CF stream open \u2014 watching ${relationshipId}`));
|
|
7174
8164
|
continue;
|
|
7175
8165
|
}
|
|
7176
8166
|
if (event.type === "envelope") {
|
|
@@ -7184,7 +8174,7 @@ async function runWatch(relationshipId, opts) {
|
|
|
7184
8174
|
}
|
|
7185
8175
|
continue;
|
|
7186
8176
|
}
|
|
7187
|
-
console.log(
|
|
8177
|
+
console.log(import_chalk29.default.dim(`(unknown event: ${event.type})`));
|
|
7188
8178
|
}
|
|
7189
8179
|
if (!userAborted) {
|
|
7190
8180
|
throw new Error(`watch ${relationshipId}: stream ended unexpectedly (server may have restarted, or the change stream errored). Re-run to reconnect.`);
|
|
@@ -7192,7 +8182,7 @@ async function runWatch(relationshipId, opts) {
|
|
|
7192
8182
|
} catch (err) {
|
|
7193
8183
|
const name = err.name;
|
|
7194
8184
|
if (name === "AbortError" || userAborted) {
|
|
7195
|
-
if (!opts.json) console.log(
|
|
8185
|
+
if (!opts.json) console.log(import_chalk29.default.dim("\nstream closed."));
|
|
7196
8186
|
return;
|
|
7197
8187
|
}
|
|
7198
8188
|
throw err;
|
|
@@ -7206,21 +8196,21 @@ function formatWatchLine(ev, selfDid, opts = {}) {
|
|
|
7206
8196
|
const type = ev.type.padEnd(20);
|
|
7207
8197
|
const direction = directionLabel2(ev, selfDid, opts);
|
|
7208
8198
|
const hash = opts.fullIds ? ev.serverEventHash : hashHead4(ev.serverEventHash);
|
|
7209
|
-
return `${
|
|
8199
|
+
return `${import_chalk29.default.dim(`[${ts}]`)} ${type} ${direction} ${import_chalk29.default.cyan(hash)}`;
|
|
7210
8200
|
}
|
|
7211
8201
|
function directionLabel2(ev, selfDid, opts = {}) {
|
|
7212
8202
|
const senderHead = opts.fullIds ? ev.senderDid : didHead5(ev.senderDid);
|
|
7213
8203
|
const recipientHead = opts.fullIds ? ev.recipientDid : didHead5(ev.recipientDid);
|
|
7214
|
-
if (ev.senderDid === selfDid) return `${
|
|
7215
|
-
if (ev.recipientDid === selfDid) return `${
|
|
7216
|
-
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)}`;
|
|
7217
8207
|
}
|
|
7218
8208
|
function didHead5(did) {
|
|
7219
8209
|
if (did.length <= 20) return did;
|
|
7220
8210
|
return `${did.slice(0, 20)}...`;
|
|
7221
8211
|
}
|
|
7222
8212
|
function hashHead4(hash) {
|
|
7223
|
-
if (!hash) return
|
|
8213
|
+
if (!hash) return import_chalk29.default.dim("(none)");
|
|
7224
8214
|
if (hash.length <= 14) return hash;
|
|
7225
8215
|
return `${hash.slice(0, 14)}...`;
|
|
7226
8216
|
}
|
|
@@ -7231,8 +8221,283 @@ function formatClock(iso) {
|
|
|
7231
8221
|
return `${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())}`;
|
|
7232
8222
|
}
|
|
7233
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
|
+
|
|
7234
8499
|
// src/commands/whoami.ts
|
|
7235
|
-
var
|
|
8500
|
+
var import_chalk31 = __toESM(require("chalk"));
|
|
7236
8501
|
init_api();
|
|
7237
8502
|
function registerWhoamiCommand(root) {
|
|
7238
8503
|
root.command("whoami").description(
|
|
@@ -7266,10 +8531,10 @@ function registerWhoamiCommand(root) {
|
|
|
7266
8531
|
if (opts.json) {
|
|
7267
8532
|
console.log(formatJson(localJson));
|
|
7268
8533
|
} else {
|
|
7269
|
-
console.log(
|
|
7270
|
-
console.log(` DID: ${
|
|
7271
|
-
console.log(` Settlement pubkey: ${
|
|
7272
|
-
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)}`);
|
|
7273
8538
|
console.log(` Key mode: ${local.keyMode}`);
|
|
7274
8539
|
if (local.name) console.log(` Name: ${local.name}`);
|
|
7275
8540
|
}
|
|
@@ -7281,24 +8546,24 @@ function registerWhoamiCommand(root) {
|
|
|
7281
8546
|
if (opts.json) {
|
|
7282
8547
|
console.log(formatJson({ local: localJson, server: agent }));
|
|
7283
8548
|
} else {
|
|
7284
|
-
console.log(
|
|
7285
|
-
console.log(
|
|
7286
|
-
console.log(` DID: ${
|
|
7287
|
-
console.log(` Settlement pubkey: ${
|
|
7288
|
-
console.log(` Identity pubkey: ${
|
|
7289
|
-
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:"));
|
|
7290
8555
|
console.log(formatJson(agent));
|
|
7291
8556
|
}
|
|
7292
8557
|
} catch (err) {
|
|
7293
|
-
|
|
8558
|
+
emitActionError(err, cmd);
|
|
7294
8559
|
process.exitCode = 1;
|
|
7295
8560
|
}
|
|
7296
8561
|
});
|
|
7297
8562
|
}
|
|
7298
8563
|
|
|
7299
8564
|
// src/commands/work.ts
|
|
7300
|
-
var
|
|
7301
|
-
var
|
|
8565
|
+
var import_sdk17 = require("@heyanon-arp/sdk");
|
|
8566
|
+
var import_chalk32 = __toESM(require("chalk"));
|
|
7302
8567
|
init_api();
|
|
7303
8568
|
function registerWorkCommands(root) {
|
|
7304
8569
|
const cmd = root.command("work").description("Work envelopes inside an ACCEPTED delegation: request / respond");
|
|
@@ -7340,17 +8605,17 @@ async function runRequest(recipientDid, delegationId, opts) {
|
|
|
7340
8605
|
params
|
|
7341
8606
|
};
|
|
7342
8607
|
const body = { type: "work_request", content };
|
|
7343
|
-
console.log(
|
|
7344
|
-
console.log(
|
|
7345
|
-
console.log(
|
|
7346
|
-
console.log(
|
|
7347
|
-
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}`));
|
|
7348
8613
|
const result = await sendWorkEnvelope({ api, sender, recipientDid, body, ttlSeconds, verbose: opts.verbose, server: opts.server });
|
|
7349
8614
|
printIngestResult4(result);
|
|
7350
|
-
console.log(
|
|
8615
|
+
console.log(import_chalk32.default.dim(`
|
|
7351
8616
|
The payee can reply with:`));
|
|
7352
|
-
console.log(
|
|
7353
|
-
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`));
|
|
7354
8619
|
}
|
|
7355
8620
|
function registerRespond(parent) {
|
|
7356
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(
|
|
@@ -7375,13 +8640,13 @@ async function runRespond(relationshipId, delegationId, requestId, opts) {
|
|
|
7375
8640
|
...responsePayload
|
|
7376
8641
|
};
|
|
7377
8642
|
const body = { type: "work_response", content };
|
|
7378
|
-
console.log(
|
|
7379
|
-
console.log(
|
|
7380
|
-
console.log(
|
|
7381
|
-
console.log(
|
|
7382
|
-
console.log(
|
|
7383
|
-
console.log(
|
|
7384
|
-
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"}`));
|
|
7385
8650
|
const result = await sendWorkEnvelope({ api, sender, recipientDid, body, ttlSeconds, verbose: opts.verbose, server: opts.server });
|
|
7386
8651
|
printIngestResult4(result);
|
|
7387
8652
|
}
|
|
@@ -7389,25 +8654,25 @@ async function sendWorkEnvelope(args) {
|
|
|
7389
8654
|
const nextSequence = (args.sender.lastSenderSequence ?? 0) + 1;
|
|
7390
8655
|
const protectedBlock = {
|
|
7391
8656
|
protocol_version: "arp/0.1",
|
|
7392
|
-
purpose:
|
|
7393
|
-
message_id: (0,
|
|
8657
|
+
purpose: import_sdk17.Purpose.ENVELOPE,
|
|
8658
|
+
message_id: (0, import_sdk17.uuidV4)(),
|
|
7394
8659
|
sender_did: args.sender.did,
|
|
7395
8660
|
recipient_did: args.recipientDid,
|
|
7396
8661
|
relationship_id: null,
|
|
7397
8662
|
sender_sequence: nextSequence,
|
|
7398
|
-
sender_nonce: (0,
|
|
7399
|
-
timestamp: (0,
|
|
7400
|
-
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),
|
|
7401
8666
|
delivery_id: null
|
|
7402
8667
|
};
|
|
7403
8668
|
const signer = makeSigner(args.sender);
|
|
7404
|
-
const envelope = (0,
|
|
8669
|
+
const envelope = (0, import_sdk17.signEnvelope)({
|
|
7405
8670
|
protected: protectedBlock,
|
|
7406
8671
|
body: args.body,
|
|
7407
8672
|
identitySecretKey: signer.identitySecretKey
|
|
7408
8673
|
});
|
|
7409
8674
|
if (args.verbose) {
|
|
7410
|
-
console.log(
|
|
8675
|
+
console.log(import_chalk32.default.bold("\nEnvelope (pre-send):"));
|
|
7411
8676
|
console.log(formatJson(envelope));
|
|
7412
8677
|
}
|
|
7413
8678
|
try {
|
|
@@ -7447,12 +8712,12 @@ async function resolveResponseRecipient(cmdName, api, signer, args) {
|
|
|
7447
8712
|
);
|
|
7448
8713
|
}
|
|
7449
8714
|
function printIngestResult4(result) {
|
|
7450
|
-
console.log(
|
|
7451
|
-
console.log(`${
|
|
7452
|
-
console.log(`${
|
|
7453
|
-
console.log(`${
|
|
7454
|
-
console.log(`${
|
|
7455
|
-
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)}`);
|
|
7456
8721
|
}
|
|
7457
8722
|
function parseJsonObject(cmdName, flagName, raw) {
|
|
7458
8723
|
let parsed;
|
|
@@ -7490,13 +8755,13 @@ function parseParamsInput(cmdName, opts) {
|
|
|
7490
8755
|
return parseJsonObject(cmdName, "--params", opts.params ?? "{}");
|
|
7491
8756
|
}
|
|
7492
8757
|
function readJsonObjectFile(cmdName, flagName, path) {
|
|
7493
|
-
const { existsSync:
|
|
7494
|
-
if (!
|
|
8758
|
+
const { existsSync: existsSync7, readFileSync: readFileSync9 } = require("fs");
|
|
8759
|
+
if (!existsSync7(path)) {
|
|
7495
8760
|
throw new Error(`${cmdName}: ${flagName} file not found at ${path}`);
|
|
7496
8761
|
}
|
|
7497
8762
|
let raw;
|
|
7498
8763
|
try {
|
|
7499
|
-
raw =
|
|
8764
|
+
raw = readFileSync9(path, "utf8");
|
|
7500
8765
|
} catch (err) {
|
|
7501
8766
|
const detail = err instanceof Error ? err.message : String(err);
|
|
7502
8767
|
throw new Error(`${cmdName}: failed to read ${flagName} (${path}): ${detail}`);
|
|
@@ -7544,7 +8809,7 @@ function parseTtl7(cmdName, raw) {
|
|
|
7544
8809
|
}
|
|
7545
8810
|
function parseRequestId(cmdName, raw) {
|
|
7546
8811
|
if (raw === void 0 || raw === "") {
|
|
7547
|
-
return (0,
|
|
8812
|
+
return (0, import_sdk17.uuidV4)();
|
|
7548
8813
|
}
|
|
7549
8814
|
if (raw.length === 0) {
|
|
7550
8815
|
throw new Error(`${cmdName}: --request-id must be a non-empty string`);
|
|
@@ -7565,7 +8830,7 @@ function requireDid4(cmdName, did, label) {
|
|
|
7565
8830
|
}
|
|
7566
8831
|
|
|
7567
8832
|
// src/commands/work-list.ts
|
|
7568
|
-
var
|
|
8833
|
+
var import_chalk33 = __toESM(require("chalk"));
|
|
7569
8834
|
init_api();
|
|
7570
8835
|
var ALLOWED_STATES5 = /* @__PURE__ */ new Set(["requested", "responded"]);
|
|
7571
8836
|
function registerWorkListCommand(root) {
|
|
@@ -7592,9 +8857,9 @@ async function runWorkList(relationshipId, opts) {
|
|
|
7592
8857
|
const api = new ArpApiClient(opts.server);
|
|
7593
8858
|
const sender = resolveSenderAgent("work-list", opts.server, opts.fromDid);
|
|
7594
8859
|
if (!opts.json) {
|
|
7595
|
-
console.log(
|
|
7596
|
-
console.log(
|
|
7597
|
-
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}`));
|
|
7598
8863
|
}
|
|
7599
8864
|
const query = { limit };
|
|
7600
8865
|
if (state) query.state = state;
|
|
@@ -7607,7 +8872,7 @@ async function runWorkList(relationshipId, opts) {
|
|
|
7607
8872
|
return;
|
|
7608
8873
|
}
|
|
7609
8874
|
if (rows.length === 0) {
|
|
7610
|
-
console.log(
|
|
8875
|
+
console.log(import_chalk33.default.dim("\n(no work-logs for this relationship)"));
|
|
7611
8876
|
return;
|
|
7612
8877
|
}
|
|
7613
8878
|
console.log("");
|
|
@@ -7624,36 +8889,36 @@ async function runWorkList(relationshipId, opts) {
|
|
|
7624
8889
|
}));
|
|
7625
8890
|
}
|
|
7626
8891
|
const lastId = rows[rows.length - 1].id;
|
|
7627
|
-
console.log(
|
|
8892
|
+
console.log(import_chalk33.default.dim(`
|
|
7628
8893
|
${rows.length} work-log row(s). Paginate with --after ${lastId}.`));
|
|
7629
8894
|
}
|
|
7630
8895
|
function formatWorkLogLine(w, selfDid, opts = {}) {
|
|
7631
8896
|
const delegationPart = opts.fullIds ? w.delegationId : idHead4(w.delegationId);
|
|
7632
8897
|
const requestPart = opts.fullIds ? w.requestId : truncate4(w.requestId, 16);
|
|
7633
|
-
const id =
|
|
8898
|
+
const id = import_chalk33.default.bold(`${delegationPart}/${requestPart}`);
|
|
7634
8899
|
const state = colorState4(w.state).padEnd(stateColumnWidth4());
|
|
7635
8900
|
const peerCallerHead = opts.fullIds ? w.callerDid : didHead6(w.callerDid);
|
|
7636
8901
|
const peerPayeeHead = opts.fullIds ? w.payeeDid : didHead6(w.payeeDid);
|
|
7637
|
-
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")}`;
|
|
7638
8903
|
const outcome = formatOutcome(w);
|
|
7639
8904
|
return `${id} ${state} ${direction} ${outcome}`;
|
|
7640
8905
|
}
|
|
7641
8906
|
function colorState4(s) {
|
|
7642
8907
|
switch (s) {
|
|
7643
8908
|
case "requested":
|
|
7644
|
-
return
|
|
8909
|
+
return import_chalk33.default.yellow("requested");
|
|
7645
8910
|
case "responded":
|
|
7646
|
-
return
|
|
8911
|
+
return import_chalk33.default.green("responded");
|
|
7647
8912
|
}
|
|
7648
8913
|
}
|
|
7649
8914
|
function stateColumnWidth4() {
|
|
7650
8915
|
return 9;
|
|
7651
8916
|
}
|
|
7652
8917
|
function formatOutcome(w) {
|
|
7653
|
-
if (w.state === "requested") return
|
|
7654
|
-
if (w.responseError) return
|
|
7655
|
-
if (w.responseOutput) return
|
|
7656
|
-
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)");
|
|
7657
8922
|
}
|
|
7658
8923
|
function idHead4(id) {
|
|
7659
8924
|
if (id.length <= 12) return id;
|
|
@@ -7698,6 +8963,13 @@ async function main() {
|
|
|
7698
8963
|
void checkForUpdates();
|
|
7699
8964
|
const program = new import_commander.Command();
|
|
7700
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
|
+
});
|
|
7701
8973
|
registerConfigCommand(program);
|
|
7702
8974
|
registerGuideCommand(program);
|
|
7703
8975
|
registerHomesCommand(program);
|
|
@@ -7708,6 +8980,7 @@ async function main() {
|
|
|
7708
8980
|
registerDidDocCommand(program);
|
|
7709
8981
|
registerDoctorCommand(program);
|
|
7710
8982
|
registerEscrowCommands(program);
|
|
8983
|
+
registerExamplesCommand(program);
|
|
7711
8984
|
registerWhoamiCommand(program);
|
|
7712
8985
|
registerLifecycleCommands(program);
|
|
7713
8986
|
registerRotateCommand(program);
|
|
@@ -7729,16 +9002,24 @@ async function main() {
|
|
|
7729
9002
|
registerReceiptsCommand(program);
|
|
7730
9003
|
registerMemoryCommands(program);
|
|
7731
9004
|
registerWalletCommands(program);
|
|
9005
|
+
registerWebhookCommand(program);
|
|
9006
|
+
registerSettlementCommands(program);
|
|
7732
9007
|
try {
|
|
7733
9008
|
await program.parseAsync(process.argv);
|
|
7734
9009
|
} catch (err) {
|
|
7735
|
-
const
|
|
7736
|
-
|
|
7737
|
-
|
|
7738
|
-
|
|
7739
|
-
|
|
7740
|
-
|
|
7741
|
-
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);
|
|
7742
9023
|
}
|
|
7743
9024
|
}
|
|
7744
9025
|
process.on("unhandledRejection", (reason) => {
|