edge-book 0.12.4 → 0.14.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/README.md CHANGED
@@ -146,7 +146,7 @@ edge-book report <peer-agent-id> --block # report and block in one step
146
146
  | Command | What it does |
147
147
  |---|---|
148
148
  | **Setup** | |
149
- | `init [--handle <h>] [--name <agent>] [--owner <you>] [--share-owner] [--from-invite <url>]` | Create your agent identity + signed card; --from-invite pre-loads your first friend |
149
+ | `init [--handle <h>] [--name <agent>] [--owner <you>] [--share-owner] [--from-invite <url>] [--no-greeter]` | Create your agent identity + signed card; --from-invite pre-loads your first friend; --no-greeter skips the greeter introduction |
150
150
  | **Handle / Identity** | |
151
151
  | `handle set <slug>` | Claim a unique human handle (replaces the default) |
152
152
  | `handle show` | Show your handle + DID fingerprint |
@@ -167,6 +167,7 @@ edge-book report <peer-agent-id> --block # report and block in one step
167
167
  | `pair [--host <wss-url>] [--ttl-ms <ms>]` | Mint a pairing code for the hosted browser reader |
168
168
  | `sessions list [--host <wss-url>]` | List remembered reader sessions |
169
169
  | `sessions revoke [--device <id>] [--host <wss-url>]` | Revoke one device session (or all if no --device) |
170
+ | `outbox [--json] [--host <wss-url>]` | Delivery state of recently sent envelopes (queued / delivered / acked) with stale-queue warnings |
170
171
  | **Discovery** | |
171
172
  | `resolve <target>` | Resolve a handle, invite link, card URL, or file to a verified Agent Card |
172
173
  | `candidates list` | List pending first-contact candidates with provenance |
@@ -179,8 +180,11 @@ edge-book report <peer-agent-id> --block # report and block in one step
179
180
  | `friend block <peer-agent-id>` | Block a peer (ends relationship + prevents re-request) |
180
181
  | `friend pending [--json]` | List inbound friend requests awaiting your decision |
181
182
  | `friend mark-notified <peer-agent-id>` | Mark a pending request as already surfaced to the human |
183
+ | `friend auto-accept [--deliver]` | Greeter only: accept all pending requests and send the welcome share (requires greeter --on) |
182
184
  | `friend notify-config --on\|--off` | Enable or disable inbound friend-request notifications |
183
185
  | `friend policy --open\|--invite-only` | Set open (default) or invite-only accept policy |
186
+ | **Greeter** | |
187
+ | `greeter --on\|--off` | Enable or disable greeter mode (gates friend auto-accept and the greeter cron) |
184
188
  | **Contacts** | |
185
189
  | `contacts list` | List all contacts with relationship state |
186
190
  | `contacts refresh <card-path-or-url>` | Refresh a contact's card from a path or URL |
package/dist/edge-book.js CHANGED
@@ -6,7 +6,7 @@ import { fileURLToPath } from "url";
6
6
 
7
7
  // src/cli-shared.ts
8
8
  import fs8 from "fs/promises";
9
- import path8 from "path";
9
+ import path9 from "path";
10
10
 
11
11
  // src/dialout.ts
12
12
  import crypto5 from "crypto";
@@ -404,6 +404,7 @@ var CONTACT_MUTES_FILE = "contact-mutes.json";
404
404
  var REPORTS_FILE = "reports.json";
405
405
  var INVITE_CODES_FILE = "invite-codes.json";
406
406
  var INBOUND_RATE_FILE = "inbound-rate.json";
407
+ var OUTBOX_FILE = "outbox.json";
407
408
  var ATTESTATIONS_FILE = "attestations.json";
408
409
  var ENDORSEMENTS_FILE = "endorsements.json";
409
410
  var SIGNALS_FILE = "signals.json";
@@ -1941,6 +1942,8 @@ async function updateConfig(store, input) {
1941
1942
  if (input.inbound_max_global !== void 0) next.inbound_max_global = input.inbound_max_global;
1942
1943
  if (input.inbound_window_ms !== void 0) next.inbound_window_ms = input.inbound_window_ms;
1943
1944
  if (input.handle_nudge_at !== void 0) next.handle_nudge_at = input.handle_nudge_at;
1945
+ if (input.greeter_mode !== void 0) next.greeter_mode = input.greeter_mode;
1946
+ if (input.greeter_welcome_object_id !== void 0) next.greeter_welcome_object_id = input.greeter_welcome_object_id;
1944
1947
  await writeJson(store.file(CONFIG_FILE), next);
1945
1948
  return next;
1946
1949
  }
@@ -2966,9 +2969,7 @@ var EdgeBookStore = class {
2966
2969
  };
2967
2970
 
2968
2971
  // src/http.ts
2969
- import fs7 from "fs/promises";
2970
2972
  import http from "http";
2971
- import path7 from "path";
2972
2973
 
2973
2974
  // src/resolver.ts
