edge-book 0.3.0 → 0.4.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 +256 -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 = {}) {
|
|
@@ -470,6 +486,7 @@ var EdgeBookStore = class {
|
|
|
470
486
|
}
|
|
471
487
|
const grant = await this.findUsableGrant(peerAgentId, scope);
|
|
472
488
|
if (!grant) throw new EdgeBookError("missing_grant", `No active grant for ${scope}`);
|
|
489
|
+
await this.assertGrantSignature(grant);
|
|
473
490
|
const envelope = await this.signEnvelope({
|
|
474
491
|
type: "privileged_message",
|
|
475
492
|
to_agent_id: peerAgentId,
|
|
@@ -497,6 +514,7 @@ var EdgeBookStore = class {
|
|
|
497
514
|
if (!grant || grant.status !== "active" || grant.subject_agent_id !== envelope.from_agent_id || !grant.scopes.includes("message.friend")) {
|
|
498
515
|
throw new EdgeBookError("missing_grant", "Message does not carry an active grant issued to sender");
|
|
499
516
|
}
|
|
517
|
+
await this.assertGrantSignature(grant);
|
|
500
518
|
await appendJsonl(this.file(INBOX_FILE), envelope);
|
|
501
519
|
await this.audit("message.receive", envelope.from_agent_id, { message_id: envelope.message_id });
|
|
502
520
|
}
|
|
@@ -614,6 +632,43 @@ var EdgeBookStore = class {
|
|
|
614
632
|
const { signature, ...rest } = cap;
|
|
615
633
|
return verifyPayload(rest, signature, pub);
|
|
616
634
|
}
|
|
635
|
+
// Verify an EphemeralPost signature. lifecycle is NOT part of the signed payload
|
|
636
|
+
// (it is mutable local metadata), so strip both signature and lifecycle before verify.
|
|
637
|
+
async verifyEphemeral(post) {
|
|
638
|
+
const identity = await this.identity();
|
|
639
|
+
let pub = identity.agent_id === post.from_agent ? identity.public_key_pem : void 0;
|
|
640
|
+
if (!pub) {
|
|
641
|
+
const c = (await this.contacts())[post.from_agent];
|
|
642
|
+
pub = c?.public_keys?.[0]?.public_key_pem;
|
|
643
|
+
}
|
|
644
|
+
if (!pub) return false;
|
|
645
|
+
const { signature, lifecycle: _lc, ...signedPayload } = post;
|
|
646
|
+
return verifyPayload(signedPayload, signature, pub);
|
|
647
|
+
}
|
|
648
|
+
// Verify an Answer signature. lifecycle is NOT part of the signed payload.
|
|
649
|
+
async verifyAnswer(ans) {
|
|
650
|
+
const identity = await this.identity();
|
|
651
|
+
let pub = identity.agent_id === ans.answerer_agent_id ? identity.public_key_pem : void 0;
|
|
652
|
+
if (!pub) {
|
|
653
|
+
const c = (await this.contacts())[ans.answerer_agent_id];
|
|
654
|
+
pub = c?.public_keys?.[0]?.public_key_pem;
|
|
655
|
+
}
|
|
656
|
+
if (!pub) return false;
|
|
657
|
+
const { signature, lifecycle: _lc, ...signedPayload } = ans;
|
|
658
|
+
return verifyPayload(signedPayload, signature, pub);
|
|
659
|
+
}
|
|
660
|
+
// Verify a Signal signature. lifecycle is NOT part of the signed payload.
|
|
661
|
+
async verifySignal(sig) {
|
|
662
|
+
const identity = await this.identity();
|
|
663
|
+
let pub = identity.agent_id === sig.from_agent ? identity.public_key_pem : void 0;
|
|
664
|
+
if (!pub) {
|
|
665
|
+
const c = (await this.contacts())[sig.from_agent];
|
|
666
|
+
pub = c?.public_keys?.[0]?.public_key_pem;
|
|
667
|
+
}
|
|
668
|
+
if (!pub) return false;
|
|
669
|
+
const { signature, lifecycle: _lc, ...signedPayload } = sig;
|
|
670
|
+
return verifyPayload(signedPayload, signature, pub);
|
|
671
|
+
}
|
|
617
672
|
// Class 3: Endorse — actor-owned reified edge, strongRef parent, evidence link (R5, R8)
|
|
618
673
|
async endorsements() {
|
|
619
674
|
return readJson(this.file(ENDORSEMENTS_FILE), {});
|
|
@@ -650,8 +705,7 @@ var EdgeBookStore = class {
|
|
|
650
705
|
}
|
|
651
706
|
// Class 2: Signal — ephemeral, lifecycle + TTL (R4)
|
|
652
707
|
signalLifecycle(sig) {
|
|
653
|
-
|
|
654
|
-
return Date.parse(sig.expires_at) <= Date.now() ? "stale" : "active";
|
|
708
|
+
return computeLifecycle(sig.expires_at, false, sig.lifecycle);
|
|
655
709
|
}
|
|
656
710
|
async signals() {
|
|
657
711
|
const raw = await readJson(this.file(SIGNALS_FILE), {});
|
|
@@ -669,11 +723,10 @@ var EdgeBookStore = class {
|
|
|
669
723
|
schema: "edge-book/signal/0.1",
|
|
670
724
|
from_agent: identity.agent_id,
|
|
671
725
|
body: input.body,
|
|
672
|
-
lifecycle: "active",
|
|
673
726
|
created_at: created,
|
|
674
727
|
expires_at
|
|
675
728
|
};
|
|
676
|
-
const signal = { ...unsigned, signature: signPayload(unsigned, identity.private_key_pem) };
|
|
729
|
+
const signal = { ...unsigned, lifecycle: "active", signature: signPayload(unsigned, identity.private_key_pem) };
|
|
677
730
|
const all = await this.signals();
|
|
678
731
|
all[signal_id] = signal;
|
|
679
732
|
await this.saveSignals(all);
|
|
@@ -691,6 +744,111 @@ var EdgeBookStore = class {
|
|
|
691
744
|
}
|
|
692
745
|
if (changed) await this.saveSignals(all);
|
|
693
746
|
}
|
|
747
|
+
// Generic Class-2 ephemeral store (query/share/coordinate/delegation_request, R2/R4)
|
|
748
|
+
async saveEphemeral(posts) {
|
|
749
|
+
await writeJson(this.file(EPHEMERAL_FILE), posts);
|
|
750
|
+
}
|
|
751
|
+
async ephemeralPosts() {
|
|
752
|
+
const raw = await readJson(this.file(EPHEMERAL_FILE), {});
|
|
753
|
+
for (const id of Object.keys(raw)) {
|
|
754
|
+
raw[id].lifecycle = computeLifecycle(raw[id].expires_at, EPHEMERAL_TTL_POLICY[raw[id].post_type].hard, raw[id].lifecycle);
|
|
755
|
+
}
|
|
756
|
+
return raw;
|
|
757
|
+
}
|
|
758
|
+
async createEphemeral(type, input) {
|
|
759
|
+
if (!EPHEMERAL_TTL_POLICY[type]) throw new EdgeBookError("unknown_post_type", `Not an ephemeral Class-2 type: ${type}`);
|
|
760
|
+
const identity = await this.identity();
|
|
761
|
+
const post_id = randomId("eph");
|
|
762
|
+
const created = now();
|
|
763
|
+
const expires_at = new Date(Date.now() + (input.ttlMs ?? DEFAULT_EPHEMERAL_TTL_MS)).toISOString();
|
|
764
|
+
const unsigned = {
|
|
765
|
+
post_id,
|
|
766
|
+
post_type: type,
|
|
767
|
+
schema: "edge-book/ephemeral/0.1",
|
|
768
|
+
from_agent: identity.agent_id,
|
|
769
|
+
body: input.body,
|
|
770
|
+
...input.subject_agent_id ? { subject_agent_id: input.subject_agent_id } : {},
|
|
771
|
+
...input.ref ? { ref: input.ref } : {},
|
|
772
|
+
created_at: created,
|
|
773
|
+
expires_at
|
|
774
|
+
};
|
|
775
|
+
const post = { ...unsigned, lifecycle: "active", signature: signPayload(unsigned, identity.private_key_pem) };
|
|
776
|
+
const all = await this.ephemeralPosts();
|
|
777
|
+
all[post_id] = post;
|
|
778
|
+
await this.saveEphemeral(all);
|
|
779
|
+
await this.audit(type + ".create", identity.agent_id, { post_id, ...input.subject_agent_id ? { subject_agent_id: input.subject_agent_id } : {} });
|
|
780
|
+
return post;
|
|
781
|
+
}
|
|
782
|
+
async expireEphemeral() {
|
|
783
|
+
const all = await readJson(this.file(EPHEMERAL_FILE), {});
|
|
784
|
+
let changed = false;
|
|
785
|
+
for (const id of Object.keys(all)) {
|
|
786
|
+
const next = computeLifecycle(all[id].expires_at, EPHEMERAL_TTL_POLICY[all[id].post_type].hard, all[id].lifecycle);
|
|
787
|
+
if (next !== all[id].lifecycle) {
|
|
788
|
+
all[id].lifecycle = next;
|
|
789
|
+
changed = true;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
if (changed) await this.saveEphemeral(all);
|
|
793
|
+
}
|
|
794
|
+
async cancelEphemeral(postId) {
|
|
795
|
+
const all = await readJson(this.file(EPHEMERAL_FILE), {});
|
|
796
|
+
const post = all[postId];
|
|
797
|
+
if (!post) throw new EdgeBookError("not_found", `No ephemeral post ${postId}`);
|
|
798
|
+
post.lifecycle = "cancelled";
|
|
799
|
+
await this.saveEphemeral(all);
|
|
800
|
+
await this.audit("ephemeral.cancel", post.from_agent, { post_id: postId });
|
|
801
|
+
return post;
|
|
802
|
+
}
|
|
803
|
+
// Class 3: Answer — actor-owned, strongRef to a Query (R5)
|
|
804
|
+
async saveAnswers(answers) {
|
|
805
|
+
await writeJson(this.file(ANSWERS_FILE), answers);
|
|
806
|
+
}
|
|
807
|
+
async answers() {
|
|
808
|
+
return readJson(this.file(ANSWERS_FILE), {});
|
|
809
|
+
}
|
|
810
|
+
async createAnswer(input) {
|
|
811
|
+
if (!input.parent?.uri || !input.parent?.hash) {
|
|
812
|
+
throw new EdgeBookError("missing_parent", "Answer requires a strongRef parent (uri + hash) \u2014 R5");
|
|
813
|
+
}
|
|
814
|
+
const identity = await this.identity();
|
|
815
|
+
const answer_id = randomId("ans");
|
|
816
|
+
const unsigned = {
|
|
817
|
+
answer_id,
|
|
818
|
+
post_type: "answer",
|
|
819
|
+
schema: "edge-book/answer/0.1",
|
|
820
|
+
answerer_agent_id: identity.agent_id,
|
|
821
|
+
// actor-owned (R5)
|
|
822
|
+
parent: input.parent,
|
|
823
|
+
body: input.body,
|
|
824
|
+
created_at: now()
|
|
825
|
+
};
|
|
826
|
+
const answer = { ...unsigned, lifecycle: "active", signature: signPayload(unsigned, identity.private_key_pem) };
|
|
827
|
+
const all = await this.answers();
|
|
828
|
+
all[answer_id] = answer;
|
|
829
|
+
await this.saveAnswers(all);
|
|
830
|
+
await this.audit("answer.create", identity.agent_id, { answer_id, parent: input.parent.uri });
|
|
831
|
+
return answer;
|
|
832
|
+
}
|
|
833
|
+
// R7: deleting a Query tombstones (archives) it AND its Answers — never hard-drops.
|
|
834
|
+
async deleteQuery(queryId) {
|
|
835
|
+
const eph = await readJson(this.file(EPHEMERAL_FILE), {});
|
|
836
|
+
const q = eph[queryId];
|
|
837
|
+
if (!q || q.post_type !== "query") throw new EdgeBookError("not_found", `No query ${queryId}`);
|
|
838
|
+
q.lifecycle = "tombstoned";
|
|
839
|
+
await this.saveEphemeral(eph);
|
|
840
|
+
const parentUri = "edgebook:query:" + queryId;
|
|
841
|
+
const ans = await this.answers();
|
|
842
|
+
let changed = false;
|
|
843
|
+
for (const id of Object.keys(ans)) {
|
|
844
|
+
if (ans[id].parent.uri === parentUri && ans[id].lifecycle !== "tombstoned") {
|
|
845
|
+
ans[id].lifecycle = "tombstoned";
|
|
846
|
+
changed = true;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
if (changed) await this.saveAnswers(ans);
|
|
850
|
+
await this.audit("query.delete", q.from_agent, { query_id: queryId });
|
|
851
|
+
}
|
|
694
852
|
// Class 1: Capability Advertisement — versioned, deprecate-not-delete (R3)
|
|
695
853
|
async capabilities() {
|
|
696
854
|
return readJson(this.file(CAPABILITIES_FILE), {});
|
|
@@ -745,8 +903,21 @@ var EdgeBookStore = class {
|
|
|
745
903
|
}
|
|
746
904
|
await this.saveCapabilities(caps);
|
|
747
905
|
const sigs = await readJson(this.file(SIGNALS_FILE), {});
|
|
748
|
-
for (const id of Object.keys(sigs))
|
|
906
|
+
for (const id of Object.keys(sigs)) {
|
|
907
|
+
if (sigs[id].lifecycle !== "expired") sigs[id].lifecycle = "expired";
|
|
908
|
+
}
|
|
749
909
|
await this.saveSignals(sigs);
|
|
910
|
+
const eph = await readJson(this.file(EPHEMERAL_FILE), {});
|
|
911
|
+
for (const id of Object.keys(eph)) {
|
|
912
|
+
const lc = eph[id].lifecycle;
|
|
913
|
+
if (lc === "expired" || lc === "cancelled" || lc === "tombstoned") continue;
|
|
914
|
+
const t = eph[id].post_type;
|
|
915
|
+
eph[id].lifecycle = t === "query" || t === "delegation_request" ? "cancelled" : "expired";
|
|
916
|
+
}
|
|
917
|
+
await this.saveEphemeral(eph);
|
|
918
|
+
const ans = await readJson(this.file(ANSWERS_FILE), {});
|
|
919
|
+
for (const id of Object.keys(ans)) if (ans[id].lifecycle !== "tombstoned") ans[id].lifecycle = "tombstoned";
|
|
920
|
+
await this.saveAnswers(ans);
|
|
750
921
|
await this.audit("agent.deregister", (await this.identity()).agent_id, {});
|
|
751
922
|
}
|
|
752
923
|
// Issue an `object.read` grant binding ONE object to ONE subject (revocable).
|
|
@@ -917,6 +1088,34 @@ var EdgeBookStore = class {
|
|
|
917
1088
|
(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
1089
|
);
|
|
919
1090
|
}
|
|
1091
|
+
// ea-openclaw-030 access check #6: a grant authorizes access only if its
|
|
1092
|
+
// issuer signature verifies against the issuer's accepted public key. Grants
|
|
1093
|
+
// are signed on issue (signPayload) but must be re-verified on use so that a
|
|
1094
|
+
// grant tampered after signing, or presented independently of its issuing
|
|
1095
|
+
// envelope, fails closed. Resolves the issuer key from local identity when
|
|
1096
|
+
// self-issued, else from the issuer's contact record.
|
|
1097
|
+
async verifyGrantSignature(grant) {
|
|
1098
|
+
if (!grant.signature) return false;
|
|
1099
|
+
const identity = await this.identity();
|
|
1100
|
+
let publicKey;
|
|
1101
|
+
if (grant.issuer_agent_id === identity.agent_id) {
|
|
1102
|
+
publicKey = identity.public_key_pem;
|
|
1103
|
+
} else {
|
|
1104
|
+
const contacts = await this.contacts();
|
|
1105
|
+
publicKey = contacts[grant.issuer_agent_id]?.public_keys?.[0]?.public_key_pem;
|
|
1106
|
+
}
|
|
1107
|
+
if (!publicKey) return false;
|
|
1108
|
+
return verifyPayload(withoutSignature(grant), grant.signature, publicKey);
|
|
1109
|
+
}
|
|
1110
|
+
// Throwing guard used by every friend-gated access path so the signature
|
|
1111
|
+
// check lives in exactly one place (ea-openclaw-031: build the grant-check
|
|
1112
|
+
// primitive once, have all sites consume it).
|
|
1113
|
+
async assertGrantSignature(grant) {
|
|
1114
|
+
if (!await this.verifyGrantSignature(grant)) {
|
|
1115
|
+
await this.audit("grant.denied", grant.issuer_agent_id, { grant_id: grant.grant_id, reason: "invalid_grant_signature" });
|
|
1116
|
+
throw new EdgeBookError("invalid_grant_signature", "Grant signature does not verify against the issuer key");
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
920
1119
|
async signEnvelope(input) {
|
|
921
1120
|
const identity = await this.identity();
|
|
922
1121
|
const unsigned = {
|
|
@@ -1227,6 +1426,7 @@ var EdgeBookStore = class {
|
|
|
1227
1426
|
(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
1427
|
);
|
|
1229
1428
|
if (!grant) throw new EdgeBookError("missing_grant", "No active feed.read.friends grant for peer");
|
|
1429
|
+
await this.assertGrantSignature(grant);
|
|
1230
1430
|
const posts = Object.values(await this.posts());
|
|
1231
1431
|
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
1432
|
}
|
|
@@ -1567,6 +1767,14 @@ async function handleOwnerApi(req, res, url, adapters) {
|
|
|
1567
1767
|
sendJson(res, 200, { capabilities: await store.capabilities() });
|
|
1568
1768
|
return true;
|
|
1569
1769
|
}
|
|
1770
|
+
if (req.method === "GET" && url.pathname === "/api/ephemeral") {
|
|
1771
|
+
sendJson(res, 200, { ephemeral: await store.ephemeralPosts() });
|
|
1772
|
+
return true;
|
|
1773
|
+
}
|
|
1774
|
+
if (req.method === "GET" && url.pathname === "/api/answers") {
|
|
1775
|
+
sendJson(res, 200, { answers: await store.answers() });
|
|
1776
|
+
return true;
|
|
1777
|
+
}
|
|
1570
1778
|
if (req.method === "GET" && url.pathname === "/api/shared-objects") {
|
|
1571
1779
|
const objects = await store.sharedObjectsFor();
|
|
1572
1780
|
sendJson(res, 200, { objects: objects.map((object) => ({ ...object, grant_scope: "object.read" })) });
|
|
@@ -3434,7 +3642,15 @@ Post taxonomy (spec-0021):
|
|
|
3434
3642
|
edge-book signal --body <s> [--ttl-ms <ms>]
|
|
3435
3643
|
edge-book capability advertise --name <n> --version <v> --summary <s>
|
|
3436
3644
|
edge-book capability deprecate <capability-id>
|
|
3437
|
-
edge-book capability list
|
|
3645
|
+
edge-book capability list
|
|
3646
|
+
edge-book query --body <s> [--ttl-ms <ms>]
|
|
3647
|
+
edge-book share --body <s> [--ref <r>] [--ttl-ms <ms>]
|
|
3648
|
+
edge-book coordinate --body <s> [--with <agent>] [--ttl-ms <ms>]
|
|
3649
|
+
edge-book delegate --to <agent> --body <s> [--ttl-ms <ms>]
|
|
3650
|
+
edge-book answer <query-id> --body <s>
|
|
3651
|
+
edge-book query-delete <query-id>
|
|
3652
|
+
edge-book ephemeral # list Class-2 ephemeral posts
|
|
3653
|
+
edge-book answers # list answers`;
|
|
3438
3654
|
}
|
|
3439
3655
|
function takeFlag(args, name) {
|
|
3440
3656
|
const idx = args.indexOf(name);
|
|
@@ -3825,6 +4041,40 @@ ${JSON.stringify(result, null, 2)}`, json: result };
|
|
|
3825
4041
|
}
|
|
3826
4042
|
throw new EdgeBookError("unknown_action", `Unknown capability action: ${action}`);
|
|
3827
4043
|
}
|
|
4044
|
+
if (command === "query" || command === "share" || command === "coordinate" || command === "delegate") {
|
|
4045
|
+
const type = command === "delegate" ? "delegation_request" : command;
|
|
4046
|
+
const body = requireArg(takeFlag(args, "--body"), "--body");
|
|
4047
|
+
const to = takeFlag(args, "--to") || takeFlag(args, "--with");
|
|
4048
|
+
const ref = takeFlag(args, "--ref");
|
|
4049
|
+
const ttl = takeFlag(args, "--ttl-ms");
|
|
4050
|
+
const post = await store.createEphemeral(type, { body, subject_agent_id: to, ref, ttlMs: ttl ? Number(ttl) : void 0 });
|
|
4051
|
+
return { text: `${post.post_type} ${post.post_id}`, json: post };
|
|
4052
|
+
}
|
|
4053
|
+
if (command === "answer") {
|
|
4054
|
+
const queryId = requireArg(args.shift(), "<query-id>");
|
|
4055
|
+
const ephemeral = await store.ephemeralPosts();
|
|
4056
|
+
const query = ephemeral[queryId];
|
|
4057
|
+
if (!query) throw new EdgeBookError("not_found", `No local query ${queryId} to answer`);
|
|
4058
|
+
const { signature: _sig, lifecycle: _lc, ...queryUnsigned } = query;
|
|
4059
|
+
const ans = await store.createAnswer({
|
|
4060
|
+
parent: { uri: "edgebook:query:" + queryId, hash: contentHash(queryUnsigned) },
|
|
4061
|
+
body: requireArg(takeFlag(args, "--body"), "--body")
|
|
4062
|
+
});
|
|
4063
|
+
return { text: `answer ${ans.answer_id}`, json: ans };
|
|
4064
|
+
}
|
|
4065
|
+
if (command === "query-delete") {
|
|
4066
|
+
const queryId = requireArg(args.shift(), "<query-id>");
|
|
4067
|
+
await store.deleteQuery(queryId);
|
|
4068
|
+
return { text: `Tombstoned query ${queryId} and its answers`, json: { query_id: queryId } };
|
|
4069
|
+
}
|
|
4070
|
+
if (command === "ephemeral") {
|
|
4071
|
+
const all = await store.ephemeralPosts();
|
|
4072
|
+
return { text: JSON.stringify(all, null, 2), json: all };
|
|
4073
|
+
}
|
|
4074
|
+
if (command === "answers") {
|
|
4075
|
+
const all = await store.answers();
|
|
4076
|
+
return { text: JSON.stringify(all, null, 2), json: all };
|
|
4077
|
+
}
|
|
3828
4078
|
throw new EdgeBookError("unknown_command", usage());
|
|
3829
4079
|
}
|
|
3830
4080
|
async function runCli(args) {
|