@heyanon-arp/cli 0.0.11 → 0.0.12
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 +270 -82
- package/dist/cli.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -496,7 +496,7 @@ var init_api = __esm({
|
|
|
496
496
|
* entity feed.
|
|
497
497
|
*/
|
|
498
498
|
async listDelegations(relationshipId, signer, query, signal) {
|
|
499
|
-
|
|
499
|
+
const rows = await this.signedRequest(
|
|
500
500
|
"GET",
|
|
501
501
|
`/v1/relationships/${encodeURIComponent(relationshipId)}/delegations`,
|
|
502
502
|
null,
|
|
@@ -504,6 +504,7 @@ var init_api = __esm({
|
|
|
504
504
|
query,
|
|
505
505
|
signal
|
|
506
506
|
);
|
|
507
|
+
return (0, import_shield.guardInboundFieldsBatch)(rows, ["scopeSummary", "title", "brief", "acceptanceCriteria"], { selfDid: signer.did });
|
|
507
508
|
}
|
|
508
509
|
/**
|
|
509
510
|
* Signed `GET /v1/relationships/:id/work`. One row per
|
|
@@ -513,13 +514,14 @@ var init_api = __esm({
|
|
|
513
514
|
* work-logs operating under a single delegation umbrella.
|
|
514
515
|
*/
|
|
515
516
|
async listWorkLogs(relationshipId, signer, query) {
|
|
516
|
-
|
|
517
|
+
const rows = await this.signedRequest(
|
|
517
518
|
"GET",
|
|
518
519
|
`/v1/relationships/${encodeURIComponent(relationshipId)}/work`,
|
|
519
520
|
null,
|
|
520
521
|
signer,
|
|
521
522
|
query
|
|
522
523
|
);
|
|
524
|
+
return (0, import_shield.guardInboundFieldsBatch)(rows, ["responseOutput", "requestParams"], { selfDid: signer.did });
|
|
523
525
|
}
|
|
524
526
|
/**
|
|
525
527
|
* Signed `GET /v1/relationships/:id/receipts`. One row per
|
|
@@ -770,45 +772,6 @@ var init_format = __esm({
|
|
|
770
772
|
}
|
|
771
773
|
});
|
|
772
774
|
|
|
773
|
-
// src/id-format.ts
|
|
774
|
-
function describeNonUuidShape(raw) {
|
|
775
|
-
if (raw === "") return "empty string";
|
|
776
|
-
if (raw.startsWith("del_") && UUID_RE.test(raw.slice(4))) {
|
|
777
|
-
return "looks like a delegation id with the canonical `del_` prefix. If this field expects a delegation_id, drop the `del_` prefix and pass the 36-char body. If this field expects a DIFFERENT id type (relationship_id / request_id), the `del_`-prefixed value belongs to the WRONG entity \u2014 look up the right row and copy its id";
|
|
778
|
-
}
|
|
779
|
-
if (raw.startsWith("evt_")) {
|
|
780
|
-
return "looks like an event id (evt_<uuid>) \u2014 this command expects a delegation/relationship/request id (UUID), which is a DIFFERENT column. Look up the row that emitted this event (`heyarp events <rel> --json`) and copy the appropriate id field";
|
|
781
|
-
}
|
|
782
|
-
if (raw.startsWith("did:arp:")) {
|
|
783
|
-
return "looks like a DID (agent identifier) \u2014 this command expects a delegation/relationship/request id (UUID), not a DID";
|
|
784
|
-
}
|
|
785
|
-
if (SHA256_PREFIX_RE.test(raw)) {
|
|
786
|
-
return "looks like a sha256:<hash> \u2014 this command expects a UUID, not a hash. sha256: ids show up in receipt_event_hash, request_hash, response_hash";
|
|
787
|
-
}
|
|
788
|
-
if (OBJECT_ID_24_HEX_RE.test(raw)) {
|
|
789
|
-
return "looks like a Mongo ObjectId (24-hex row id surfaced by some server read endpoints) rather than the expected UUID. Mongo ObjectIds and delegation ids are DIFFERENT formats";
|
|
790
|
-
}
|
|
791
|
-
if (UUID_NO_DASHES_RE.test(raw)) {
|
|
792
|
-
return "looks like a UUID with no dashes \u2014 canonical UUIDs need the 8-4-4-4-12 dash pattern (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)";
|
|
793
|
-
}
|
|
794
|
-
return void 0;
|
|
795
|
-
}
|
|
796
|
-
function requireUuid(cmdName, raw, label) {
|
|
797
|
-
if (UUID_RE.test(raw)) return;
|
|
798
|
-
const hint = describeNonUuidShape(raw);
|
|
799
|
-
const base = `${cmdName}: ${label} must be a UUID (got '${raw}')`;
|
|
800
|
-
throw new Error(hint ? `${base} \u2014 ${hint}` : base);
|
|
801
|
-
}
|
|
802
|
-
var UUID_RE, OBJECT_ID_24_HEX_RE, SHA256_PREFIX_RE, UUID_NO_DASHES_RE;
|
|
803
|
-
var init_id_format = __esm({
|
|
804
|
-
"src/id-format.ts"() {
|
|
805
|
-
UUID_RE = /^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$/;
|
|
806
|
-
OBJECT_ID_24_HEX_RE = /^[0-9a-f]{24}$/;
|
|
807
|
-
SHA256_PREFIX_RE = /^sha256:[0-9a-f]{64}$/;
|
|
808
|
-
UUID_NO_DASHES_RE = /^[a-fA-F0-9]{32}$/;
|
|
809
|
-
}
|
|
810
|
-
});
|
|
811
|
-
|
|
812
775
|
// src/state.ts
|
|
813
776
|
function stateFilePath() {
|
|
814
777
|
return (0, import_node_path3.join)(arpHomeDir(), "agents.json");
|
|
@@ -947,7 +910,7 @@ var init_state = __esm({
|
|
|
947
910
|
|
|
948
911
|
// src/commands/lifecycle.ts
|
|
949
912
|
function registerLifecycleCommands(root) {
|
|
950
|
-
root.command("update").description("Update an agent profile (name / description / tags). At least one flag is required.").argument("<did>").option("--server <url>", "Override ARP server base URL").option("--name <s>", "New display name").option("--description <s>", "New description").option("--tag <s>", "Capability tag \u2014 REPLACES the existing list. Repeatable: --tag translation --tag fr",
|
|
913
|
+
root.command("update").description("Update an agent profile (name / description / tags). At least one flag is required.").argument("<did>").option("--server <url>", "Override ARP server base URL").option("--name <s>", "New display name").option("--description <s>", "New description").option("--tag <s>", "Capability tag \u2014 REPLACES the existing list. Repeatable: --tag translation --tag fr", accumulate, []).option("--clear-tags", "Drop all tags (cannot be combined with --tag)", false).action(
|
|
951
914
|
async (did, opts) => {
|
|
952
915
|
const body = buildUpdateBody(opts);
|
|
953
916
|
if (Object.keys(body).length === 0) {
|
|
@@ -963,7 +926,7 @@ function registerLifecycleCommands(root) {
|
|
|
963
926
|
}
|
|
964
927
|
);
|
|
965
928
|
}
|
|
966
|
-
function
|
|
929
|
+
function accumulate(value, previous) {
|
|
967
930
|
return [...previous, value];
|
|
968
931
|
}
|
|
969
932
|
function buildUpdateBody(opts) {
|
|
@@ -983,8 +946,8 @@ function buildUpdateBody(opts) {
|
|
|
983
946
|
async function actSigned(did, serverOverride, act) {
|
|
984
947
|
const local = loadAgentOrThrow(serverOverride, did);
|
|
985
948
|
const api = new ArpApiClient(serverOverride);
|
|
986
|
-
console.log(
|
|
987
|
-
console.log(
|
|
949
|
+
console.log(import_chalk2.default.dim(`Server: ${api.serverUrl}`));
|
|
950
|
+
console.log(import_chalk2.default.dim(`Signer: ${local.did}`));
|
|
988
951
|
const signer = makeSigner(local);
|
|
989
952
|
return act(api, signer);
|
|
990
953
|
}
|
|
@@ -995,22 +958,61 @@ function makeSigner(s) {
|
|
|
995
958
|
};
|
|
996
959
|
}
|
|
997
960
|
function printAgent(verb, agent) {
|
|
998
|
-
console.log(
|
|
961
|
+
console.log(import_chalk2.default.green(`
|
|
999
962
|
${verb}.`));
|
|
1000
|
-
console.log(`${
|
|
1001
|
-
console.log(
|
|
963
|
+
console.log(`${import_chalk2.default.bold("DID")}: ${import_chalk2.default.cyan(agent.did)}`);
|
|
964
|
+
console.log(import_chalk2.default.bold("\nAgent profile:"));
|
|
1002
965
|
console.log(formatJson(agent));
|
|
1003
966
|
}
|
|
1004
|
-
var
|
|
967
|
+
var import_chalk2;
|
|
1005
968
|
var init_lifecycle = __esm({
|
|
1006
969
|
"src/commands/lifecycle.ts"() {
|
|
1007
|
-
|
|
970
|
+
import_chalk2 = __toESM(require("chalk"));
|
|
1008
971
|
init_api();
|
|
1009
972
|
init_format();
|
|
1010
973
|
init_state();
|
|
1011
974
|
}
|
|
1012
975
|
});
|
|
1013
976
|
|
|
977
|
+
// src/id-format.ts
|
|
978
|
+
function describeNonUuidShape(raw) {
|
|
979
|
+
if (raw === "") return "empty string";
|
|
980
|
+
if (raw.startsWith("del_") && UUID_RE.test(raw.slice(4))) {
|
|
981
|
+
return "looks like a delegation id with the canonical `del_` prefix. If this field expects a delegation_id, drop the `del_` prefix and pass the 36-char body. If this field expects a DIFFERENT id type (relationship_id / request_id), the `del_`-prefixed value belongs to the WRONG entity \u2014 look up the right row and copy its id";
|
|
982
|
+
}
|
|
983
|
+
if (raw.startsWith("evt_")) {
|
|
984
|
+
return "looks like an event id (evt_<uuid>) \u2014 this command expects a delegation/relationship/request id (UUID), which is a DIFFERENT column. Look up the row that emitted this event (`heyarp events <rel> --json`) and copy the appropriate id field";
|
|
985
|
+
}
|
|
986
|
+
if (raw.startsWith("did:arp:")) {
|
|
987
|
+
return "looks like a DID (agent identifier) \u2014 this command expects a delegation/relationship/request id (UUID), not a DID";
|
|
988
|
+
}
|
|
989
|
+
if (SHA256_PREFIX_RE.test(raw)) {
|
|
990
|
+
return "looks like a sha256:<hash> \u2014 this command expects a UUID, not a hash. sha256: ids show up in receipt_event_hash, request_hash, response_hash";
|
|
991
|
+
}
|
|
992
|
+
if (OBJECT_ID_24_HEX_RE.test(raw)) {
|
|
993
|
+
return "looks like a Mongo ObjectId (24-hex row id surfaced by some server read endpoints) rather than the expected UUID. Mongo ObjectIds and delegation ids are DIFFERENT formats";
|
|
994
|
+
}
|
|
995
|
+
if (UUID_NO_DASHES_RE.test(raw)) {
|
|
996
|
+
return "looks like a UUID with no dashes \u2014 canonical UUIDs need the 8-4-4-4-12 dash pattern (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)";
|
|
997
|
+
}
|
|
998
|
+
return void 0;
|
|
999
|
+
}
|
|
1000
|
+
function requireUuid(cmdName, raw, label) {
|
|
1001
|
+
if (UUID_RE.test(raw)) return;
|
|
1002
|
+
const hint = describeNonUuidShape(raw);
|
|
1003
|
+
const base = `${cmdName}: ${label} must be a UUID (got '${raw}')`;
|
|
1004
|
+
throw new Error(hint ? `${base} \u2014 ${hint}` : base);
|
|
1005
|
+
}
|
|
1006
|
+
var UUID_RE, OBJECT_ID_24_HEX_RE, SHA256_PREFIX_RE, UUID_NO_DASHES_RE;
|
|
1007
|
+
var init_id_format = __esm({
|
|
1008
|
+
"src/id-format.ts"() {
|
|
1009
|
+
UUID_RE = /^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$/;
|
|
1010
|
+
OBJECT_ID_24_HEX_RE = /^[0-9a-f]{24}$/;
|
|
1011
|
+
SHA256_PREFIX_RE = /^sha256:[0-9a-f]{64}$/;
|
|
1012
|
+
UUID_NO_DASHES_RE = /^[a-fA-F0-9]{32}$/;
|
|
1013
|
+
}
|
|
1014
|
+
});
|
|
1015
|
+
|
|
1014
1016
|
// src/commands/status.ts
|
|
1015
1017
|
function registerStatusCommand(root) {
|
|
1016
1018
|
root.command("status").description("Where am I in the work cycle? FSM state + next-action hint for ONE relationship (signed reads)").argument("<relationship-id>", "Relationship UUID").option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Signer DID \u2014 required only if multiple agents are registered against this server").option("--json", "Machine-readable: single JSON object with the composed summary. Pipe-safe.", false).option(
|
|
@@ -2576,7 +2578,29 @@ async function runOffer(recipientDid, opts) {
|
|
|
2576
2578
|
console.log(import_chalk6.default.dim(`Sender: ${sender.did}`));
|
|
2577
2579
|
console.log(import_chalk6.default.dim(`Recipient: ${recipientDid}`));
|
|
2578
2580
|
console.log(import_chalk6.default.dim(`Delegation: ${delegationId}`));
|
|
2579
|
-
|
|
2581
|
+
let result;
|
|
2582
|
+
try {
|
|
2583
|
+
result = await sendDelegationEnvelope({ api, sender, recipientDid, body, ttlSeconds, verbose: opts.verbose, server: opts.server });
|
|
2584
|
+
} catch (err) {
|
|
2585
|
+
if (err instanceof ApiError && err.payload.code === "DELEGATION_PRICING_MISMATCH") {
|
|
2586
|
+
const d = err.payload.details;
|
|
2587
|
+
console.error(import_chalk6.default.yellow(`
|
|
2588
|
+
The recipient's published accept-prefs rejected this offer${d?.reason ? ` (mismatch: ${d.reason})` : ""}.`));
|
|
2589
|
+
if (d !== void 0) {
|
|
2590
|
+
console.error(import_chalk6.default.dim(` accepted: ${JSON.stringify(d.accepted)}`));
|
|
2591
|
+
console.error(import_chalk6.default.dim(` offered: ${JSON.stringify(d.offered)}`));
|
|
2592
|
+
}
|
|
2593
|
+
console.error(
|
|
2594
|
+
import_chalk6.default.dim(
|
|
2595
|
+
`
|
|
2596
|
+
Check what the recipient accepts BEFORE offering:
|
|
2597
|
+
heyarp agents accept-prefs show ${recipientDid}
|
|
2598
|
+
then re-run with matching --pricing-model / --currency / --amount.`
|
|
2599
|
+
)
|
|
2600
|
+
);
|
|
2601
|
+
}
|
|
2602
|
+
throw err;
|
|
2603
|
+
}
|
|
2580
2604
|
printIngestResult(result);
|
|
2581
2605
|
console.log(import_chalk6.default.dim(`
|
|
2582
2606
|
Reference this delegation on subsequent calls with:`));
|
|
@@ -3194,6 +3218,12 @@ var init_delegation = __esm({
|
|
|
3194
3218
|
"DELEGATION_ACCEPTER_IS_OFFERER",
|
|
3195
3219
|
"DELEGATION_DECLINER_IS_OFFERER",
|
|
3196
3220
|
"DELEGATION_CANCELER_NOT_OFFERER",
|
|
3221
|
+
// PricingPolicy: the offer's terms fell outside the RECIPIENT's
|
|
3222
|
+
// published accept-prefs. Fires in `handleOffer` AFTER the event
|
|
3223
|
+
// row commit (pre-materialization on the server), so the sequence
|
|
3224
|
+
// was consumed — advance it or the corrected re-offer trips
|
|
3225
|
+
// ENV_SEQUENCE_BACKWARDS.
|
|
3226
|
+
"DELEGATION_PRICING_MISMATCH",
|
|
3197
3227
|
// `DELEGATION_PENDING_LOCK` fires from the body handler's
|
|
3198
3228
|
// `requireDelegationInState` AFTER the event row is persisted
|
|
3199
3229
|
// (same code path as DELEGATION_INVALID_STATE), so an accept
|
|
@@ -3685,9 +3715,10 @@ async function autoSignAndDeliverPayeeSig(opts, cmdName = "receipt propose") {
|
|
|
3685
3715
|
if (!delegation) {
|
|
3686
3716
|
throw new Error(`${cmdName}: delegation ${delegationId} not found under relationship ${relId} (paginated 5000 rows).`);
|
|
3687
3717
|
}
|
|
3688
|
-
|
|
3718
|
+
const SETTLEABLE_STATES = /* @__PURE__ */ new Set(["accepted", "locked"]);
|
|
3719
|
+
if (!SETTLEABLE_STATES.has(delegation.state ?? "")) {
|
|
3689
3720
|
throw new Error(
|
|
3690
|
-
`${cmdName}: delegation ${delegationId} is in state '${delegation.state ?? "unknown"}' \u2014 only an 'accepted' delegation is settleable (its escrow lock is still LOCKED). A non-
|
|
3721
|
+
`${cmdName}: delegation ${delegationId} is in state '${delegation.state ?? "unknown"}' \u2014 only an 'accepted' or 'locked' delegation is settleable (its escrow lock is still LOCKED). A non-settleable state'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.`
|
|
3691
3722
|
);
|
|
3692
3723
|
}
|
|
3693
3724
|
if (!delegation.amount) {
|
|
@@ -5056,7 +5087,7 @@ var import_simple_update_notifier = __toESM(require("simple-update-notifier"));
|
|
|
5056
5087
|
// package.json
|
|
5057
5088
|
var package_default = {
|
|
5058
5089
|
name: "@heyanon-arp/cli",
|
|
5059
|
-
version: "0.0.
|
|
5090
|
+
version: "0.0.12",
|
|
5060
5091
|
description: "Command-line client for the Agent Relationship Protocol \u2014 register agents, sign envelopes, run escrowed work cycles on Solana.",
|
|
5061
5092
|
license: "MIT",
|
|
5062
5093
|
keywords: ["arp", "agent-relationship-protocol", "did", "solana", "escrow", "ed25519", "agents", "a2a", "cli"],
|
|
@@ -5101,11 +5132,13 @@ var package_default = {
|
|
|
5101
5132
|
};
|
|
5102
5133
|
|
|
5103
5134
|
// src/commands/agents.ts
|
|
5104
|
-
var
|
|
5135
|
+
var import_chalk3 = __toESM(require("chalk"));
|
|
5105
5136
|
init_api();
|
|
5106
5137
|
init_format();
|
|
5138
|
+
init_state();
|
|
5139
|
+
init_lifecycle();
|
|
5107
5140
|
function registerAgentsCommand(root) {
|
|
5108
|
-
root.command("agents").description("Discover published agents on the server (public catalog) \u2014 filter by tag / free-text").option("--server <url>", "Override ARP server base URL").option("--tag <s>", "Filter by capability tag \u2014 repeatable; AND-semantics across tags",
|
|
5141
|
+
const agents = root.command("agents").description("Discover published agents on the server (public catalog) \u2014 filter by tag / free-text").option("--server <url>", "Override ARP server base URL").option("--tag <s>", "Filter by capability tag \u2014 repeatable; AND-semantics across tags", accumulate2, []).option("--query <s>", "Full-text search over name + description").option("--after <id>", "Cursor: pass the previous page's last `id` to fetch the next page").option("--limit <n>", "Max rows to return (1..100)", "20").option(
|
|
5109
5142
|
"--verbose",
|
|
5110
5143
|
'After the one-line summaries, print a framed "Full agent payloads (N rows)" block containing the JSON array of all rows (with owner-string sanitisation for terminal safety)',
|
|
5111
5144
|
false
|
|
@@ -5127,11 +5160,12 @@ function registerAgentsCommand(root) {
|
|
|
5127
5160
|
).action(async (opts) => {
|
|
5128
5161
|
await runAgents(opts);
|
|
5129
5162
|
});
|
|
5163
|
+
registerAcceptPrefsCommands(agents);
|
|
5130
5164
|
}
|
|
5131
5165
|
async function runAgents(opts) {
|
|
5132
5166
|
const limit = parseLimit(opts.limit);
|
|
5133
5167
|
const api = new ArpApiClient(opts.server);
|
|
5134
|
-
progress(opts.json,
|
|
5168
|
+
progress(opts.json, import_chalk3.default.dim(`Server: ${api.serverUrl}`));
|
|
5135
5169
|
const query = { limit };
|
|
5136
5170
|
if (opts.tag && opts.tag.length > 0) query.tag = opts.tag.map((t) => t.trim().toLowerCase());
|
|
5137
5171
|
if (opts.query) query.q = opts.query;
|
|
@@ -5147,7 +5181,7 @@ async function runAgents(opts) {
|
|
|
5147
5181
|
return;
|
|
5148
5182
|
}
|
|
5149
5183
|
if (rows.length === 0) {
|
|
5150
|
-
console.log(
|
|
5184
|
+
console.log(import_chalk3.default.dim("\n(no agents matched)"));
|
|
5151
5185
|
return;
|
|
5152
5186
|
}
|
|
5153
5187
|
console.log("");
|
|
@@ -5158,10 +5192,10 @@ async function runAgents(opts) {
|
|
|
5158
5192
|
const useUnicode = supportsUnicodeFrame();
|
|
5159
5193
|
const heavyRule = useUnicode ? "\u2501".repeat(60) : "=".repeat(60);
|
|
5160
5194
|
const headerGlyph = useUnicode ? "\u25BC" : ">";
|
|
5161
|
-
const rule =
|
|
5195
|
+
const rule = import_chalk3.default.cyan(heavyRule);
|
|
5162
5196
|
console.log(`
|
|
5163
5197
|
${rule}`);
|
|
5164
|
-
console.log(
|
|
5198
|
+
console.log(import_chalk3.default.bold.cyan(`${headerGlyph} Full agent payloads (${safeRows.length} row${safeRows.length === 1 ? "" : "s"})`));
|
|
5165
5199
|
console.log(rule);
|
|
5166
5200
|
console.log(formatJson(safeRows));
|
|
5167
5201
|
console.log(`${rule}
|
|
@@ -5169,16 +5203,16 @@ ${rule}`);
|
|
|
5169
5203
|
}
|
|
5170
5204
|
if (rows.length === limit) {
|
|
5171
5205
|
const cursor = rows[rows.length - 1].id;
|
|
5172
|
-
console.log(
|
|
5206
|
+
console.log(import_chalk3.default.dim(`
|
|
5173
5207
|
Next page: re-run with --after ${cursor}`));
|
|
5174
5208
|
}
|
|
5175
5209
|
}
|
|
5176
5210
|
function formatAgentLine(a, opts = {}) {
|
|
5177
5211
|
const idDisplay = opts.fullIds ? a.id : `${a.id.slice(0, 8)}...${a.id.slice(-4)}`;
|
|
5178
5212
|
const tags = `[${a.tags.join(",")}]`;
|
|
5179
|
-
const name = a.name ? `"${truncate(sanitizeForTerminal(a.name), 40)}"` :
|
|
5180
|
-
const description = a.description ? `\u2014 ${
|
|
5181
|
-
return `${
|
|
5213
|
+
const name = a.name ? `"${truncate(sanitizeForTerminal(a.name), 40)}"` : import_chalk3.default.dim("(unnamed)");
|
|
5214
|
+
const description = a.description ? `\u2014 ${import_chalk3.default.dim(truncate(sanitizeForTerminal(a.description), 60))}` : "";
|
|
5215
|
+
return `${import_chalk3.default.dim(idDisplay)} ${import_chalk3.default.cyan(a.did)} ${import_chalk3.default.magenta(tags)} ${name} ${description}`.trim();
|
|
5182
5216
|
}
|
|
5183
5217
|
function truncate(s, max) {
|
|
5184
5218
|
if (s.length <= max) return s;
|
|
@@ -5195,12 +5229,129 @@ function parseLimit(raw) {
|
|
|
5195
5229
|
}
|
|
5196
5230
|
return n;
|
|
5197
5231
|
}
|
|
5198
|
-
function
|
|
5232
|
+
function accumulate2(value, previous) {
|
|
5199
5233
|
return [...previous, value];
|
|
5200
5234
|
}
|
|
5235
|
+
var PRICING_MODELS = ["flat", "usage_based"];
|
|
5236
|
+
function registerAcceptPrefsCommands(agents) {
|
|
5237
|
+
const prefs = agents.command("accept-prefs").description("Worker-side accept-preferences (PricingPolicy): what delegation offers the server should reject on your behalf");
|
|
5238
|
+
prefs.command("set").description("Publish accept-prefs for an agent you own \u2014 REPLACES the whole object on every call").argument("<did>", "Agent DID (must have a local key \u2014 owner-only)").option("--server <url>", "Override ARP server base URL").option("--pricing-model <m>", "Accepted pricing model: flat | usage_based. Repeatable; omit to accept any model.", accumulate2, []).option(
|
|
5239
|
+
"--currency <spec>",
|
|
5240
|
+
`Accepted currency: "<caip19-asset-id>[,<min>[,<max>]]" \u2014 optional inclusive amount bounds as decimal strings in that currency's units (empty segment skips: "asset,,500" = max only). Repeatable; omit to accept any currency.`,
|
|
5241
|
+
accumulate2,
|
|
5242
|
+
[]
|
|
5243
|
+
).option(
|
|
5244
|
+
"--from-json <json>",
|
|
5245
|
+
'Full AcceptPrefs JSON object (mutually exclusive with --pricing-model / --currency). Shape: {"pricingModels":[...],"currencies":[{"assetId":"...","minAmount":"...","maxAmount":"..."}]}'
|
|
5246
|
+
).action(async (did, _opts, cmd) => {
|
|
5247
|
+
const opts = cmd.optsWithGlobals();
|
|
5248
|
+
const body = buildAcceptPrefs(opts);
|
|
5249
|
+
const local = loadAgentOrThrow(opts.server, did);
|
|
5250
|
+
const api = new ArpApiClient(opts.server);
|
|
5251
|
+
console.log(import_chalk3.default.dim(`Server: ${api.serverUrl}`));
|
|
5252
|
+
console.log(import_chalk3.default.dim(`Signer: ${local.did}`));
|
|
5253
|
+
const agent = await api.updateAgent(did, { acceptPrefs: body }, makeSigner(local));
|
|
5254
|
+
console.log(import_chalk3.default.green("\nAccept-prefs published."));
|
|
5255
|
+
printAcceptPrefs(agent.acceptPrefs);
|
|
5256
|
+
console.log(import_chalk3.default.dim("\nIncompatible delegation offers are now rejected server-side with DELEGATION_PRICING_MISMATCH."));
|
|
5257
|
+
});
|
|
5258
|
+
prefs.command("show").description(
|
|
5259
|
+
"Read an agent's published accept-prefs (any DID \u2014 the read is signed with YOUR local agent key; any registered agent may read). Buyers: run this BEFORE `delegation offer` so the offer matches"
|
|
5260
|
+
).argument("<did>", "Agent DID to inspect (any agent, not just your own)").option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Signer DID \u2014 required only if multiple agents are registered against this server").option("--json", "Emit only the AcceptPrefs JSON (or null) on stdout \u2014 jq-pipeable", false).action(async (did, _opts, cmd) => {
|
|
5261
|
+
const opts = cmd.optsWithGlobals();
|
|
5262
|
+
const sender = resolveSenderAgent("agents accept-prefs show", opts.server, opts.fromDid);
|
|
5263
|
+
const api = new ArpApiClient(opts.server);
|
|
5264
|
+
progress(opts.json, import_chalk3.default.dim(`Server: ${api.serverUrl}`));
|
|
5265
|
+
progress(opts.json, import_chalk3.default.dim(`Signer: ${sender.did}`));
|
|
5266
|
+
const agent = await api.getAgent(did, makeSigner(sender));
|
|
5267
|
+
if (opts.json) {
|
|
5268
|
+
jsonOut(agent.acceptPrefs ?? null);
|
|
5269
|
+
return;
|
|
5270
|
+
}
|
|
5271
|
+
console.log("");
|
|
5272
|
+
printAcceptPrefs(agent.acceptPrefs);
|
|
5273
|
+
});
|
|
5274
|
+
prefs.command("clear").description('Remove the published accept-prefs (back to "accept anything") \u2014 owner-only').argument("<did>", "Agent DID (must have a local key)").option("--server <url>", "Override ARP server base URL").action(async (did, _opts, cmd) => {
|
|
5275
|
+
const opts = cmd.optsWithGlobals();
|
|
5276
|
+
const local = loadAgentOrThrow(opts.server, did);
|
|
5277
|
+
const api = new ArpApiClient(opts.server);
|
|
5278
|
+
console.log(import_chalk3.default.dim(`Server: ${api.serverUrl}`));
|
|
5279
|
+
console.log(import_chalk3.default.dim(`Signer: ${local.did}`));
|
|
5280
|
+
await api.updateAgent(did, { acceptPrefs: null }, makeSigner(local));
|
|
5281
|
+
console.log(import_chalk3.default.green("\nAccept-prefs cleared \u2014 the agent accepts any offer terms again."));
|
|
5282
|
+
});
|
|
5283
|
+
}
|
|
5284
|
+
function buildAcceptPrefs(opts) {
|
|
5285
|
+
const hasGranular = (opts.pricingModel?.length ?? 0) > 0 || (opts.currency?.length ?? 0) > 0;
|
|
5286
|
+
if (opts.fromJson !== void 0 && hasGranular) {
|
|
5287
|
+
throw new Error("agents accept-prefs set: --from-json and --pricing-model/--currency are mutually exclusive. Pick one path.");
|
|
5288
|
+
}
|
|
5289
|
+
if (opts.fromJson !== void 0) {
|
|
5290
|
+
let parsed;
|
|
5291
|
+
try {
|
|
5292
|
+
parsed = JSON.parse(opts.fromJson);
|
|
5293
|
+
} catch (err) {
|
|
5294
|
+
throw new Error(`agents accept-prefs set: --from-json is not valid JSON: ${err.message}`);
|
|
5295
|
+
}
|
|
5296
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
5297
|
+
throw new Error(`agents accept-prefs set: --from-json must be a JSON object, got ${Array.isArray(parsed) ? "array" : typeof parsed}.`);
|
|
5298
|
+
}
|
|
5299
|
+
return parsed;
|
|
5300
|
+
}
|
|
5301
|
+
if (!hasGranular) {
|
|
5302
|
+
throw new Error("agents accept-prefs set: pass at least one of --pricing-model / --currency / --from-json (to remove prefs entirely use `accept-prefs clear`).");
|
|
5303
|
+
}
|
|
5304
|
+
const prefs = {};
|
|
5305
|
+
if (opts.pricingModel && opts.pricingModel.length > 0) {
|
|
5306
|
+
const models = [...new Set(opts.pricingModel.map((m) => m.trim().toLowerCase()))];
|
|
5307
|
+
for (const m of models) {
|
|
5308
|
+
if (!PRICING_MODELS.includes(m)) {
|
|
5309
|
+
throw new Error(`agents accept-prefs set: --pricing-model must be one of flat | usage_based (got '${m}')`);
|
|
5310
|
+
}
|
|
5311
|
+
}
|
|
5312
|
+
prefs.pricingModels = models;
|
|
5313
|
+
}
|
|
5314
|
+
if (opts.currency && opts.currency.length > 0) {
|
|
5315
|
+
prefs.currencies = opts.currency.map(parseCurrencySpec);
|
|
5316
|
+
}
|
|
5317
|
+
return prefs;
|
|
5318
|
+
}
|
|
5319
|
+
function parseCurrencySpec(spec) {
|
|
5320
|
+
const parts = spec.split(",").map((p) => p.trim());
|
|
5321
|
+
if (parts.length > 3) {
|
|
5322
|
+
throw new Error(`agents accept-prefs set: --currency expects "<asset-id>[,<min>[,<max>]]" (got ${parts.length} comma-separated segments in '${spec}')`);
|
|
5323
|
+
}
|
|
5324
|
+
const [assetId, min, max] = parts;
|
|
5325
|
+
if (!assetId) {
|
|
5326
|
+
throw new Error(`agents accept-prefs set: --currency spec '${spec}' is missing the asset id`);
|
|
5327
|
+
}
|
|
5328
|
+
return {
|
|
5329
|
+
assetId,
|
|
5330
|
+
...min ? { minAmount: min } : {},
|
|
5331
|
+
...max ? { maxAmount: max } : {}
|
|
5332
|
+
};
|
|
5333
|
+
}
|
|
5334
|
+
function printAcceptPrefs(prefs) {
|
|
5335
|
+
if (!prefs || (prefs.pricingModels?.length ?? 0) === 0 && (prefs.currencies?.length ?? 0) === 0) {
|
|
5336
|
+
console.log(import_chalk3.default.dim("(no accept-prefs published \u2014 this agent accepts any offer terms)"));
|
|
5337
|
+
return;
|
|
5338
|
+
}
|
|
5339
|
+
const models = prefs.pricingModels ?? [];
|
|
5340
|
+
console.log(`${import_chalk3.default.bold("Pricing models:")} ${models.length > 0 ? models.join(", ") : import_chalk3.default.dim("any")}`);
|
|
5341
|
+
const currencies = prefs.currencies ?? [];
|
|
5342
|
+
if (currencies.length === 0) {
|
|
5343
|
+
console.log(`${import_chalk3.default.bold("Currencies:")} ${import_chalk3.default.dim("any (no amount bounds)")}`);
|
|
5344
|
+
return;
|
|
5345
|
+
}
|
|
5346
|
+
console.log(import_chalk3.default.bold("Currencies:"));
|
|
5347
|
+
for (const c of currencies) {
|
|
5348
|
+
const bounds = c.minAmount !== void 0 || c.maxAmount !== void 0 ? import_chalk3.default.dim(` (min: ${c.minAmount ?? "\u2014"}, max: ${c.maxAmount ?? "\u2014"})`) : import_chalk3.default.dim(" (no amount bounds)");
|
|
5349
|
+
console.log(` ${import_chalk3.default.cyan(c.assetId)}${bounds}`);
|
|
5350
|
+
}
|
|
5351
|
+
}
|
|
5201
5352
|
|
|
5202
5353
|
// src/commands/config.ts
|
|
5203
|
-
var
|
|
5354
|
+
var import_chalk4 = __toESM(require("chalk"));
|
|
5204
5355
|
init_api();
|
|
5205
5356
|
init_config();
|
|
5206
5357
|
function registerConfigCommand(root) {
|
|
@@ -5209,15 +5360,15 @@ function registerConfigCommand(root) {
|
|
|
5209
5360
|
const opts = config.opts();
|
|
5210
5361
|
if (isGlobalConfigKey(key)) {
|
|
5211
5362
|
setGlobalConfigValue(key, value);
|
|
5212
|
-
console.log(`${
|
|
5363
|
+
console.log(`${import_chalk4.default.green("\u2713")} ${import_chalk4.default.bold(key)} = ${import_chalk4.default.cyan(value)} ${import_chalk4.default.dim("(global)")}`);
|
|
5213
5364
|
} else if (isServerConfigKey(key)) {
|
|
5214
5365
|
const serverUrl = resolveServerUrl(opts.server);
|
|
5215
5366
|
setServerConfigValue(serverUrl, key, value);
|
|
5216
|
-
console.log(`${
|
|
5367
|
+
console.log(`${import_chalk4.default.green("\u2713")} ${import_chalk4.default.bold(key)} = ${import_chalk4.default.cyan(value)} ${import_chalk4.default.dim(`(scoped to ${serverUrl})`)}`);
|
|
5217
5368
|
} else {
|
|
5218
5369
|
throw unknownKey(key);
|
|
5219
5370
|
}
|
|
5220
|
-
console.log(
|
|
5371
|
+
console.log(import_chalk4.default.dim(` stored in ${configFilePath()}`));
|
|
5221
5372
|
});
|
|
5222
5373
|
config.command("get <key>").description("Print one config value").action((key) => {
|
|
5223
5374
|
const opts = config.opts();
|
|
@@ -5231,7 +5382,7 @@ function registerConfigCommand(root) {
|
|
|
5231
5382
|
throw unknownKey(key);
|
|
5232
5383
|
}
|
|
5233
5384
|
if (value === void 0) {
|
|
5234
|
-
console.log(
|
|
5385
|
+
console.log(import_chalk4.default.dim("(not set)"));
|
|
5235
5386
|
return;
|
|
5236
5387
|
}
|
|
5237
5388
|
console.log(value);
|
|
@@ -5242,23 +5393,23 @@ function registerConfigCommand(root) {
|
|
|
5242
5393
|
const servers = all.servers ?? {};
|
|
5243
5394
|
const serverUrls = Object.keys(servers);
|
|
5244
5395
|
if (globalEntries.length === 0 && serverUrls.length === 0) {
|
|
5245
|
-
console.log(
|
|
5246
|
-
console.log(
|
|
5396
|
+
console.log(import_chalk4.default.dim("(no config set)"));
|
|
5397
|
+
console.log(import_chalk4.default.dim(` would write to ${configFilePath()}`));
|
|
5247
5398
|
return;
|
|
5248
5399
|
}
|
|
5249
5400
|
if (globalEntries.length > 0) {
|
|
5250
|
-
console.log(
|
|
5401
|
+
console.log(import_chalk4.default.bold("global"));
|
|
5251
5402
|
for (const [k, v] of globalEntries) {
|
|
5252
|
-
console.log(` ${
|
|
5403
|
+
console.log(` ${import_chalk4.default.bold(k)} = ${import_chalk4.default.cyan(v)}`);
|
|
5253
5404
|
}
|
|
5254
5405
|
}
|
|
5255
5406
|
for (const url of serverUrls) {
|
|
5256
5407
|
const bucket = servers[url];
|
|
5257
5408
|
const bucketEntries = Object.entries(bucket).filter((entry) => typeof entry[1] === "string");
|
|
5258
5409
|
if (bucketEntries.length === 0) continue;
|
|
5259
|
-
console.log(`${
|
|
5410
|
+
console.log(`${import_chalk4.default.bold("server")} ${import_chalk4.default.dim(url)}`);
|
|
5260
5411
|
for (const [k, v] of bucketEntries) {
|
|
5261
|
-
console.log(` ${
|
|
5412
|
+
console.log(` ${import_chalk4.default.bold(k)} = ${import_chalk4.default.cyan(v)}`);
|
|
5262
5413
|
}
|
|
5263
5414
|
}
|
|
5264
5415
|
});
|
|
@@ -5266,11 +5417,11 @@ function registerConfigCommand(root) {
|
|
|
5266
5417
|
const opts = config.opts();
|
|
5267
5418
|
if (isGlobalConfigKey(key)) {
|
|
5268
5419
|
unsetGlobalConfigValue(key);
|
|
5269
|
-
console.log(`${
|
|
5420
|
+
console.log(`${import_chalk4.default.green("\u2713")} unset ${import_chalk4.default.bold(key)} ${import_chalk4.default.dim("(global)")}`);
|
|
5270
5421
|
} else if (isServerConfigKey(key)) {
|
|
5271
5422
|
const serverUrl = resolveServerUrl(opts.server);
|
|
5272
5423
|
unsetServerConfigValue(serverUrl, key);
|
|
5273
|
-
console.log(`${
|
|
5424
|
+
console.log(`${import_chalk4.default.green("\u2713")} unset ${import_chalk4.default.bold(key)} ${import_chalk4.default.dim(`(scoped to ${serverUrl})`)}`);
|
|
5274
5425
|
} else {
|
|
5275
5426
|
throw unknownKey(key);
|
|
5276
5427
|
}
|
|
@@ -5982,7 +6133,7 @@ var GUIDE_SECTIONS = [
|
|
|
5982
6133
|
" cursor (last serverTimestamp + eventId); persist them however you like:",
|
|
5983
6134
|
' NEW=$(heyarp inbox --json ${TS:+--since "$TS" --since-event-id "$EVT"})',
|
|
5984
6135
|
' [ "$(jq length <<<"$NEW")" -gt 0 ] || exit 0 # nothing new \u2192 stay silent',
|
|
5985
|
-
|
|
6136
|
+
` jq -c '.[] | select(.readModelStatus == "materialized")' <<<"$NEW" | while read -r ev; do`,
|
|
5986
6137
|
' : # act on $(jq -r .body.type <<<"$ev"): handshake\u2192respond, delegation.offer\u2192accept, work_request\u2192respond',
|
|
5987
6138
|
" done",
|
|
5988
6139
|
" # new cursor = newest (last) row; persist these two for next tick:",
|
|
@@ -5994,6 +6145,36 @@ var GUIDE_SECTIONS = [
|
|
|
5994
6145
|
],
|
|
5995
6146
|
crossRefs: ['Long-lived pattern details: see "Live tail vs polling \u2014 the FSM-wait pattern".']
|
|
5996
6147
|
},
|
|
6148
|
+
{
|
|
6149
|
+
id: "worker.accept-prefs",
|
|
6150
|
+
roles: ["worker"],
|
|
6151
|
+
title: "Accept-prefs \u2014 let the SERVER bounce offers you would decline anyway",
|
|
6152
|
+
body: [
|
|
6153
|
+
' Publish "what I accept" ONCE and the server rejects incompatible offers',
|
|
6154
|
+
" (wrong currency / pricing model / amount out of range) AT INGEST: the",
|
|
6155
|
+
" buyer gets an instant DELEGATION_PRICING_MISMATCH and NO delegation is",
|
|
6156
|
+
" created \u2014 nothing for you to act on or decline. (The rejected envelope",
|
|
6157
|
+
" still lands in the audit chain, so inbox feeds may show it with",
|
|
6158
|
+
" readModelStatus: 'rejected' \u2014 your loop should skip non-materialized",
|
|
6159
|
+
" rows.) Optional: no prefs = everything reaches you (you still decide;",
|
|
6160
|
+
" prefs only auto-REJECT, never auto-accept).",
|
|
6161
|
+
"",
|
|
6162
|
+
" heyarp agents accept-prefs set <your-did> \\",
|
|
6163
|
+
" --pricing-model flat \\",
|
|
6164
|
+
' --currency "<caip19-asset-id>,<min>,<max>" # bounds optional + per-currency',
|
|
6165
|
+
" heyarp agents accept-prefs show <your-did> # verify what is live",
|
|
6166
|
+
' heyarp agents accept-prefs clear <your-did> # back to "anything reaches me"',
|
|
6167
|
+
"",
|
|
6168
|
+
" Each `set` REPLACES the whole object. Buyers see your prefs on your",
|
|
6169
|
+
" public profile/catalog row and on the rejection itself, so honest buyers",
|
|
6170
|
+
" re-offer with matching terms."
|
|
6171
|
+
],
|
|
6172
|
+
commonErrors: [
|
|
6173
|
+
"Do NOT treat accept-prefs as a guarantee of good offers \u2014 they filter currency/model/amount only; scope-vs-price judgment is still YOUR accept/decline decision.",
|
|
6174
|
+
"Never act on an inbox row with readModelStatus != 'materialized' \u2014 it is the audit trace of a REJECTED envelope (e.g. a prefs-bounced offer); there is no delegation behind it.",
|
|
6175
|
+
"Remember bounds are per-currency and inclusive; an offer exactly at min or max passes."
|
|
6176
|
+
]
|
|
6177
|
+
},
|
|
5997
6178
|
{
|
|
5998
6179
|
id: "buyer.flow",
|
|
5999
6180
|
roles: ["buyer"],
|
|
@@ -6016,7 +6197,7 @@ var GUIDE_SECTIONS = [
|
|
|
6016
6197
|
{ when: "you need a task done", then: "find a worker `heyarp agents --tag <tag>`, then open contact `heyarp send-handshake <worker-did>`" },
|
|
6017
6198
|
{
|
|
6018
6199
|
when: "the worker accepts the handshake",
|
|
6019
|
-
then: "offer the work (terms only, NO lock) `heyarp delegation offer <worker-did> --delegation-id <new-uuid> --title \u2026 --scope \u2026 --pricing-model flat --amount \u2026 --currency USDC:solana-devnet --deadline \u2026` then wait `--wait-until delegation.accepted`"
|
|
6200
|
+
then: "pre-flight the money terms against the worker's published accept-prefs `heyarp agents accept-prefs show <worker-did>` (a mismatching offer is rejected server-side with DELEGATION_PRICING_MISMATCH), then offer the work (terms only, NO lock) `heyarp delegation offer <worker-did> --delegation-id <new-uuid> --title \u2026 --scope \u2026 --pricing-model flat --amount \u2026 --currency USDC:solana-devnet --deadline \u2026` then wait `--wait-until delegation.accepted`"
|
|
6020
6201
|
},
|
|
6021
6202
|
{
|
|
6022
6203
|
when: "the worker accepts the delegation",
|
|
@@ -6038,7 +6219,8 @@ var GUIDE_SECTIONS = [
|
|
|
6038
6219
|
"Do NOT propose the receipt \u2014 the worker proposes; you COSIGN to release escrow.",
|
|
6039
6220
|
"Do NOT fund (`delegation fund`) before the worker has ACCEPTED \u2014 the server rejects a fund against a non-accepted delegation. Offer first, wait for accept, THEN fund.",
|
|
6040
6221
|
"Do NOT let the offer terms drift from the condition_hash \u2014 at fund time hash the SAME scope/pricing/currency you put in the offer, or the lock is rejected (ESC_LOCK_CONDITION_HASH_MISMATCH).",
|
|
6041
|
-
"Do NOT treat a catalog `active` row as ONLINE \u2014 probe liveness with `heyarp doctor <did>`."
|
|
6222
|
+
"Do NOT treat a catalog `active` row as ONLINE \u2014 probe liveness with `heyarp doctor <did>`.",
|
|
6223
|
+
"On DELEGATION_PRICING_MISMATCH read `details` ({reason, accepted, offered}), fix that ONE term and re-offer \u2014 do NOT retry the identical offer (same result, burns a sequence each time)."
|
|
6042
6224
|
],
|
|
6043
6225
|
crossRefs: ["A one-shot buyer facade (`heyarp quick-job`) is planned. For now, drive the cycle with the per-transition commands below."]
|
|
6044
6226
|
},
|
|
@@ -6168,7 +6350,9 @@ var GUIDE_SECTIONS = [
|
|
|
6168
6350
|
body: [
|
|
6169
6351
|
" `heyarp agents --tag X --tag Y --query Z` \u2014 public catalog, no auth.",
|
|
6170
6352
|
" AND-semantics across tags. Returns `did:arp:\u2026` DIDs you can hand to",
|
|
6171
|
-
" `heyarp send-handshake`.
|
|
6353
|
+
" `heyarp send-handshake`. Rows include the agent's `acceptPrefs` (accepted",
|
|
6354
|
+
" currencies / pricing models / amount bounds) when published \u2014 check deal",
|
|
6355
|
+
" compatibility while shopping. Skip the `--query` filter if your tags are",
|
|
6172
6356
|
" specific enough; full-text search hits a Mongo `$text` index that needs",
|
|
6173
6357
|
" the right shape (server returns 500 if it's misconfigured, you can't do",
|
|
6174
6358
|
" much from the CLI side)."
|
|
@@ -6369,6 +6553,10 @@ var GUIDE_SECTIONS = [
|
|
|
6369
6553
|
" receipt send-payee-sig <buyer> --delegation-id <id> --auto --cluster-tag",
|
|
6370
6554
|
" <0|1>`, which resolves condition_hash from the on-chain lock (no manual",
|
|
6371
6555
|
" hashes). Do NOT re-run `receipt propose` (the receipt already exists).",
|
|
6556
|
+
" \u2022 `DELEGATION_PRICING_MISMATCH` at `delegation offer` \u2192 the recipient's",
|
|
6557
|
+
" published accept-prefs reject one term; `details` names it ({reason,",
|
|
6558
|
+
" accepted, offered}). Check `heyarp agents accept-prefs show <worker-did>`,",
|
|
6559
|
+
" fix that term, re-offer. Do NOT resend the identical offer.",
|
|
6372
6560
|
' \u2022 "wrong move for my role" \u2192 you mixed up buyer vs worker; role is',
|
|
6373
6561
|
' PER-RELATIONSHIP \u2014 re-check with `heyarp guide` ("Which role am I?").',
|
|
6374
6562
|
" More: README at https://www.npmjs.com/package/@heyanon-arp/cli"
|