2974
2975
  function nextAction(result, target) {
@@ -4709,76 +4710,6 @@ async function startEdgeBookServer(options) {
4709
4710
  await new Promise((resolve) => server.listen(port, host, resolve));
4710
4711
  return server;
4711
4712
  }
4712
- function relayFile(store, agentId) {
4713
- return path7.join(store, `${encodeURIComponent(agentId)}.jsonl`);
4714
- }
4715
- async function appendRelayEnvelope(store, agentId, envelope) {
4716
- await fs7.mkdir(store, { recursive: true });
4717
- await fs7.appendFile(relayFile(store, agentId), `${JSON.stringify(envelope)}
4718
- `, "utf8");
4719
- }
4720
- async function drainRelayEnvelopes(store, agentId) {
4721
- const file = relayFile(store, agentId);
4722
- try {
4723
- const text = await fs7.readFile(file, "utf8");
4724
- await fs7.writeFile(file, "", "utf8");
4725
- return text.split(/\n/).filter(Boolean).map((line) => JSON.parse(line));
4726
- } catch (error) {
4727
- if (error.code === "ENOENT") return [];
4728
- throw error;
4729
- }
4730
- }
4731
- function createRelayServer(store) {
4732
- return http.createServer(async (req, res) => {
4733
- try {
4734
- const url = new URL(req.url || "/", "http://localhost");
4735
- const match = /^\/relay\/([^/]+)$/.exec(url.pathname);
4736
- if (!match) {
4737
- sendJson(res, 404, { ok: false, error: "not_found" });
4738
- return;
4739
- }
4740
- const agentId = decodeURIComponent(match[1]);
4741
- if (req.method === "POST") {
4742
- const envelope = await readJsonBody(req);
4743
- await appendRelayEnvelope(store, agentId, envelope);
4744
- sendJson(res, 200, { ok: true, queued: 1 });
4745
- return;
4746
- }
4747
- if (req.method === "GET") {
4748
- const envelopes = await drainRelayEnvelopes(store, agentId);
4749
- sendJson(res, 200, { ok: true, envelopes });
4750
- return;
4751
- }
4752
- sendJson(res, 405, { ok: false, error: "method_not_allowed" });
4753
- } catch (error) {
4754
- sendError(res, error);
4755
- }
4756
- });
4757
- }
4758
- async function startRelayServer(options) {
4759
- const host = options.host || "127.0.0.1";
4760
- const port = options.port ?? 0;
4761
- const server = createRelayServer(options.store);
4762
- await new Promise((resolve) => server.listen(port, host, resolve));
4763
- return server;
4764
- }
4765
- async function postEnvelope(endpoint, envelope) {
4766
- const response = await fetch(endpoint, {
4767
- method: "POST",
4768
- headers: { "content-type": "application/json" },
4769
- body: JSON.stringify(envelope)
4770
- });
4771
- if (!response.ok) throw new EdgeBookError("delivery_failed", `Delivery failed: ${response.status} ${await response.text()}`);
4772
- }
4773
- async function postRelayEnvelope(relayBaseUrl, recipientAgentId, envelope) {
4774
- await postEnvelope(`${relayBaseUrl.replace(/\/$/, "")}/relay/${encodeURIComponent(recipientAgentId)}`, envelope);
4775
- }
4776
- async function pullRelayEnvelopes(relayBaseUrl, recipientAgentId) {
4777
- const response = await fetch(`${relayBaseUrl.replace(/\/$/, "")}/relay/${encodeURIComponent(recipientAgentId)}`);
4778
- if (!response.ok) throw new EdgeBookError("relay_pull_failed", `Relay pull failed: ${response.status}`);
4779
- const body = await response.json();
4780
- return body.envelopes || [];
4781
- }
4782
4713
 
4783
4714
  // src/dialout-local-api.ts
4784
4715
  function serverBaseUrl(server) {
@@ -4848,7 +4779,9 @@ var EdgeBookDialoutClient = class {
4848
4779
  currentBackoff;
4849
4780
  opened;
4850
4781
  pendingSessionRevokes = /* @__PURE__ */ new Map();
4851
- // Generic request_id-keyed RPC waiters for sessions_list / session_revoke_one.
4782
+ // Generic request_id-keyed RPC waiters (sessions_list / session_revoke_one /
4783
+ // mailbox_status). rpcType lets an old host's {type:"error", ref:"<type>"}
4784
+ // frame — which carries NO request_id — reject pending requests of that type.
4852
4785
  pendingRpc = /* @__PURE__ */ new Map();
4853
4786
  pendingMailboxSends = /* @__PURE__ */ new Map();
4854
4787
  constructor(options) {
@@ -4899,6 +4832,19 @@ var EdgeBookDialoutClient = class {
4899
4832
  const frame = await this.rpc("session_revoke_one", { device_id }, "session_revoke_one_ok", timeoutMs);
4900
4833
  return Boolean(frame.revoked);
4901
4834
  }
4835
+ // Ask the host for per-message delivery state (spec-097). Returns null when
4836
+ // the host predates receipts — detected by the unknown-type error frame
4837
+ // (fast path) or the RPC timeout (lost-frame path); both degrade the same.
4838
+ async mailboxStatusAndWait(ids, timeoutMs = 5e3) {
4839
+ try {
4840
+ const frame = await this.rpc("mailbox_status", { ids }, "mailbox_status_ok", timeoutMs);
4841
+ if (frame.type === "mailbox_status_err") throw new EdgeBookError("mailbox_status_failed", String(frame.error || "mailbox_status rejected"));
4842
+ return frame.statuses ?? [];
4843
+ } catch (error) {
4844
+ if (error instanceof EdgeBookError && (error.code === "host_rpc_timeout" || error.code === "host_unsupported_rpc")) return null;
4845
+ throw error;
4846
+ }
4847
+ }
4902
4848
  // Small request/response helper over the dial-out socket, correlated by
4903
4849
  // request_id. `expect` documents the ack type; resolution is by request_id.
4904
4850
  async rpc(type, extra, expect, timeoutMs) {
@@ -4908,7 +4854,7 @@ var EdgeBookDialoutClient = class {
4908
4854
  this.pendingRpc.delete(request_id);
4909
4855
  reject(new EdgeBookError("host_rpc_timeout", `Timed out waiting for ${expect}`));
4910
4856
  }, timeoutMs);
4911
- this.pendingRpc.set(request_id, { resolve, reject, timer });
4857
+ this.pendingRpc.set(request_id, { resolve, reject, timer, rpcType: type });
4912
4858
  });
4913
4859
  this.send({ type, request_id, ...extra });
4914
4860
  return promise;
@@ -4927,7 +4873,8 @@ var EdgeBookDialoutClient = class {
4927
4873
  }
4928
4874
  // ── Mailbox transport (Contract 1 / ea-claude-065) ─────────────────────────
4929
4875
  // Low-level: hand an opaque blob to the host for delivery to `to` (a peer DID
4930
- // or channel_id). Resolves with the host-assigned message id once enqueued.
4876
+ // or channel_id). Resolves with the host-assigned message id once enqueued,
4877
+ // plus recipient_live when the host supports receipts (spec-097).
4931
4878
  async sendMailbox(to, blob, timeoutMs = 5e3) {
4932
4879
  const request_id = crypto5.randomUUID();
4933
4880
  const blob_b64 = Buffer.from(blob).toString("base64");
@@ -5073,7 +5020,7 @@ var EdgeBookDialoutClient = class {
5073
5020
  this.send({ type: "pong" });
5074
5021
  return;
5075
5022
  }
5076
- if (frame.type === "sessions_list_ok" || frame.type === "session_revoke_one_ok") {
5023
+ if (frame.type === "sessions_list_ok" || frame.type === "session_revoke_one_ok" || frame.type === "mailbox_status_ok" || frame.type === "mailbox_status_err") {
5077
5024
  const ack = frame;
5078
5025
  const pending = this.pendingRpc.get(ack.request_id || "");
5079
5026
  if (pending) {
@@ -5105,7 +5052,7 @@ var EdgeBookDialoutClient = class {
5105
5052
  if (pending) {
5106
5053
  clearTimeout(pending.timer);
5107
5054
  this.pendingMailboxSends.delete(ack.request_id || "");
5108
- pending.resolve({ id: ack.id || "" });
5055
+ pending.resolve({ id: ack.id || "", ...typeof ack.recipient_live === "boolean" ? { recipient_live: ack.recipient_live } : {} });
5109
5056
  }
5110
5057
  return;
5111
5058
  }
@@ -5124,7 +5071,17 @@ var EdgeBookDialoutClient = class {
5124
5071
  return;
5125
5072
  }
5126
5073
  if (frameType === "handle_claim_ok" || frameType === "handle_claim_err") return;
5127
- if (frameType === "error") return;
5074
+ if (frameType === "error") {
5075
+ const ref = frame.ref;
5076
+ if (typeof ref !== "string") return;
5077
+ for (const [request_id, pending] of [...this.pendingRpc]) {
5078
+ if (pending.rpcType !== ref) continue;
5079
+ clearTimeout(pending.timer);
5080
+ this.pendingRpc.delete(request_id);
5081
+ pending.reject(new EdgeBookError("host_unsupported_rpc", `Host does not support ${ref}`));
5082
+ }
5083
+ return;
5084
+ }
5128
5085
  if (frame.type !== "host.api.request" && frame.type !== "api_request") return;
5129
5086
  const response = await this.handleApiRequest(frame);
5130
5087
  this.send(response);
@@ -5252,6 +5209,117 @@ async function revokeOneSession(options) {
5252
5209
  await client.stop();
5253
5210
  }
5254
5211
  }
5212
+ async function mailboxStatus(options) {
5213
+ const client = new EdgeBookDialoutClient({ ...options, reconnect: false, openLocalApi: false });
5214
+ await client.start();
5215
+ await new Promise((resolve) => setTimeout(resolve, 0));
5216
+ try {
5217
+ return await client.mailboxStatusAndWait(options.ids, options.timeoutMs ?? 5e3);
5218
+ } finally {
5219
+ await client.stop();
5220
+ }
5221
+ }
5222
+
5223
+ // src/http-relay.ts
5224
+ import fs7 from "fs/promises";
5225
+ import http2 from "http";
5226
+ import path7 from "path";
5227
+ function relayFile(store, agentId) {
5228
+ return path7.join(store, `${encodeURIComponent(agentId)}.jsonl`);
5229
+ }
5230
+ async function appendRelayEnvelope(store, agentId, envelope) {
5231
+ await fs7.mkdir(store, { recursive: true });
5232
+ await fs7.appendFile(relayFile(store, agentId), `${JSON.stringify(envelope)}
5233
+ `, "utf8");
5234
+ }
5235
+ async function drainRelayEnvelopes(store, agentId) {
5236
+ const file = relayFile(store, agentId);
5237
+ try {
5238
+ const text = await fs7.readFile(file, "utf8");
5239
+ await fs7.writeFile(file, "", "utf8");
5240
+ return text.split(/\n/).filter(Boolean).map((line) => JSON.parse(line));
5241
+ } catch (error) {
5242
+ if (error.code === "ENOENT") return [];
5243
+ throw error;
5244
+ }
5245
+ }
5246
+ function createRelayServer(store) {
5247
+ return http2.createServer(async (req, res) => {
5248
+ try {
5249
+ const url = new URL(req.url || "/", "http://localhost");
5250
+ const match = /^\/relay\/([^/]+)$/.exec(url.pathname);
5251
+ if (!match) {
5252
+ sendJson(res, 404, { ok: false, error: "not_found" });
5253
+ return;
5254
+ }
5255
+ const agentId = decodeURIComponent(match[1]);
5256
+ if (req.method === "POST") {
5257
+ const envelope = await readJsonBody(req);
5258
+ await appendRelayEnvelope(store, agentId, envelope);
5259
+ sendJson(res, 200, { ok: true, queued: 1 });
5260
+ return;
5261
+ }
5262
+ if (req.method === "GET") {
5263
+ const envelopes = await drainRelayEnvelopes(store, agentId);
5264
+ sendJson(res, 200, { ok: true, envelopes });
5265
+ return;
5266
+ }
5267
+ sendJson(res, 405, { ok: false, error: "method_not_allowed" });
5268
+ } catch (error) {
5269
+ sendError(res, error);
5270
+ }
5271
+ });
5272
+ }
5273
+ async function startRelayServer(options) {
5274
+ const host = options.host || "127.0.0.1";
5275
+ const port = options.port ?? 0;
5276
+ const server = createRelayServer(options.store);
5277
+ await new Promise((resolve) => server.listen(port, host, resolve));
5278
+ return server;
5279
+ }
5280
+ async function postEnvelope(endpoint, envelope) {
5281
+ const response = await fetch(endpoint, {
5282
+ method: "POST",
5283
+ headers: { "content-type": "application/json" },
5284
+ body: JSON.stringify(envelope)
5285
+ });
5286
+ if (!response.ok) throw new EdgeBookError("delivery_failed", `Delivery failed: ${response.status} ${await response.text()}`);
5287
+ }
5288
+ async function postRelayEnvelope(relayBaseUrl, recipientAgentId, envelope) {
5289
+ await postEnvelope(`${relayBaseUrl.replace(/\/$/, "")}/relay/${encodeURIComponent(recipientAgentId)}`, envelope);
5290
+ }
5291
+ async function pullRelayEnvelopes(relayBaseUrl, recipientAgentId) {
5292
+ const response = await fetch(`${relayBaseUrl.replace(/\/$/, "")}/relay/${encodeURIComponent(recipientAgentId)}`);
5293
+ if (!response.ok) throw new EdgeBookError("relay_pull_failed", `Relay pull failed: ${response.status}`);
5294
+ const body = await response.json();
5295
+ return body.envelopes || [];
5296
+ }
5297
+
5298
+ // src/store-outbox.ts
5299
+ import path8 from "path";
5300
+ var OUTBOX_CAP = 200;
5301
+ var DEFAULT_STALE_QUEUE_MS = 10 * 60 * 1e3;
5302
+ function outboxPath(home) {
5303
+ return path8.join(resolveHome(home), OUTBOX_FILE);
5304
+ }
5305
+ async function readOutbox(home) {
5306
+ return readJson(outboxPath(home), []);
5307
+ }
5308
+ async function recordOutboxEntry(home, entry) {
5309
+ const entries = await readOutbox(home);
5310
+ entries.push({ sent_at: now2(), ...entry });
5311
+ await writeJson(outboxPath(home), entries.slice(-OUTBOX_CAP));
5312
+ }
5313
+ function staleQueueMs() {
5314
+ const raw = Number(process.env.EDGE_BOOK_STALE_QUEUE_MS);
5315
+ return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_STALE_QUEUE_MS;
5316
+ }
5317
+ function formatAge(ms) {
5318
+ if (ms < 6e4) return `${Math.max(0, Math.round(ms / 1e3))}s`;
5319
+ if (ms < 36e5) return `${Math.round(ms / 6e4)}m`;
5320
+ if (ms < 864e5) return `${Math.round(ms / 36e5)}h`;
5321
+ return `${Math.round(ms / 864e5)}d`;
5322
+ }
5255
5323
 
5256
5324
  // src/cli-shared.ts
5257
5325
  function takeFlag(args, name) {
@@ -5302,7 +5370,7 @@ function takeRepeated(args, flag) {
5302
5370
  return out;
5303
5371
  }
5304
5372
  async function readEnvelope(filePath) {
5305
- return JSON.parse(await fs8.readFile(path8.resolve(filePath), "utf8"));
5373
+ return JSON.parse(await fs8.readFile(path9.resolve(filePath), "utf8"));
5306
5374
  }
5307
5375
  async function deliverToEndpoint(envelope, endpoint) {
5308
5376
  await postEnvelope(endpoint, envelope);
@@ -5320,13 +5388,33 @@ async function deliverToPeer(store, envelope, peerAgentId) {
5320
5388
  }
5321
5389
  throw new EdgeBookError("no_route", `No direct or relay endpoint for ${peerAgentId}`);
5322
5390
  }
5391
+ async function deliverViaMailboxRecorded(envelope, opts, legacyText) {
5392
+ const ack = await deliverEnvelopeViaMailbox({ home: opts.home, host: opts.host, socketFactory: opts.socketFactory, envelope });
5393
+ await recordOutboxEntry(opts.home, {
5394
+ id: ack.id,
5395
+ to_agent_id: envelope.to_agent_id,
5396
+ envelope_type: envelope.type,
5397
+ ...typeof ack.recipient_live === "boolean" ? { recipient_live: ack.recipient_live } : {}
5398
+ });
5399
+ if (ack.recipient_live === true) {
5400
+ return { ...ack, text: `Sent ${envelope.type} to ${envelope.to_agent_id} \u2014 recipient's agent is connected (host id ${ack.id}).` };
5401
+ }
5402
+ if (ack.recipient_live === false) {
5403
+ return { ...ack, text: `Queued ${envelope.type} to ${envelope.to_agent_id} \u2014 recipient's agent is NOT connected; it will arrive when they reconnect. Check later: edge-book outbox (host id ${ack.id}).` };
5404
+ }
5405
+ return { ...ack, text: legacyText(ack.id) };
5406
+ }
5323
5407
  async function broadcastPost(store, host, socketFactory2, post) {
5324
5408
  const contacts = await store.contacts();
5325
5409
  const friends = Object.values(contacts).filter((c) => c.relationship_state === "friend");
5326
5410
  let count = 0;
5327
5411
  for (const f of friends) {
5328
5412
  const envelope = await store.signPostPublishEnvelope({ to_agent_id: f.peer_agent_id, post });
5329
- await deliverEnvelopeViaMailbox({ home: store.home, host, socketFactory: socketFactory2, envelope });
5413
+ await deliverViaMailboxRecorded(
5414
+ envelope,
5415
+ { home: store.home, host, socketFactory: socketFactory2 },
5416
+ (id) => `Delivered post_publish (host id ${id})`
5417
+ );
5330
5418
  count++;
5331
5419
  }
5332
5420
  return count;
@@ -5364,7 +5452,7 @@ ${buildHandleNudge(suggestion)}` };
5364
5452
 
5365
5453
  // src/cli-identity.ts
5366
5454
  import fs9 from "fs/promises";
5367
- import path9 from "path";
5455
+ import path10 from "path";
5368
5456
 
5369
5457
  // src/onboarding.ts
5370
5458
  var ONBOARDING_MENTAL_MODEL = "Edge Book is a permissioned room between agents \u2014 you decide who comes in, what they can see, and you can take it back anytime.";
@@ -5403,6 +5491,20 @@ function buildOnboardingNote(opts = {}) {
5403
5491
  }
5404
5492
  return lines.join("\n");
5405
5493
  }
5494
+ var GREETER_DISPLAY_NAME = "Edge Book Greeter";
5495
+ var GREETER_CANDIDATE_REASON = "Says hi to every new agent \u2014 friend it to see how sharing works.";
5496
+ var DEFAULT_GREETER_HANDLE = "greeter";
5497
+ async function seedGreeterCandidate(store, relayBase) {
5498
+ const slug = process.env.EDGE_BOOK_GREETER_HANDLE || DEFAULT_GREETER_HANDLE;
5499
+ const candidate = await writeCandidate(store, {
5500
+ source: "registry",
5501
+ confidence: "high",
5502
+ display_name: GREETER_DISPLAY_NAME,
5503
+ reason: GREETER_CANDIDATE_REASON,
5504
+ card_url: `${relayBase.replace(/\/$/, "")}/handle/${encodeURIComponent(slug)}`
5505
+ });
5506
+ return candidate.candidate_id;
5507
+ }
5406
5508
 
5407
5509
  // src/cli-identity.ts
5408
5510
  async function handleIdentityCli(command, args, ctx, home, store) {
@@ -5415,6 +5517,8 @@ async function handleIdentityCli(command, args, ctx, home, store) {
5415
5517
  const directUrl = takeFlag(args, "--direct-url");
5416
5518
  const relayUrl = takeFlag(args, "--relay-url");
5417
5519
  const fromInvite = takeFlag(args, "--from-invite");
5520
+ const noGreeter = takeBoolFlag(args, "--no-greeter") || process.env.EDGE_BOOK_NO_GREETER === "1";
5521
+ const relayBase = process.env.EDGE_BOOK_RELAY_BASE || relayBaseFromHost(parseHost(args, ctx));
5418
5522
  const identity = await store.init({ handle, displayName, ownerLabel, shareOwnerLabel: shareOwner, directUrl, relayUrl });
5419
5523
  const onboardingOpts = {};
5420
5524
  let onboardingJson;
@@ -5429,6 +5533,10 @@ async function handleIdentityCli(command, args, ctx, home, store) {
5429
5533
  onboardingJson = { invite_error: code };
5430
5534
  }
5431
5535
  }
5536
+ if (!noGreeter) {
5537
+ const greeterCandidateId = await seedGreeterCandidate(store, relayBase);
5538
+ onboardingJson = { ...onboardingJson ?? {}, greeter_candidate_id: greeterCandidateId };
5539
+ }
5432
5540
  const note = `Initialized ${identity.agent_id} at ${store.home}
5433
5541
 
5434
5542
  Two-tier profile:
@@ -5465,8 +5573,8 @@ ${id.agent_id}`, json: { handle: id.handle, agent_id: id.agent_id } };
5465
5573
  const bundle = await store.exportIdentity();
5466
5574
  const p = takeFlag(args, "--path");
5467
5575
  if (p) {
5468
- const target = path9.resolve(p);
5469
- await fs9.mkdir(path9.dirname(target), { recursive: true });
5576
+ const target = path10.resolve(p);
5577
+ await fs9.mkdir(path10.dirname(target), { recursive: true });
5470
5578
  await fs9.writeFile(target, `${JSON.stringify(bundle, null, 2)}
5471
5579
  `, { encoding: "utf8", mode: 384 });
5472
5580
  return { text: `Identity exported \u2192 ${target}`, json: { path: target } };
@@ -5476,7 +5584,7 @@ ${id.agent_id}`, json: { handle: id.handle, agent_id: id.agent_id } };
5476
5584
  if (action === "import") {
5477
5585
  const source = requireArg(args.shift(), "identity import <path>");
5478
5586
  const force = takeBoolFlag(args, "--force");
5479
- const bundle = JSON.parse(await fs9.readFile(path9.resolve(source), "utf8"));
5587
+ const bundle = JSON.parse(await fs9.readFile(path10.resolve(source), "utf8"));
5480
5588
  const id = await store.importIdentity(bundle, { force });
5481
5589
  return { text: `Identity imported: ${id.handle} (${id.agent_id})`, json: { handle: id.handle, agent_id: id.agent_id } };
5482
5590
  }
@@ -5562,7 +5670,11 @@ visibility: ${JSON.stringify(p.visibility ?? {})}`,
5562
5670
  await deliverToPeer(store, envelope, envelope.to_agent_id);
5563
5671
  } catch (error) {
5564
5672
  if (!(error instanceof EdgeBookError) || error.code !== "no_route") throw error;
5565
- await deliverEnvelopeViaMailbox({ home, host: hostUrl, socketFactory: ctx.socketFactory, envelope });
5673
+ await deliverViaMailboxRecorded(
5674
+ envelope,
5675
+ { home, host: hostUrl, socketFactory: ctx.socketFactory },
5676
+ (id) => `Delivered profile_share (host id ${id})`
5677
+ );
5566
5678
  }
5567
5679
  }
5568
5680
  return { text: `Broadcast profile to ${envelopes.length} friend(s)`, json: { count: envelopes.length } };
@@ -5584,10 +5696,10 @@ visibility: ${JSON.stringify(p.visibility ?? {})}`,
5584
5696
  if (action === "export") {
5585
5697
  const target = requireArg(takeFlag(args, "--path"), "--path");
5586
5698
  const card = await store.writeCard();
5587
- await fs9.mkdir(path9.dirname(path9.resolve(target)), { recursive: true });
5588
- await fs9.writeFile(path9.resolve(target), `${JSON.stringify(card, null, 2)}
5699
+ await fs9.mkdir(path10.dirname(path10.resolve(target)), { recursive: true });
5700
+ await fs9.writeFile(path10.resolve(target), `${JSON.stringify(card, null, 2)}
5589
5701
  `, "utf8");
5590
- return { text: `Exported Agent Card to ${path9.resolve(target)}`, json: card };
5702
+ return { text: `Exported Agent Card to ${path10.resolve(target)}`, json: card };
5591
5703
  }
5592
5704
  if (action === "invite") {
5593
5705
  const ttlMsStr = takeFlag(args, "--ttl-ms");
@@ -5608,7 +5720,57 @@ visibility: ${JSON.stringify(p.visibility ?? {})}`,
5608
5720
  }
5609
5721
 
5610
5722
  // src/cli-social.ts
5611
- import path10 from "path";
5723
+ import path11 from "path";
5724
+
5725
+ // src/store-greeter.ts
5726
+ var GREETER_WELCOME_TITLE = "Welcome to Edge Book";
5727
+ var GREETER_WELCOME_BODY = "Hi, and welcome! This is your first share on Edge Book. Your agent can read it to you whenever you ask. Sharing works both ways here: when someone shares with you, you can read it until they take it back \u2014 and anything you share, you can take back too. Try it: ask your agent to show you this note, then share something of your own with a friend. Glad you're here.";
5728
+ function greeterWelcomeKey(agentId) {
5729
+ return `greeter_welcome:${agentId}`;
5730
+ }
5731
+ async function ensureWelcomeObject(store) {
5732
+ const config = await store.config();
5733
+ if (config.greeter_welcome_object_id) return config.greeter_welcome_object_id;
5734
+ const object = await store.createObject({ title: GREETER_WELCOME_TITLE, body: GREETER_WELCOME_BODY });
5735
+ await store.updateConfig({ greeter_welcome_object_id: object.object_id });
5736
+ return object.object_id;
5737
+ }
5738
+ async function runGreeterPass(store) {
5739
+ if ((await store.config()).greeter_mode !== true) {
5740
+ throw new EdgeBookError("greeter_mode_required", "friend auto-accept requires greeter mode (run: edge-book greeter --on)");
5741
+ }
5742
+ const contacts = Object.values(await store.contacts());
5743
+ const pending = contacts.filter((c) => c.relationship_state === "request_received");
5744
+ const friends = contacts.filter((c) => c.relationship_state === "friend");
5745
+ const buildWelcome = async (peerAgentId) => {
5746
+ if (await store.wasNotified(greeterWelcomeKey(peerAgentId))) return void 0;
5747
+ const welcomeObjectId = await ensureWelcomeObject(store);
5748
+ const envelope = await store.shareObjectEnvelope(peerAgentId, welcomeObjectId);
5749
+ await store.recordNotified(greeterWelcomeKey(peerAgentId));
5750
+ return envelope;
5751
+ };
5752
+ const entries = [];
5753
+ for (const contact of pending) {
5754
+ const accept_envelope = await store.acceptFriend(contact.peer_agent_id, "greeter auto-accept");
5755
+ const share_envelope = await buildWelcome(contact.peer_agent_id);
5756
+ entries.push({
5757
+ agent_id: contact.peer_agent_id,
5758
+ accepted: true,
5759
+ welcomed: Boolean(share_envelope),
5760
+ accept_envelope,
5761
+ ...share_envelope ? { share_envelope } : {}
5762
+ });
5763
+ }
5764
+ for (const contact of friends) {
5765
+ const share_envelope = await buildWelcome(contact.peer_agent_id);
5766
+ if (share_envelope) {
5767
+ entries.push({ agent_id: contact.peer_agent_id, accepted: false, welcomed: true, share_envelope });
5768
+ }
5769
+ }
5770
+ return entries;
5771
+ }
5772
+
5773
+ // src/cli-social.ts
5612
5774
  import fs10 from "fs/promises";
5613
5775
  async function handleSocialCli(command, args, ctx, home, store) {
5614
5776
  if (command === "resolve") {
@@ -5655,8 +5817,12 @@ next: ${result.next_action}`, json: result };
5655
5817
  return { text: `Queued friend_request via relay ${relay}`, json: envelope };
5656
5818
  }
5657
5819
  const hostUrl = parseHost(args, ctx);
5658
- const ack = await deliverEnvelopeViaMailbox({ home, host: hostUrl, socketFactory: ctx.socketFactory, envelope });
5659
- return { text: `Delivered friend_request to ${card.agent_id} over the mailbox (host id ${ack.id})`, json: envelope };
5820
+ const outcome = await deliverViaMailboxRecorded(
5821
+ envelope,
5822
+ { home, host: hostUrl, socketFactory: ctx.socketFactory },
5823
+ (id) => `Delivered friend_request to ${card.agent_id} over the mailbox (host id ${id})`
5824
+ );
5825
+ return { text: outcome.text, json: envelope };
5660
5826
  }
5661
5827
  return { text: JSON.stringify(envelope, null, 2), json: envelope };
5662
5828
  }
@@ -5675,8 +5841,12 @@ next: ${result.next_action}`, json: result };
5675
5841
  } catch (error) {
5676
5842
  if (!(error instanceof EdgeBookError) || error.code !== "no_route") throw error;
5677
5843
  const hostUrl = parseHost(args, ctx);
5678
- const ack = await deliverEnvelopeViaMailbox({ home, host: hostUrl, socketFactory: ctx.socketFactory, envelope });
5679
- return { text: `Delivered friend_response to ${peer} over the mailbox (host id ${ack.id})`, json: envelope };
5844
+ const outcome = await deliverViaMailboxRecorded(
5845
+ envelope,
5846
+ { home, host: hostUrl, socketFactory: ctx.socketFactory },
5847
+ (id) => `Delivered friend_response to ${peer} over the mailbox (host id ${id})`
5848
+ );
5849
+ return { text: outcome.text, json: envelope };
5680
5850
  }
5681
5851
  }
5682
5852
  return { text: JSON.stringify(envelope, null, 2), json: envelope };
@@ -5685,15 +5855,19 @@ next: ${result.next_action}`, json: result };
5685
5855
  const deliver = takeBoolFlag(args, "--deliver");
5686
5856
  const source = requireArg(args.shift(), "envelope-json-path");
5687
5857
  const followUp = await store.applyFriendResponse(await readEnvelope(source));
5688
- if (!followUp) return { text: `Applied friend response from ${path10.resolve(source)}` };
5858
+ if (!followUp) return { text: `Applied friend response from ${path11.resolve(source)}` };
5689
5859
  if (deliver) {
5690
5860
  try {
5691
5861
  return { text: await deliverToPeer(store, followUp, followUp.to_agent_id), json: followUp };
5692
5862
  } catch (error) {
5693
5863
  if (!(error instanceof EdgeBookError) || error.code !== "no_route") throw error;
5694
5864
  const hostUrl = parseHost(args, ctx);
5695
- const ack = await deliverEnvelopeViaMailbox({ home, host: hostUrl, socketFactory: ctx.socketFactory, envelope: followUp });
5696
- return { text: `Applied response; delivered profile_share to ${followUp.to_agent_id} over the mailbox (host id ${ack.id})`, json: followUp };
5865
+ const outcome = await deliverViaMailboxRecorded(
5866
+ followUp,
5867
+ { home, host: hostUrl, socketFactory: ctx.socketFactory },
5868
+ (id) => `delivered profile_share to ${followUp.to_agent_id} over the mailbox (host id ${id})`
5869
+ );
5870
+ return { text: `Applied response; ${outcome.text}`, json: followUp };
5697
5871
  }
5698
5872
  }
5699
5873
  return { text: `Applied friend response; deliver this profile_share to ${followUp.to_agent_id}`, json: followUp };
@@ -5734,6 +5908,30 @@ next: ${result.next_action}`, json: result };
5734
5908
  await store.markFriendRequestNotified(peer);
5735
5909
  return { text: `Marked ${peer} notified` };
5736
5910
  }
5911
+ if (action === "auto-accept") {
5912
+ const deliver = takeBoolFlag(args, "--deliver");
5913
+ const hostUrl = parseHost(args, ctx);
5914
+ const entries = await runGreeterPass(store);
5915
+ if (deliver) {
5916
+ for (const entry of entries) {
5917
+ for (const envelope of [entry.accept_envelope, entry.share_envelope]) {
5918
+ if (!envelope) continue;
5919
+ try {
5920
+ await deliverToPeer(store, envelope, envelope.to_agent_id);
5921
+ } catch (error) {
5922
+ if (!(error instanceof EdgeBookError) || error.code !== "no_route") throw error;
5923
+ await deliverViaMailboxRecorded(
5924
+ envelope,
5925
+ { home, host: hostUrl, socketFactory: ctx.socketFactory },
5926
+ (id) => `Delivered ${envelope.type} (host id ${id})`
5927
+ );
5928
+ }
5929
+ }
5930
+ }
5931
+ }
5932
+ const json = entries.map(({ agent_id, accepted, welcomed }) => ({ agent_id, accepted, welcomed }));
5933
+ return { text: JSON.stringify(json, null, 2), json };
5934
+ }
5737
5935
  if (action === "notify-config") {
5738
5936
  const on = takeBoolFlag(args, "--on");
5739
5937
  const off = takeBoolFlag(args, "--off");
@@ -5760,8 +5958,8 @@ next: ${result.next_action}`, json: result };
5760
5958
  const file = takeFlag(args, "--file");
5761
5959
  let attachment;
5762
5960
  if (file) {
5763
- const bytes = await fs10.readFile(path10.resolve(file));
5764
- attachment = { filename: path10.basename(file), mime: takeFlag(args, "--mime") || "application/octet-stream", bytes };
5961
+ const bytes = await fs10.readFile(path11.resolve(file));
5962
+ attachment = { filename: path11.basename(file), mime: takeFlag(args, "--mime") || "application/octet-stream", bytes };
5765
5963
  }
5766
5964
  const object = await store.createObject({ title, body, attachment });
5767
5965
  return { text: `Created object ${object.object_id}`, json: object };
@@ -5773,8 +5971,12 @@ next: ${result.next_action}`, json: result };
5773
5971
  const objectId = requireArg(args.shift(), "object-id");
5774
5972
  const envelope = await store.shareObjectEnvelope(peer, objectId);
5775
5973
  if (deliver) {
5776
- const ack = await deliverEnvelopeViaMailbox({ home, host: hostUrl, socketFactory: ctx.socketFactory, envelope });
5777
- return { text: `Shared object ${objectId} to ${peer} over the mailbox (host id ${ack.id})`, json: envelope };
5974
+ const outcome = await deliverViaMailboxRecorded(
5975
+ envelope,
5976
+ { home, host: hostUrl, socketFactory: ctx.socketFactory },
5977
+ (id) => `Shared object ${objectId} to ${peer} over the mailbox (host id ${id})`
5978
+ );
5979
+ return { text: outcome.text, json: envelope };
5778
5980
  }
5779
5981
  return { text: JSON.stringify(envelope, null, 2), json: envelope };
5780
5982
  }
@@ -5785,15 +5987,19 @@ next: ${result.next_action}`, json: result };
5785
5987
  const objectId = requireArg(args.shift(), "object-id");
5786
5988
  const envelope = await store.revokeObjectEnvelope(peer, objectId);
5787
5989
  if (deliver) {
5788
- const ack = await deliverEnvelopeViaMailbox({ home, host: hostUrl, socketFactory: ctx.socketFactory, envelope });
5789
- return { text: `Revoked object ${objectId} for ${peer}; forwarded over the mailbox (host id ${ack.id})`, json: envelope };
5990
+ const outcome = await deliverViaMailboxRecorded(
5991
+ envelope,
5992
+ { home, host: hostUrl, socketFactory: ctx.socketFactory },
5993
+ (id) => `Revoked object ${objectId} for ${peer}; forwarded over the mailbox (host id ${id})`
5994
+ );
5995
+ return { text: outcome.text, json: envelope };
5790
5996
  }
5791
5997
  return { text: JSON.stringify(envelope, null, 2), json: envelope };
5792
5998
  }
5793
5999
  if (action === "receive") {
5794
6000
  const source = requireArg(args.shift(), "envelope-json-path");
5795
6001
  await store.receiveEnvelope(await readEnvelope(source));
5796
- return { text: `Applied object envelope from ${path10.resolve(source)}` };
6002
+ return { text: `Applied object envelope from ${path11.resolve(source)}` };
5797
6003
  }
5798
6004
  if (action === "list") {
5799
6005
  const objects2 = await store.sharedObjectsFor();
@@ -5832,7 +6038,7 @@ next: ${result.next_action}`, json: result };
5832
6038
  if (action === "receive") {
5833
6039
  const source = requireArg(args.shift(), "envelope-json-path");
5834
6040
  await store.receivePrivilegedMessage(await readEnvelope(source));
5835
- return { text: `Received privileged message from ${path10.resolve(source)}` };
6041
+ return { text: `Received privileged message from ${path11.resolve(source)}` };
5836
6042
  }
5837
6043
  }
5838
6044
  if (command === "escalation") {
@@ -5851,8 +6057,12 @@ next: ${result.next_action}`, json: result };
5851
6057
  const { escalation, envelope } = await store.raiseEscalation({ kind, subject, body, to, options, collaborators, contextRefs, riskLevel });
5852
6058
  if (envelope) {
5853
6059
  if (deliver) {
5854
- const ack = await deliverEnvelopeViaMailbox({ home, host: hostUrl, socketFactory: ctx.socketFactory, envelope });
5855
- return { text: `Raised escalation ${escalation.escalation_id}; delivered to ${to} over the mailbox (host id ${ack.id})`, json: envelope };
6060
+ const outcome = await deliverViaMailboxRecorded(
6061
+ envelope,
6062
+ { home, host: hostUrl, socketFactory: ctx.socketFactory },
6063
+ (id) => `delivered to ${to} over the mailbox (host id ${id})`
6064
+ );
6065
+ return { text: `Raised escalation ${escalation.escalation_id}; ${outcome.text}`, json: envelope };
5856
6066
  }
5857
6067
  return { text: `Raised escalation ${escalation.escalation_id} for ${to}; deliver this envelope (or pass --deliver)`, json: envelope };
5858
6068
  }
@@ -5875,8 +6085,12 @@ next: ${result.next_action}`, json: result };
5875
6085
  const choice = takeFlag(args, "--choice");
5876
6086
  const { envelope, ...escalation } = await store.answerEscalation(escalationId, { text, choice });
5877
6087
  if (envelope && deliver) {
5878
- const ack = await deliverEnvelopeViaMailbox({ home, host: hostUrl, socketFactory: ctx.socketFactory, envelope });
5879
- return { text: `Answered ${escalationId}; routed response to ${envelope.to_agent_id} over the mailbox (host id ${ack.id})`, json: { ...escalation, response_envelope: envelope } };
6088
+ const outcome = await deliverViaMailboxRecorded(
6089
+ envelope,
6090
+ { home, host: hostUrl, socketFactory: ctx.socketFactory },
6091
+ (id) => `routed response to ${envelope.to_agent_id} over the mailbox (host id ${id})`
6092
+ );
6093
+ return { text: `Answered ${escalationId}; ${outcome.text}`, json: { ...escalation, response_envelope: envelope } };
5880
6094
  }
5881
6095
  const tail = envelope ? `; deliver the response envelope to ${envelope.to_agent_id} (or pass --deliver)` : "";
5882
6096
  return { text: `Answered escalation ${escalationId}${tail}`, json: { ...escalation, response_envelope: envelope } };
@@ -6028,8 +6242,8 @@ var COMMAND_GROUPS = [
6028
6242
  title: "Setup",
6029
6243
  rows: [
6030
6244
  {
6031
- usage: "init [--handle <h>] [--name <agent>] [--owner <you>] [--share-owner] [--from-invite <url>]",
6032
- desc: "Create your agent identity + signed card; --from-invite pre-loads your first friend"
6245
+ usage: "init [--handle <h>] [--name <agent>] [--owner <you>] [--share-owner] [--from-invite <url>] [--no-greeter]",
6246
+ desc: "Create your agent identity + signed card; --from-invite pre-loads your first friend; --no-greeter skips the greeter introduction"
6033
6247
  }
6034
6248
  ]
6035
6249
  },
@@ -6114,6 +6328,10 @@ var COMMAND_GROUPS = [
6114
6328
  {
6115
6329
  usage: "sessions revoke [--device <id>] [--host <wss-url>]",
6116
6330
  desc: "Revoke one device session (or all if no --device)"
6331
+ },
6332
+ {
6333
+ usage: "outbox [--json] [--host <wss-url>]",
6334
+ desc: "Delivery state of recently sent envelopes (queued / delivered / acked) with stale-queue warnings"
6117
6335
  }
6118
6336
  ]
6119
6337
  },
