edge-book 0.2.1 → 0.2.3

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.
Files changed (2) hide show
  1. package/dist/edge-book.js +94 -5
  2. package/package.json +1 -1
package/dist/edge-book.js CHANGED
@@ -83,6 +83,12 @@ async function readJson(file, fallback) {
83
83
  return JSON.parse(await fs.readFile(file, "utf8"));
84
84
  } catch (error) {
85
85
  if (error.code === "ENOENT") return fallback;
86
+ if (error instanceof SyntaxError) {
87
+ try {
88
+ return JSON.parse(await fs.readFile(file, "utf8"));
89
+ } catch {
90
+ }
91
+ }
86
92
  throw error;
87
93
  }
88
94
  }
@@ -95,9 +101,16 @@ async function chmodBestEffort(file, mode) {
95
101
  }
96
102
  async function writeJson(file, value, mode) {
97
103
  await fs.mkdir(path.dirname(file), { recursive: true });
98
- await fs.writeFile(file, `${JSON.stringify(value, null, 2)}
104
+ const tmp = `${file}.tmp-${crypto.randomBytes(6).toString("hex")}`;
105
+ try {
106
+ await fs.writeFile(tmp, `${JSON.stringify(value, null, 2)}
99
107
  `, "utf8");
100
- if (mode !== void 0) await chmodBestEffort(file, mode);
108
+ if (mode !== void 0) await chmodBestEffort(tmp, mode);
109
+ await fs.rename(tmp, file);
110
+ } catch (error) {
111
+ await fs.rm(tmp, { force: true }).catch(() => void 0);
112
+ throw error;
113
+ }
101
114
  }
102
115
  async function appendJsonl(file, value) {
103
116
  await fs.mkdir(path.dirname(file), { recursive: true });
@@ -107,7 +120,15 @@ async function appendJsonl(file, value) {
107
120
  async function readJsonl(file) {
108
121
  try {
109
122
  const text = await fs.readFile(file, "utf8");
110
- return text.split(/\n/).filter(Boolean).map((line) => JSON.parse(line));
123
+ const out = [];
124
+ for (const line of text.split(/\n/)) {
125
+ if (!line) continue;
126
+ try {
127
+ out.push(JSON.parse(line));
128
+ } catch {
129
+ }
130
+ }
131
+ return out;
111
132
  } catch (error) {
112
133
  if (error.code === "ENOENT") return [];
113
134
  throw error;
@@ -1305,7 +1326,7 @@ async function handleOwnerApi(req, res, url, adapters) {
1305
1326
  return true;
1306
1327
  }
1307
1328
  if (req.method === "GET" && url.pathname === "/api/invite") {
1308
- const card = await store.writeCard();
1329
+ const card = await store.buildCard();
1309
1330
  const identity = await store.identity();
1310
1331
  const invite_url = `edgebook:invite:${Buffer.from(JSON.stringify(card), "utf8").toString("base64url")}`;
1311
1332
  sendJson(res, 200, { agent_id: identity.agent_id, display_name: identity.display_name, card_url: card.card_url, card, invite_url });
@@ -2757,6 +2778,8 @@ var EdgeBookDialoutClient = class {
2757
2778
  currentBackoff;
2758
2779
  opened;
2759
2780
  pendingSessionRevokes = /* @__PURE__ */ new Map();
2781
+ // Generic request_id-keyed RPC waiters for sessions_list / session_revoke_one.
2782
+ pendingRpc = /* @__PURE__ */ new Map();
2760
2783
  pendingMailboxSends = /* @__PURE__ */ new Map();
2761
2784
  constructor(options) {
2762
2785
  this.options = {
@@ -2796,6 +2819,30 @@ var EdgeBookDialoutClient = class {
2796
2819
  this.send(frame);
2797
2820
  return frame;
2798
2821
  }
2822
+ // List this agent's remembered devices on the host (ea-claude-057).
2823
+ async listSessionsAndWait(timeoutMs = 5e3) {
2824
+ const frame = await this.rpc("sessions_list", {}, "sessions_list_ok", timeoutMs);
2825
+ return frame.devices || [];
2826
+ }
2827
+ // Revoke ONE device by its public device_id (ea-claude-057).
2828
+ async revokeOneSessionAndWait(device_id, timeoutMs = 5e3) {
2829
+ const frame = await this.rpc("session_revoke_one", { device_id }, "session_revoke_one_ok", timeoutMs);
2830
+ return Boolean(frame.revoked);
2831
+ }
2832
+ // Small request/response helper over the dial-out socket, correlated by
2833
+ // request_id. `expect` documents the ack type; resolution is by request_id.
2834
+ async rpc(type, extra, expect, timeoutMs) {
2835
+ const request_id = crypto2.randomUUID();
2836
+ const promise = new Promise((resolve, reject) => {
2837
+ const timer = setTimeout(() => {
2838
+ this.pendingRpc.delete(request_id);
2839
+ reject(new EdgeBookError("host_rpc_timeout", `Timed out waiting for ${expect}`));
2840
+ }, timeoutMs);
2841
+ this.pendingRpc.set(request_id, { resolve, reject, timer });
2842
+ });
2843
+ this.send({ type, request_id, ...extra });
2844
+ return promise;
2845
+ }
2799
2846
  async revokeSessionsAndWait(timeoutMs = 5e3) {
2800
2847
  const frame = await createSessionsRevokeFrame(this.store);
2801
2848
  const ackPromise = new Promise((resolve, reject) => {
@@ -2938,6 +2985,16 @@ var EdgeBookDialoutClient = class {
2938
2985
  this.send({ type: "pong" });
2939
2986
  return;
2940
2987
  }
2988
+ if (frame.type === "sessions_list_ok" || frame.type === "session_revoke_one_ok") {
2989
+ const ack = frame;
2990
+ const pending = this.pendingRpc.get(ack.request_id || "");
2991
+ if (pending) {
2992
+ clearTimeout(pending.timer);
2993
+ this.pendingRpc.delete(ack.request_id || "");
2994
+ pending.resolve(frame);
2995
+ }
2996
+ return;
2997
+ }
2941
2998
  if (frame.type === "stand_down" || frame.type === "dialout_idle") {
2942
2999
  await this.standDown(frame);
2943
3000
  return;
@@ -3061,6 +3118,26 @@ async function sendSessionsRevoke(options) {
3061
3118
  await client.stop();
3062
3119
  return { ...frame, channel_id: ack.channel_id };
3063
3120
  }
3121
+ async function listSessions(options) {
3122
+ const client = new EdgeBookDialoutClient({ ...options, reconnect: false, openLocalApi: false });
3123
+ await client.start();
3124
+ await new Promise((resolve) => setTimeout(resolve, 0));
3125
+ try {
3126
+ return await client.listSessionsAndWait();
3127
+ } finally {
3128
+ await client.stop();
3129
+ }
3130
+ }
3131
+ async function revokeOneSession(options) {
3132
+ const client = new EdgeBookDialoutClient({ ...options, reconnect: false, openLocalApi: false });
3133
+ await client.start();
3134
+ await new Promise((resolve) => setTimeout(resolve, 0));
3135
+ try {
3136
+ return await client.revokeOneSessionAndWait(options.deviceId);
3137
+ } finally {
3138
+ await client.stop();
3139
+ }
3140
+ }
3064
3141
 
3065
3142
  // src/cli.ts
3066
3143
  function usage() {
@@ -3072,7 +3149,8 @@ Usage:
3072
3149
  Hosted reader:
3073
3150
  edge-book dialout [--host <ws-url>] [--home <dir>]
3074
3151
  edge-book pair [--host <ws-url>] [--ttl-ms <ms>] [--home <dir>]
3075
- edge-book sessions revoke [--host <ws-url>] [--home <dir>]
3152
+ edge-book sessions list [--host <ws-url>] [--home <dir>]
3153
+ edge-book sessions revoke [--device <id>] [--host <ws-url>] [--home <dir>]
3076
3154
 
3077
3155
  Local agent:
3078
3156
  edge-book doctor [--home <dir>]
@@ -3375,8 +3453,19 @@ Expires in: ${registration.frame.ttl_ms}ms`, json: registration };
3375
3453
  }
