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.
- package/dist/edge-book.js +94 -5
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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 };
|