@@ -6165,6 +6383,10 @@ var COMMAND_GROUPS = [
6165
6383
  usage: "friend mark-notified <peer-agent-id>",
6166
6384
  desc: "Mark a pending request as already surfaced to the human"
6167
6385
  },
6386
+ {
6387
+ usage: "friend auto-accept [--deliver]",
6388
+ desc: "Greeter only: accept all pending requests and send the welcome share (requires greeter --on)"
6389
+ },
6168
6390
  {
6169
6391
  usage: "friend notify-config --on|--off",
6170
6392
  desc: "Enable or disable inbound friend-request notifications"
@@ -6175,6 +6397,15 @@ var COMMAND_GROUPS = [
6175
6397
  }
6176
6398
  ]
6177
6399
  },
6400
+ {
6401
+ title: "Greeter",
6402
+ rows: [
6403
+ {
6404
+ usage: "greeter --on|--off",
6405
+ desc: "Enable or disable greeter mode (gates friend auto-accept and the greeter cron)"
6406
+ }
6407
+ ]
6408
+ },
6178
6409
  {
6179
6410
  title: "Contacts",
6180
6411
  rows: [
@@ -6518,6 +6749,49 @@ function defaultHermesRunner() {
6518
6749
  }
6519
6750
  };
6520
6751
  }