3376
3454
  if (command === "sessions") {
3377
3455
  const action = args.shift();
3456
+ if (action === "list") {
3457
+ const hostUrl = parseHost(args, ctx);
3458
+ const devices = await listSessions({ home, host: hostUrl, socketFactory: ctx.socketFactory });
3459
+ const lines = devices.length ? devices.map((d) => `${d.device_id} ${d.label} (added ${new Date(d.created_at).toISOString()}, last seen ${new Date(d.last_seen_at).toISOString()})`).join("\n") : "No remembered devices.";
3460
+ return { text: lines, json: { devices } };
3461
+ }
3378
3462
  if (action === "revoke") {
3379
3463
  const hostUrl = parseHost(args, ctx);
3464
+ const deviceId = takeFlag(args, "--device");
3465
+ if (deviceId) {
3466
+ const revoked = await revokeOneSession({ home, host: hostUrl, socketFactory: ctx.socketFactory, deviceId });
3467
+ return { text: revoked ? `Revoked device ${deviceId}` : `No device ${deviceId} found on your channel`, json: { device_id: deviceId, revoked } };
3468
+ }
3380
3469
  const frame = await sendSessionsRevoke({ home, host: hostUrl, socketFactory: ctx.socketFactory });
3381
3470
  const channel = frame.channel_id || "unknown-channel";
3382
3471
  return { text: `Received sessions_revoke_ok for request ${frame.request_id} on ${channel}`, json: frame };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edge-book",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Run your own Edge Book agent and connect it to the hosted reader.",
5
5
  "license": "MIT",
6
6
  "type": "module",