edge-book 0.3.0 → 0.5.0
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/edge-book.js +259 -6
- package/package.json +1 -1
package/dist/edge-book.js
CHANGED
|
@@ -17,6 +17,12 @@ import crypto from "crypto";
|
|
|
17
17
|
import fs from "fs/promises";
|
|
18
18
|
import os from "os";
|
|
19
19
|
import path from "path";
|
|
20
|
+
var EPHEMERAL_TTL_POLICY = {
|
|
21
|
+
query: { hard: true },
|
|
22
|
+
delegation_request: { hard: true },
|
|
23
|
+
share: { hard: false },
|
|
24
|
+
coordinate: { hard: false }
|
|
25
|
+
};
|
|
20
26
|
var EdgeBookError = class extends Error {
|
|
21
27
|
code;
|
|
22
28
|
constructor(code, message) {
|
|
@@ -46,7 +52,10 @@ var ATTESTATIONS_FILE = "attestations.json";
|
|
|
46
52
|
var ENDORSEMENTS_FILE = "endorsements.json";
|
|
47
53
|
var SIGNALS_FILE = "signals.json";
|
|
48
54
|
var CAPABILITIES_FILE = "capabilities.json";
|
|
55
|
+
var EPHEMERAL_FILE = "ephemeral-posts.json";
|
|
56
|
+
var ANSWERS_FILE = "answers.json";
|
|
49
57
|
var DEFAULT_SIGNAL_TTL_MS = 6 * 60 * 60 * 1e3;
|
|
58
|
+
var DEFAULT_EPHEMERAL_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
50
59
|
function resolveHome(home) {
|
|
51
60
|
if (home?.trim()) return path.resolve(home.trim());
|
|
52
61
|
if (process.env.EDGE_BOOK_HOME?.trim()) return path.resolve(process.env.EDGE_BOOK_HOME.trim());
|
|
@@ -145,6 +154,13 @@ async function readJsonl(file) {
|
|
|
145
154
|
function relationshipId(a, b) {
|
|
146
155
|
return `rel_${crypto.createHash("sha256").update([a, b].sort().join("|")).digest("base64url").slice(0, 24)}`;
|
|
147
156
|
}
|
|
157
|
+
function computeLifecycle(expiresAt, hard, current) {
|
|
158
|
+
if (current === "expired" || current === "cancelled" || current === "tombstoned") {
|
|
159
|
+
return current;
|
|
160
|
+
}
|
|
161
|
+
if (Date.parse(expiresAt) <= Date.now()) return hard ? "expired" : "stale";
|
|
162
|
+
return "active";
|
|
163
|
+
}
|
|
148
164
|
var EdgeBookStore = class {
|
|
149
165
|
home;
|
|
150
166
|
constructor(options = {}) {
|
|
@@ -218,6 +234,7 @@ var EdgeBookStore = class {
|
|
|
218
234
|
const transports = [{ mode: "local", endpoint: this.home }];
|
|
219
235
|
if (config.direct_url) transports.push({ mode: "direct", endpoint: config.direct_url });
|
|
220
236
|
if (config.relay_url) transports.push({ mode: "relay", endpoint: config.relay_url });
|
|
237
|
+
const caps = Object.values(await this.capabilities()).map((c) => ({ name: c.name, version: c.version, summary: c.summary, status: c.status }));
|
|
221
238
|
const unsigned = {
|
|
222
239
|
schema: "openclaw-agent-card/0.1",
|
|
223
240
|
agent_id: identity.agent_id,
|
|
@@ -229,6 +246,7 @@ var EdgeBookStore = class {
|
|
|
229
246
|
card_version: 1,
|
|
230
247
|
public_keys: [{ id: `${identity.agent_id}#main`, type: "ed25519", public_key_pem: identity.public_key_pem }],
|
|
231
248
|
capabilities: ["friend_request", "friend_gated_message", "feed_read_friends"],
|
|
249
|
+
...caps.length ? { advertised_capabilities: caps } : {},
|
|
232
250
|
transports,
|
|
233
251
|
refresh_after: new Date(Date.now() + 24 * 60 * 60 * 1e3).toISOString(),
|
|
234
252
|
expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3).toISOString()
|
|
@@ -310,6 +328,7 @@ var EdgeBookStore = class {
|
|
|
310
328
|
// Carry the peer's shared human name (undefined if they didn't opt in, or
|
|
311
329
|
// dropped on refresh if they turned sharing off).
|
|
312
330
|
owner_label: card.owner_label,
|
|
331
|
+
advertised_capabilities: card.advertised_capabilities,
|
|
313
332
|
card_url: card.card_url,
|
|
314
333
|
known_endpoints: card.transports,
|
|
315
334
|
public_keys: card.public_keys,
|
|
@@ -470,6 +489,7 @@ var EdgeBookStore = class {
|
|
|
470
489
|
}
|
|
471
490
|
const grant = await this.findUsableGrant(peerAgentId, scope);
|
|
472
491
|
if (!grant) throw new EdgeBookError("missing_grant", `No active grant for ${scope}`);
|
|
492
|
+
await this.assertGrantSignature(grant);
|
|
473
493
|
const envelope = await this.signEnvelope({
|
|
474
494
|
type: "privileged_message",
|
|
475
495
|
to_agent_id: peerAgentId,
|
|
@@ -497,6 +517,7 @@ var EdgeBookStore = class {
|
|
|
497
517
|
if (!grant || grant.status !== "active" || grant.subject_agent_id !== envelope.from_agent_id || !grant.scopes.includes("message.friend")) {
|
|
498
518
|
throw new EdgeBookError("missing_grant", "Message does not carry an active grant issued to sender");
|
|
499
519
|
}
|
|
520
|
+
await this.assertGrantSignature(grant);
|
|
500
521
|
await appendJsonl(this.file(INBOX_FILE), envelope);
|
|
501
522
|
await this.audit("message.receive", envelope.from_agent_id, { message_id: envelope.message_id });
|
|
502
523
|
}
|
|
@@ -614,6 +635,43 @@ var EdgeBookStore = class {
|
|
|
614
635
|
const { signature, ...rest } = cap;
|
|
615
636
|
return verifyPayload(rest, signature, pub);
|
|
616
637
|
}
|
|
638
|
+
// Verify an EphemeralPost signature. lifecycle is NOT part of the signed payload
|
|
639
|
+
// (it is mutable local metadata), so strip both signature and lifecycle before verify.
|
|
640
|
+
async verifyEphemeral(post) {
|
|
641
|
+
const identity = await this.identity();
|
|
642
|
+
let pub = identity.agent_id === post.from_agent ? identity.public_key_pem : void 0;
|
|
643
|
+
if (!pub) {
|
|
644
|
+
const c = (await this.contacts())[post.from_agent];
|
|
645
|
+
pub = c?.public_keys?.[0]?.public_key_pem;
|
|
646
|
+
}
|
|
647
|
+
if (!pub) return false;
|
|
648
|
+
const { signature, lifecycle: _lc, ...signedPayload } = post;
|
|
649
|
+
return verifyPayload(signedPayload, signature, pub);
|
|
650
|
+
}
|
|
651
|
+
// Verify an Answer signature. lifecycle is NOT part of the signed payload.
|
|
652
|
+
async verifyAnswer(ans) {
|
|
653
|
+
const identity = await this.identity();
|
|
654
|
+
let pub = identity.agent_id === ans.answerer_agent_id ? identity.public_key_pem : void 0;
|
|
655
|
+
if (!pub) {
|
|
656
|
+
const c = (await this.contacts())[ans.answerer_agent_id];
|
|
657
|
+
pub = c?.public_keys?.[0]?.public_key_pem;
|
|
658
|
+
}
|
|
659
|
+
if (!pub) return false;
|
|
660
|
+
const { signature, lifecycle: _lc, ...signedPayload } = ans;
|
|
661
|
+
return verifyPayload(signedPayload, signature, pub);
|
|
662
|
+
}
|
|
663
|
+
// Verify a Signal signature. lifecycle is NOT part of the signed payload.
|
|
664
|
+
async verifySignal(sig) {
|
|
665
|
+
const identity = await this.identity();
|
|
666
|
+
let pub = identity.agent_id === sig.from_agent ? identity.public_key_pem : void 0;
|
|
667
|
+
if (!pub) {
|
|
668
|
+
const c = (await this.contacts())[sig.from_agent];
|
|
669
|
+
pub = c?.public_keys?.[0]?.public_key_pem;
|
|
670
|
+
}
|
|
671
|
+
if (!pub) return false;
|
|
672
|
+
const { signature, lifecycle: _lc, ...signedPayload } = sig;
|
|
673
|
+
return verifyPayload(signedPayload, signature, pub);
|
|
674
|
+
}
|
|
617
675
|
// Class 3: Endorse — actor-owned reified edge, strongRef parent, evidence link (R5, R8)
|
|
618
676
|
async endorsements() {
|
|
619
677
|
return readJson(this.file(ENDORSEMENTS_FILE), {});
|
|
@@ -650,8 +708,7 @@ var EdgeBookStore = class {
|
|
|
650
708
|
}
|
|
651
709
|
// Class 2: Signal — ephemeral, lifecycle + TTL (R4)
|
|
652
710
|
signalLifecycle(sig) {
|
|
653
|
-
|
|
654
|
-
return Date.parse(sig.expires_at) <= Date.now() ? "stale" : "active";
|
|
711
|
+
return computeLifecycle(sig.expires_at, false, sig.lifecycle);
|
|
655
712
|
}
|
|
656
713
|
async signals() {
|
|
657
714
|
const raw = await readJson(this.file(SIGNALS_FILE), {});
|
|
@@ -669,11 +726,10 @@ var EdgeBookStore = class {
|
|
|
669
726
|
schema: "edge-book/signal/0.1",
|
|
670
727
|
from_agent: identity.agent_id,
|
|
671
728
|
body: input.body,
|
|
672
|
-
lifecycle: "active",
|
|
673
729
|
created_at: created,
|
|
674
730
|
expires_at
|
|
675
731
|
};
|
|
676
|
-
const signal = { ...unsigned, signature: signPayload(unsigned, identity.private_key_pem) };
|
|
732
|
+
const signal = { ...unsigned, lifecycle: "active", signature: signPayload(unsigned, identity.private_key_pem) };
|
|
677
733
|
const all = await this.signals();
|
|
678
734
|
all[signal_id] = signal;
|
|
679
735
|
await this.saveSignals(all);
|
|
@@ -691,6 +747,111 @@ var EdgeBookStore = class {
|
|
|
691
747
|
}
|
|
692
748
|
if (changed) await this.saveSignals(all);
|
|
693
749
|
}
|
|
750
|
+
// Generic Class-2 ephemeral store (query/share/coordinate/delegation_request, R2/R4)
|
|
751
|
+
async saveEphemeral(posts) {
|
|
752
|
+
await writeJson(this.file(EPHEMERAL_FILE), posts);
|
|
753
|
+
}
|
|
754
|
+
async ephemeralPosts() {
|
|
755
|
+
const raw = await readJson(this.file(EPHEMERAL_FILE), {});
|
|
756
|
+
for (const id of Object.keys(raw)) {
|
|
757
|
+
raw[id].lifecycle = computeLifecycle(raw[id].expires_at, EPHEMERAL_TTL_POLICY[raw[id].post_type].hard, raw[id].lifecycle);
|
|
758
|
+
}
|
|
759
|
+
return raw;
|
|
760
|
+
}
|
|
761
|
+
async createEphemeral(type, input) {
|
|
762
|
+
if (!EPHEMERAL_TTL_POLICY[type]) throw new EdgeBookError("unknown_post_type", `Not an ephemeral Class-2 type: ${type}`);
|
|
763
|
+
const identity = await this.identity();
|
|
764
|
+
const post_id = randomId("eph");
|
|
765
|
+
const created = now();
|
|
766
|
+
const expires_at = new Date(Date.now() + (input.ttlMs ?? DEFAULT_EPHEMERAL_TTL_MS)).toISOString();
|
|
767
|
+
const unsigned = {
|
|
768
|
+
post_id,
|
|
769
|
+
post_type: type,
|
|
770
|
+
schema: "edge-book/ephemeral/0.1",
|
|
771
|
+
from_agent: identity.agent_id,
|
|
772
|
+
body: input.body,
|
|
773
|
+
...input.subject_agent_id ? { subject_agent_id: input.subject_agent_id } : {},
|
|
774
|
+
...input.ref ? { ref: input.ref } : {},
|
|
775
|
+
created_at: created,
|
|
776
|
+
expires_at
|
|
777
|
+
};
|
|
778
|
+
const post = { ...unsigned, lifecycle: "active", signature: signPayload(unsigned, identity.private_key_pem) };
|
|
779
|
+
const all = await this.ephemeralPosts();
|
|
780
|
+
all[post_id] = post;
|
|
781
|
+
await this.saveEphemeral(all);
|
|
782
|
+
await this.audit(type + ".create", identity.agent_id, { post_id, ...input.subject_agent_id ? { subject_agent_id: input.subject_agent_id } : {} });
|
|
783
|
+
return post;
|
|
784
|
+
}
|
|
785
|
+
async expireEphemeral() {
|
|
786
|
+
const all = await readJson(this.file(EPHEMERAL_FILE), {});
|
|
787
|
+
let changed = false;
|
|
788
|
+
for (const id of Object.keys(all)) {
|
|
789
|
+
const next = computeLifecycle(all[id].expires_at, EPHEMERAL_TTL_POLICY[all[id].post_type].hard, all[id].lifecycle);
|
|
790
|
+
if (next !== all[id].lifecycle) {
|
|
791
|
+
all[id].lifecycle = next;
|
|
792
|
+
changed = true;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
if (changed) await this.saveEphemeral(all);
|
|
796
|
+
}
|
|
797
|
+
async cancelEphemeral(postId) {
|
|
798
|
+
const all = await readJson(this.file(EPHEMERAL_FILE), {});
|
|
799
|
+
const post = all[postId];
|
|
800
|
+
if (!post) throw new EdgeBookError("not_found", `No ephemeral post ${postId}`);
|
|
801
|
+
post.lifecycle = "cancelled";
|
|
802
|
+
await this.saveEphemeral(all);
|
|
803
|
+
await this.audit("ephemeral.cancel", post.from_agent, { post_id: postId });
|
|
804
|
+
return post;
|
|
805
|
+
}
|
|
806
|
+
// Class 3: Answer — actor-owned, strongRef to a Query (R5)
|
|
807
|
+
async saveAnswers(answers) {
|
|
808
|
+
await writeJson(this.file(ANSWERS_FILE), answers);
|
|
809
|
+
}
|
|
810
|
+
async answers() {
|
|
811
|
+
return readJson(this.file(ANSWERS_FILE), {});
|
|
812
|
+
}
|
|
813
|
+
async createAnswer(input) {
|
|
814
|
+
if (!input.parent?.uri || !input.parent?.hash) {
|
|
815
|
+
throw new EdgeBookError("missing_parent", "Answer requires a strongRef parent (uri + hash) \u2014 R5");
|
|
816
|
+
}
|
|
817
|
+
const identity = await this.identity();
|
|
818
|
+
const answer_id = randomId("ans");
|
|
819
|
+
const unsigned = {
|
|
820
|
+
answer_id,
|
|
821
|
+
post_type: "answer",
|
|
822
|
+
schema: "edge-book/answer/0.1",
|
|
823
|
+
answerer_agent_id: identity.agent_id,
|
|
824
|
+
// actor-owned (R5)
|
|
825
|
+
parent: input.parent,
|
|
826
|
+
body: input.body,
|
|
827
|
+
created_at: now()
|
|
828
|
+
};
|
|
829
|
+
const answer = { ...unsigned, lifecycle: "active", signature: signPayload(unsigned, identity.private_key_pem) };
|
|
830
|
+
const all = await this.answers();
|
|
831
|
+
all[answer_id] = answer;
|
|
832
|
+
await this.saveAnswers(all);
|
|
833
|
+
await this.audit("answer.create", identity.agent_id, { answer_id, parent: input.parent.uri });
|
|
834
|
+
return answer;
|
|
835
|
+
}
|
|
836
|
+
// R7: deleting a Query tombstones (archives) it AND its Answers — never hard-drops.
|
|
837
|
+
async deleteQuery(queryId) {
|
|
838
|
+
const eph = await readJson(this.file(EPHEMERAL_FILE), {});
|
|
839
|
+
const q = eph[queryId];
|
|
840
|
+
if (!q || q.post_type !== "query") throw new EdgeBookError("not_found", `No query ${queryId}`);
|
|
841
|
+
q.lifecycle = "tombstoned";
|
|
842
|
+
await this.saveEphemeral(eph);
|
|
843
|
+
const parentUri = "edgebook:query:" + queryId;
|
|
844
|
+
const ans = await this.answers();
|
|
845
|
+
let changed = false;
|
|
846
|
+
for (const id of Object.keys(ans)) {
|
|
847
|
+
if (ans[id].parent.uri === parentUri && ans[id].lifecycle !== "tombstoned") {
|
|
848
|
+
ans[id].lifecycle = "tombstoned";
|
|
849
|
+
changed = true;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
if (changed) await this.saveAnswers(ans);
|
|
853
|
+
await this.audit("query.delete", q.from_agent, { query_id: queryId });
|
|
854
|
+
}
|
|
694
855
|
// Class 1: Capability Advertisement — versioned, deprecate-not-delete (R3)
|
|
695
856
|
async capabilities() {
|
|
696
857
|
return readJson(this.file(CAPABILITIES_FILE), {});
|
|
@@ -745,8 +906,21 @@ var EdgeBookStore = class {
|
|
|
745
906
|
}
|
|
746
907
|
await this.saveCapabilities(caps);
|
|
747
908
|
const sigs = await readJson(this.file(SIGNALS_FILE), {});
|
|
748
|
-
for (const id of Object.keys(sigs))
|
|
909
|
+
for (const id of Object.keys(sigs)) {
|
|
910
|
+
if (sigs[id].lifecycle !== "expired") sigs[id].lifecycle = "expired";
|
|
911
|
+
}
|
|
749
912
|
await this.saveSignals(sigs);
|
|
913
|
+
const eph = await readJson(this.file(EPHEMERAL_FILE), {});
|
|
914
|
+
for (const id of Object.keys(eph)) {
|
|
915
|
+
const lc = eph[id].lifecycle;
|
|
916
|
+
if (lc === "expired" || lc === "cancelled" || lc === "tombstoned") continue;
|
|
917
|
+
const t = eph[id].post_type;
|
|
918
|
+
eph[id].lifecycle = t === "query" || t === "delegation_request" ? "cancelled" : "expired";
|
|
919
|
+
}
|
|
920
|
+
await this.saveEphemeral(eph);
|
|
921
|
+
const ans = await readJson(this.file(ANSWERS_FILE), {});
|
|
922
|
+
for (const id of Object.keys(ans)) if (ans[id].lifecycle !== "tombstoned") ans[id].lifecycle = "tombstoned";
|
|
923
|
+
await this.saveAnswers(ans);
|
|
750
924
|
await this.audit("agent.deregister", (await this.identity()).agent_id, {});
|
|
751
925
|
}
|
|
752
926
|
// Issue an `object.read` grant binding ONE object to ONE subject (revocable).
|
|
@@ -917,6 +1091,34 @@ var EdgeBookStore = class {
|
|
|
917
1091
|
(grant) => grant.issuer_agent_id === peerAgentId && grant.subject_agent_id === identity.agent_id && grant.status === "active" && grant.scopes.includes(scope) && (!grant.expires_at || Date.parse(grant.expires_at) > Date.now())
|
|
918
1092
|
);
|
|
919
1093
|
}
|
|
1094
|
+
// ea-openclaw-030 access check #6: a grant authorizes access only if its
|
|
1095
|
+
// issuer signature verifies against the issuer's accepted public key. Grants
|
|
1096
|
+
// are signed on issue (signPayload) but must be re-verified on use so that a
|
|
1097
|
+
// grant tampered after signing, or presented independently of its issuing
|
|
1098
|
+
// envelope, fails closed. Resolves the issuer key from local identity when
|
|
1099
|
+
// self-issued, else from the issuer's contact record.
|
|
1100
|
+
async verifyGrantSignature(grant) {
|
|
1101
|
+
if (!grant.signature) return false;
|
|
1102
|
+
const identity = await this.identity();
|
|
1103
|
+
let publicKey;
|
|
1104
|
+
if (grant.issuer_agent_id === identity.agent_id) {
|
|
1105
|
+
publicKey = identity.public_key_pem;
|
|
1106
|
+
} else {
|
|
1107
|
+
const contacts = await this.contacts();
|
|
1108
|
+
publicKey = contacts[grant.issuer_agent_id]?.public_keys?.[0]?.public_key_pem;
|
|
1109
|
+
}
|
|
1110
|
+
if (!publicKey) return false;
|
|
1111
|
+
return verifyPayload(withoutSignature(grant), grant.signature, publicKey);
|
|
1112
|
+
}
|
|
1113
|
+
// Throwing guard used by every friend-gated access path so the signature
|
|
1114
|
+
// check lives in exactly one place (ea-openclaw-031: build the grant-check
|
|
1115
|
+
// primitive once, have all sites consume it).
|
|
1116
|
+
async assertGrantSignature(grant) {
|
|
1117
|
+
if (!await this.verifyGrantSignature(grant)) {
|
|
1118
|
+
await this.audit("grant.denied", grant.issuer_agent_id, { grant_id: grant.grant_id, reason: "invalid_grant_signature" });
|
|
1119
|
+
throw new EdgeBookError("invalid_grant_signature", "Grant signature does not verify against the issuer key");
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
920
1122
|
async signEnvelope(input) {
|
|
921
1123
|
const identity = await this.identity();
|
|
922
1124
|
const unsigned = {
|
|
@@ -1227,6 +1429,7 @@ var EdgeBookStore = class {
|
|
|
1227
1429
|
(candidate) => candidate.issuer_agent_id === identity.agent_id && candidate.subject_agent_id === peerAgentId && candidate.status === "active" && candidate.scopes.includes("feed.read.friends") && (!candidate.expires_at || Date.parse(candidate.expires_at) > Date.now())
|
|
1228
1430
|
);
|
|
1229
1431
|
if (!grant) throw new EdgeBookError("missing_grant", "No active feed.read.friends grant for peer");
|
|
1432
|
+
await this.assertGrantSignature(grant);
|
|
1230
1433
|
const posts = Object.values(await this.posts());
|
|
1231
1434
|
return posts.filter((post) => post.visibility === "friends" && ["published", "edited"].includes(post.status)).filter((post) => !post.expires_at || Date.parse(post.expires_at) > Date.now()).sort((a, b) => b.updated_at.localeCompare(a.updated_at));
|
|
1232
1435
|
}
|
|
@@ -1567,6 +1770,14 @@ async function handleOwnerApi(req, res, url, adapters) {
|
|
|
1567
1770
|
sendJson(res, 200, { capabilities: await store.capabilities() });
|
|
1568
1771
|
return true;
|
|
1569
1772
|
}
|
|
1773
|
+
if (req.method === "GET" && url.pathname === "/api/ephemeral") {
|
|
1774
|
+
sendJson(res, 200, { ephemeral: await store.ephemeralPosts() });
|
|
1775
|
+
return true;
|
|
1776
|
+
}
|
|
1777
|
+
if (req.method === "GET" && url.pathname === "/api/answers") {
|
|
1778
|
+
sendJson(res, 200, { answers: await store.answers() });
|
|
1779
|
+
return true;
|
|
1780
|
+
}
|
|
1570
1781
|
if (req.method === "GET" && url.pathname === "/api/shared-objects") {
|
|
1571
1782
|
const objects = await store.sharedObjectsFor();
|
|
1572
1783
|
sendJson(res, 200, { objects: objects.map((object) => ({ ...object, grant_scope: "object.read" })) });
|
|
@@ -3434,7 +3645,15 @@ Post taxonomy (spec-0021):
|
|
|
3434
3645
|
edge-book signal --body <s> [--ttl-ms <ms>]
|
|
3435
3646
|
edge-book capability advertise --name <n> --version <v> --summary <s>
|
|
3436
3647
|
edge-book capability deprecate <capability-id>
|
|
3437
|
-
edge-book capability list
|
|
3648
|
+
edge-book capability list
|
|
3649
|
+
edge-book query --body <s> [--ttl-ms <ms>]
|
|
3650
|
+
edge-book share --body <s> [--ref <r>] [--ttl-ms <ms>]
|
|
3651
|
+
edge-book coordinate --body <s> [--with <agent>] [--ttl-ms <ms>]
|
|
3652
|
+
edge-book delegate --to <agent> --body <s> [--ttl-ms <ms>]
|
|
3653
|
+
edge-book answer <query-id> --body <s>
|
|
3654
|
+
edge-book query-delete <query-id>
|
|
3655
|
+
edge-book ephemeral # list Class-2 ephemeral posts
|
|
3656
|
+
edge-book answers # list answers`;
|
|
3438
3657
|
}
|
|
3439
3658
|
function takeFlag(args, name) {
|
|
3440
3659
|
const idx = args.indexOf(name);
|
|
@@ -3825,6 +4044,40 @@ ${JSON.stringify(result, null, 2)}`, json: result };
|
|
|
3825
4044
|
}
|
|
3826
4045
|
throw new EdgeBookError("unknown_action", `Unknown capability action: ${action}`);
|
|
3827
4046
|
}
|
|
4047
|
+
if (command === "query" || command === "share" || command === "coordinate" || command === "delegate") {
|
|
4048
|
+
const type = command === "delegate" ? "delegation_request" : command;
|
|
4049
|
+
const body = requireArg(takeFlag(args, "--body"), "--body");
|
|
4050
|
+
const to = takeFlag(args, "--to") || takeFlag(args, "--with");
|
|
4051
|
+
const ref = takeFlag(args, "--ref");
|
|
4052
|
+
const ttl = takeFlag(args, "--ttl-ms");
|
|
4053
|
+
const post = await store.createEphemeral(type, { body, subject_agent_id: to, ref, ttlMs: ttl ? Number(ttl) : void 0 });
|
|
4054
|
+
return { text: `${post.post_type} ${post.post_id}`, json: post };
|
|
4055
|
+
}
|
|
4056
|
+
if (command === "answer") {
|
|
4057
|
+
const queryId = requireArg(args.shift(), "<query-id>");
|
|
4058
|
+
const ephemeral = await store.ephemeralPosts();
|
|
4059
|
+
const query = ephemeral[queryId];
|
|
4060
|
+
if (!query) throw new EdgeBookError("not_found", `No local query ${queryId} to answer`);
|
|
4061
|
+
const { signature: _sig, lifecycle: _lc, ...queryUnsigned } = query;
|
|
4062
|
+
const ans = await store.createAnswer({
|
|
4063
|
+
parent: { uri: "edgebook:query:" + queryId, hash: contentHash(queryUnsigned) },
|
|
4064
|
+
body: requireArg(takeFlag(args, "--body"), "--body")
|
|
4065
|
+
});
|
|
4066
|
+
return { text: `answer ${ans.answer_id}`, json: ans };
|
|
4067
|
+
}
|
|
4068
|
+
if (command === "query-delete") {
|
|
4069
|
+
const queryId = requireArg(args.shift(), "<query-id>");
|
|
4070
|
+
await store.deleteQuery(queryId);
|
|
4071
|
+
return { text: `Tombstoned query ${queryId} and its answers`, json: { query_id: queryId } };
|
|
4072
|
+
}
|
|
4073
|
+
if (command === "ephemeral") {
|
|
4074
|
+
const all = await store.ephemeralPosts();
|
|
4075
|
+
return { text: JSON.stringify(all, null, 2), json: all };
|
|
4076
|
+
}
|
|
4077
|
+
if (command === "answers") {
|
|
4078
|
+
const all = await store.answers();
|
|
4079
|
+
return { text: JSON.stringify(all, null, 2), json: all };
|
|
4080
|
+
}
|
|
3828
4081
|
throw new EdgeBookError("unknown_command", usage());
|
|
3829
4082
|
}
|
|
3830
4083
|
async function runCli(args) {
|