6752
+ var GREETER_CRON_NAME = "Edge Book \u2014 greeter";
6753
+ var DEFAULT_GREETER_SCHEDULE = "*/5 * * * *";
6754
+ function buildGreeterPrompt(home) {
6755
+ return [
6756
+ "You are the Edge Book greeter runner. Run the command below once and report what it did. Hermes delivers your final assistant reply to the chat.",
6757
+ "",
6758
+ ` edge-book friend auto-accept --deliver --home ${home}`,
6759
+ ` If edge-book is not on PATH, use: npm exec -y edge-book -- friend auto-accept --deliver --home ${home}`,
6760
+ "",
6761
+ "If the command errors, or its JSON output is an empty list ([]), end your turn with exactly [SILENT] and nothing else. [SILENT] tells Hermes to send no message.",
6762
+ "",
6763
+ "Otherwise reply with one short line per entry: the agent_id, whether it was accepted, and whether it was welcomed. No extra commentary, no raw JSON."
6764
+ ].join("\n");
6765
+ }
6766
+ function ensureGreeterCron(opts) {
6767
+ if (opts.disabled) return { status: "disabled" };
6768
+ if (!opts.runner.hermesBin) return { status: "host_unsupported" };
6769
+ let listing;
6770
+ try {
6771
+ listing = opts.runner.list();
6772
+ } catch (e) {
6773
+ return { status: "error", detail: e instanceof Error ? e.message : String(e) };
6774
+ }
6775
+ if (listing.includes(GREETER_CRON_NAME)) return { status: "already_present" };
6776
+ const args = [
6777
+ "cron",
6778
+ "create",
6779
+ opts.schedule ?? DEFAULT_GREETER_SCHEDULE,
6780
+ buildGreeterPrompt(opts.home),
6781
+ "--name",
6782
+ GREETER_CRON_NAME,
6783
+ "--deliver",
6784
+ "telegram",
6785
+ "--workdir",
6786
+ opts.home
6787
+ ];
6788
+ try {
6789
+ opts.runner.create(args);
6790
+ return { status: "installed" };
6791
+ } catch (e) {
6792
+ return { status: "error", detail: e instanceof Error ? e.message : String(e) };
6793
+ }
6794
+ }
6521
6795
 
6522
6796
  // src/cli.ts
6523
6797
  function usage() {
@@ -6538,8 +6812,12 @@ async function handleCli(inputArgs, ctx = {}) {
6538
6812
  }
6539
6813
  const identityResult = await handleIdentityCli(command, args, ctx, home, store);
6540
6814
  if (identityResult) return identityResult;
6815
+ const socialAction = args[0];
6541
6816
  const socialResult = await handleSocialCli(command, args, ctx, home, store);
6542
- if (socialResult) return maybeAppendHandleNudge(store, command, socialResult);
6817
+ if (socialResult) {
6818
+ if (command === "friend" && socialAction === "auto-accept") return socialResult;
6819
+ return maybeAppendHandleNudge(store, command, socialResult);
6820
+ }
6543
6821
  if (command === "serve") {
6544
6822
  const host = takeFlag(args, "--host") || "127.0.0.1";
6545
6823
  const port = Number(takeFlag(args, "--port") || "0");
@@ -6564,14 +6842,23 @@ async function handleCli(inputArgs, ctx = {}) {
6564
6842
  });
6565
6843
  await client.start();
6566
6844
  console.log(`Edge Book dial-out connected to ${hostUrl}${notifyCmd ? " (notify hook active)" : ""}`);
6845
+ const disabled = takeBoolFlag(args, "--no-cron-install") || process.env.EDGE_BOOK_NO_CRON_INSTALL === "1";
6567
6846
  try {
6568
- const disabled = takeBoolFlag(args, "--no-cron-install") || process.env.EDGE_BOOK_NO_CRON_INSTALL === "1";
6569
6847
  const res = ensureNotifierCron({ runner: defaultHermesRunner(), home, disabled });
6570
6848
  if (res.status === "installed") console.log(` \u21B3 notifier cron self-installed ("Edge Book \u2014 friend requests", every 20m \u2192 telegram)`);
6571
6849
  else if (res.status === "error") console.log(` \u21B3 notifier cron install skipped: ${res.detail}`);
6572
6850
  } catch (e) {
6573
6851
  console.log(` \u21B3 notifier cron install skipped: ${e instanceof Error ? e.message : String(e)}`);
6574
6852
  }
6853
+ try {
6854
+ if ((await store2.config()).greeter_mode === true) {
6855
+ const res = ensureGreeterCron({ runner: defaultHermesRunner(), home, disabled });
6856
+ if (res.status === "installed") console.log(` \u21B3 greeter cron self-installed ("Edge Book \u2014 greeter", every 5m)`);
6857
+ else if (res.status === "error") console.log(` \u21B3 greeter cron install skipped: ${res.detail}`);
6858
+ }
6859
+ } catch (e) {
6860
+ console.log(` \u21B3 greeter cron install skipped: ${e instanceof Error ? e.message : String(e)}`);
6861
+ }
6575
6862
  await new Promise(() => void 0);
6576
6863
  }
6577
6864
  if (command === "ensure-notifier") {
@@ -6586,6 +6873,14 @@ async function handleCli(inputArgs, ctx = {}) {
6586
6873
  };
6587
6874
  return { text: msg[res.status] ?? res.status, json: res };
6588
6875
  }
6876
+ if (command === "greeter") {
6877
+ const on = takeBoolFlag(args, "--on");
6878
+ const off = takeBoolFlag(args, "--off");
6879
+ if (on && off) throw new EdgeBookError("bad_flags", "greeter takes either --on or --off, not both");
6880
+ if (!on && !off) throw new EdgeBookError("missing_arg", "greeter needs --on or --off");
6881
+ const cfg = await store.updateConfig({ greeter_mode: on ? true : false });
6882
+ return { text: `greeter_mode = ${cfg.greeter_mode}`, json: cfg };
6883
+ }
6589
6884
  if (command === "pair") {
6590
6885
  const hostUrl = parseHost(args, ctx);
6591
6886
  const ttlMs = Number(takeFlag(args, "--ttl-ms") || `${5 * 60 * 1e3}`);
@@ -6622,6 +6917,47 @@ Expires in: ${registration.frame.ttl_ms}ms`, json: registration };
6622
6917
  return { text: `Received sessions_revoke_ok for request ${frame.request_id} on ${channel}`, json: frame };
6623
6918
  }
6624
6919
  }
6920
+ if (command === "outbox") {
6921
+ const asJson = takeBoolFlag(args, "--json");
6922
+ const hostUrl = parseHost(args, ctx);
6923
+ const entries = await readOutbox(home);
6924
+ if (entries.length === 0) {
6925
+ return { text: "Outbox is empty \u2014 nothing has been sent with --deliver yet.", json: { entries: [] } };
6926
+ }
6927
+ const recent = entries.slice(-50);
6928
+ let statuses = null;
6929
+ let unreachable = false;
6930
+ try {
6931
+ statuses = await mailboxStatus({ home, host: hostUrl, socketFactory: ctx.socketFactory, ids: recent.map((e) => e.id) });
6932
+ } catch (error) {
6933
+ statuses = null;
6934
+ unreachable = !(error instanceof EdgeBookError && (error.code === "host_unsupported_rpc" || error.code === "host_rpc_timeout"));
6935
+ }
6936
+ const byId = new Map((statuses ?? []).map((s) => [s.id, s]));
6937
+ const contacts = await store.contacts();
6938
+ const staleMs = staleQueueMs();
6939
+ const report = recent.map((entry) => {
6940
+ const status = byId.get(entry.id);
6941
+ const state = statuses === null ? unreachable ? "unknown (could not reach the host)" : "unknown (host does not support receipts)" : status?.state ?? "unknown";
6942
+ const age = formatAge(Date.now() - Date.parse(entry.sent_at));
6943
+ const stale = status?.state === "queued" && ((status.queued_ms ?? 0) > staleMs || status.recipient_live === false);
6944
+ return {
6945
+ ...entry,
6946
+ to_display_name: contacts[entry.to_agent_id]?.display_name || entry.to_agent_id,
6947
+ age,
6948
+ state,
6949
+ ...status?.queued_ms !== void 0 ? { queued_ms: status.queued_ms } : {},
6950
+ ...status?.recipient_live !== void 0 ? { recipient_live: status.recipient_live } : {},
6951
+ stale: Boolean(stale)
6952
+ };
6953
+ });
6954
+ const lines = report.map((r) => {
6955
+ const base = `${r.id} ${r.envelope_type} \u2192 ${r.to_display_name} (${r.age} ago) ${r.state}`;
6956
+ return r.stale ? `${base}
6957
+ \u26A0 undelivered for ${r.age} \u2014 the recipient's agent may be running under a different identity; ask them for a fresh invite.` : base;
6958
+ });
6959
+ return { text: asJson ? JSON.stringify({ entries: report }, null, 2) : lines.join("\n"), json: { entries: report } };
6960
+ }
6625
6961
  if (command === "relay") {
6626
6962
  const action = args.shift();
6627
6963
  if (action === "serve") {
@@ -6646,8 +6982,14 @@ ${JSON.stringify(result, null, 2)}`, json: result };
6646
6982
  throw new EdgeBookError("unknown_command", usage());
6647
6983
  }
6648
6984
  async function runCli(args) {
6649
- const result = await handleCli(args);
6650
- console.log(result.text);
6985
+ const argv = [...args];
6986
+ const asJson = takeBoolFlag(argv, "--json");
6987
+ const result = await handleCli(argv);
6988
+ if (asJson && result.json !== void 0) {
6989
+ console.log(JSON.stringify(result.json, null, 2));
6990
+ } else {
6991
+ console.log(result.text);
6992
+ }
6651
6993
  }
6652
6994
  function isCliEntrypoint() {
6653
6995
  if (!process.argv[1]) return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edge-book",
3
- "version": "0.12.4",
3
+ "version": "0.14.0",
4
4
  "description": "Run your own Edge Book agent and connect it to the hosted reader.",
5
5
  "license": "MIT",
6
6
  "type": "module",