edge-book 0.17.0 → 0.18.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 +11 -5
- package/dist/edge-book.js +787 -112
- package/package.json +1 -1
package/dist/edge-book.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { realpathSync } from "fs";
|
|
5
|
-
import { fileURLToPath } from "url";
|
|
5
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6
6
|
|
|
7
7
|
// src/cli-shared.ts
|
|
8
|
-
import
|
|
9
|
-
import
|
|
8
|
+
import fs11 from "fs/promises";
|
|
9
|
+
import path11 from "path";
|
|
10
10
|
|
|
11
11
|
// src/dialout.ts
|
|
12
12
|
import crypto6 from "crypto";
|
|
@@ -18,7 +18,7 @@ import fs from "fs/promises";
|
|
|
18
18
|
import path from "path";
|
|
19
19
|
var DIALOUT_KEY_FILE = "host-dialout-key.json";
|
|
20
20
|
var KEY_FILE = DIALOUT_KEY_FILE;
|
|
21
|
-
var DEFAULT_PAIR_TTL_MS =
|
|
21
|
+
var DEFAULT_PAIR_TTL_MS = 10 * 60 * 1e3;
|
|
22
22
|
var PAIRING_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
|
23
23
|
function now() {
|
|
24
24
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -1702,12 +1702,13 @@ async function receiveFriendRequest(store, envelope) {
|
|
|
1702
1702
|
return contact;
|
|
1703
1703
|
}
|
|
1704
1704
|
async function pendingFriendRequests(store) {
|
|
1705
|
+
const contacts = await store.contacts();
|
|
1706
|
+
return Object.values(contacts).filter((c) => c.relationship_state === "request_received");
|
|
1707
|
+
}
|
|
1708
|
+
async function unnotifiedFriendRequests(store) {
|
|
1705
1709
|
const config = await store.config();
|
|
1706
1710
|
if (config.notify_on_friend_request === false) return [];
|
|
1707
|
-
|
|
1708
|
-
return Object.values(contacts).filter(
|
|
1709
|
-
(c) => c.relationship_state === "request_received" && !c.notified_at
|
|
1710
|
-
);
|
|
1711
|
+
return (await pendingFriendRequests(store)).filter((c) => !c.notified_at);
|
|
1711
1712
|
}
|
|
1712
1713
|
async function markFriendRequestNotified(store, peerAgentId) {
|
|
1713
1714
|
const contacts = await store.contacts();
|
|
@@ -2031,8 +2032,16 @@ async function updateConfig(store, input) {
|
|
|
2031
2032
|
if (input.onboarding_nudge_at !== void 0) next.onboarding_nudge_at = input.onboarding_nudge_at;
|
|
2032
2033
|
if (input.onboarding_nudge_count !== void 0) next.onboarding_nudge_count = input.onboarding_nudge_count;
|
|
2033
2034
|
if (input.greeter_mode !== void 0) next.greeter_mode = input.greeter_mode;
|
|
2035
|
+
if (input.notifier_prompt_ack !== void 0) next.notifier_prompt_ack = input.notifier_prompt_ack;
|
|
2036
|
+
if (input.notifier_nudge_at !== void 0) next.notifier_nudge_at = input.notifier_nudge_at;
|
|
2034
2037
|
if (input.support_inbox !== void 0) next.support_inbox = input.support_inbox;
|
|
2035
2038
|
if (input.greeter_welcome_object_id !== void 0) next.greeter_welcome_object_id = input.greeter_welcome_object_id;
|
|
2039
|
+
if (input.auto_update !== void 0) next.auto_update = input.auto_update;
|
|
2040
|
+
if (input.update_check_at !== void 0) next.update_check_at = input.update_check_at;
|
|
2041
|
+
if (input.update_latest_known !== void 0) next.update_latest_known = input.update_latest_known;
|
|
2042
|
+
if (input.update_nudge_at !== void 0) next.update_nudge_at = input.update_nudge_at;
|
|
2043
|
+
if (input.updated_at !== void 0) next.updated_at = input.updated_at;
|
|
2044
|
+
if (input.dialout_respawn_expected !== void 0) next.dialout_respawn_expected = input.dialout_respawn_expected;
|
|
2036
2045
|
await writeJson(store.file(CONFIG_FILE), next);
|
|
2037
2046
|
return next;
|
|
2038
2047
|
}
|
|
@@ -2744,11 +2753,15 @@ var EdgeBookStore = class {
|
|
|
2744
2753
|
async receiveFriendRequest(envelope) {
|
|
2745
2754
|
return receiveFriendRequest(this, envelope);
|
|
2746
2755
|
}
|
|
2747
|
-
//
|
|
2748
|
-
// agent has notifications disabled. Read-only — the notifier cron consumes this.
|
|
2756
|
+
// All inbound friend requests awaiting accept/decline (spec-139). Read-only.
|
|
2749
2757
|
async pendingFriendRequests() {
|
|
2750
2758
|
return pendingFriendRequests(this);
|
|
2751
2759
|
}
|
|
2760
|
+
// Inbound friend requests the human hasn't been told about yet. Empty when the
|
|
2761
|
+
// agent has notifications disabled. Read-only — the notifier cron consumes this.
|
|
2762
|
+
async unnotifiedFriendRequests() {
|
|
2763
|
+
return unnotifiedFriendRequests(this);
|
|
2764
|
+
}
|
|
2752
2765
|
// Stamp a request as notified so it won't surface again (idempotent sweep,
|
|
2753
2766
|
// mirrors expireEscalations).
|
|
2754
2767
|
async markFriendRequestNotified(peerAgentId) {
|
|
@@ -5110,6 +5123,246 @@ function gateHostFrame(frame) {
|
|
|
5110
5123
|
return { ok: false, frameType: type, errorPaths: paths };
|
|
5111
5124
|
}
|
|
5112
5125
|
|
|
5126
|
+
// src/update-drift.ts
|
|
5127
|
+
import fs9 from "fs/promises";
|
|
5128
|
+
|
|
5129
|
+
// src/self-update.ts
|
|
5130
|
+
import { execFile } from "child_process";
|
|
5131
|
+
import fs8 from "fs/promises";
|
|
5132
|
+
import path8 from "path";
|
|
5133
|
+
import { fileURLToPath } from "url";
|
|
5134
|
+
import { promisify } from "util";
|
|
5135
|
+
var execFileAsync = promisify(execFile);
|
|
5136
|
+
var REGISTRY_LATEST_URL = "https://registry.npmjs.org/edge-book/latest";
|
|
5137
|
+
var REGISTRY_TIMEOUT_MS = 3e3;
|
|
5138
|
+
var UPDATE_CHECK_THROTTLE_MS = 24 * 60 * 60 * 1e3;
|
|
5139
|
+
var SELF_UPDATE_LOCK = ".edge-book-self-update.lock";
|
|
5140
|
+
var LOCK_STALE_MS = 15 * 60 * 1e3;
|
|
5141
|
+
var SYSTEM_ROOT_PREFIXES = ["/usr/lib", "/usr/local/lib", "/usr/share", "/opt/homebrew", "/snap/", "/nix/store"];
|
|
5142
|
+
var cachedRunning;
|
|
5143
|
+
async function runningVersion() {
|
|
5144
|
+
if (cachedRunning) return cachedRunning;
|
|
5145
|
+
try {
|
|
5146
|
+
const pkg = JSON.parse(await fs8.readFile(new URL("../package.json", import.meta.url), "utf8"));
|
|
5147
|
+
cachedRunning = typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
5148
|
+
} catch {
|
|
5149
|
+
cachedRunning = "0.0.0";
|
|
5150
|
+
}
|
|
5151
|
+
return cachedRunning;
|
|
5152
|
+
}
|
|
5153
|
+
function compareVersions(a, b) {
|
|
5154
|
+
const pa = a.split("-")[0].split(".").map(Number);
|
|
5155
|
+
const pb = b.split("-")[0].split(".").map(Number);
|
|
5156
|
+
for (let i = 0; i < 3; i++) {
|
|
5157
|
+
const d = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
5158
|
+
if (d !== 0) return d < 0 ? -1 : 1;
|
|
5159
|
+
}
|
|
5160
|
+
return 0;
|
|
5161
|
+
}
|
|
5162
|
+
function majorOf(version) {
|
|
5163
|
+
return Number(version.split(".")[0]) || 0;
|
|
5164
|
+
}
|
|
5165
|
+
function autoUpdateAllowed(running, latest) {
|
|
5166
|
+
const runningMajor = majorOf(running);
|
|
5167
|
+
if (runningMajor >= 1 && majorOf(latest) !== runningMajor) return false;
|
|
5168
|
+
return true;
|
|
5169
|
+
}
|
|
5170
|
+
async function checkLatest(store, opts = {}) {
|
|
5171
|
+
const now4 = opts.now ?? Date.now();
|
|
5172
|
+
let cached;
|
|
5173
|
+
try {
|
|
5174
|
+
const config = await store.config();
|
|
5175
|
+
cached = config.update_latest_known;
|
|
5176
|
+
if (!opts.force && config.update_check_at !== void 0 && now4 - config.update_check_at < UPDATE_CHECK_THROTTLE_MS) {
|
|
5177
|
+
return cached;
|
|
5178
|
+
}
|
|
5179
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
5180
|
+
const response = await fetchImpl(REGISTRY_LATEST_URL, { signal: AbortSignal.timeout(REGISTRY_TIMEOUT_MS) });
|
|
5181
|
+
if (!response.ok) throw new Error(`registry ${response.status}`);
|
|
5182
|
+
const body = await response.json();
|
|
5183
|
+
if (typeof body.version !== "string" || !/^\d+\.\d+\.\d+/.test(body.version)) throw new Error("malformed registry response");
|
|
5184
|
+
await store.updateConfig({ update_check_at: now4, update_latest_known: body.version });
|
|
5185
|
+
return body.version;
|
|
5186
|
+
} catch {
|
|
5187
|
+
await store.updateConfig({ update_check_at: now4 }).catch(() => void 0);
|
|
5188
|
+
return cached;
|
|
5189
|
+
}
|
|
5190
|
+
}
|
|
5191
|
+
function resolveInstallRoot(modulePath = fileURLToPath(import.meta.url)) {
|
|
5192
|
+
let dir = path8.dirname(modulePath);
|
|
5193
|
+
for (; ; ) {
|
|
5194
|
+
const parent = path8.dirname(dir);
|
|
5195
|
+
if (path8.basename(dir) === "edge-book" && path8.basename(parent) === "node_modules") {
|
|
5196
|
+
return path8.dirname(parent);
|
|
5197
|
+
}
|
|
5198
|
+
if (parent === dir) return null;
|
|
5199
|
+
dir = parent;
|
|
5200
|
+
}
|
|
5201
|
+
}
|
|
5202
|
+
function notSelfUpdatable(detail, manualCommand) {
|
|
5203
|
+
return new EdgeBookError(
|
|
5204
|
+
"install_not_self_updatable",
|
|
5205
|
+
`install_not_self_updatable: ${detail}. Update manually with the right permissions: ${manualCommand}`
|
|
5206
|
+
);
|
|
5207
|
+
}
|
|
5208
|
+
async function assertSelfUpdatableRoot(root) {
|
|
5209
|
+
if (root === null) {
|
|
5210
|
+
throw notSelfUpdatable("this edge-book does not run from an npm install root", "npm install -g edge-book@latest");
|
|
5211
|
+
}
|
|
5212
|
+
const manual = `npm install edge-book@latest --prefix ${root}`;
|
|
5213
|
+
if (SYSTEM_ROOT_PREFIXES.some((prefix) => root === prefix.replace(/\/$/, "") || root.startsWith(prefix.endsWith("/") ? prefix : `${prefix}/`))) {
|
|
5214
|
+
throw notSelfUpdatable(`install root ${root} is system-managed`, manual);
|
|
5215
|
+
}
|
|
5216
|
+
try {
|
|
5217
|
+
await fs8.access(root, fs8.constants.W_OK);
|
|
5218
|
+
await fs8.access(path8.join(root, "node_modules"), fs8.constants.W_OK);
|
|
5219
|
+
} catch {
|
|
5220
|
+
throw notSelfUpdatable(`install root ${root} is not writable by this process`, manual);
|
|
5221
|
+
}
|
|
5222
|
+
return root;
|
|
5223
|
+
}
|
|
5224
|
+
async function acquireLock(lockPath, now4) {
|
|
5225
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
5226
|
+
try {
|
|
5227
|
+
await fs8.writeFile(lockPath, `${process.pid} ${new Date(now4).toISOString()}
|
|
5228
|
+
`, { flag: "wx" });
|
|
5229
|
+
return;
|
|
5230
|
+
} catch (error) {
|
|
5231
|
+
if (error.code !== "EEXIST") throw error;
|
|
5232
|
+
const stat = await fs8.stat(lockPath).catch(() => null);
|
|
5233
|
+
if (stat && now4 - stat.mtimeMs > LOCK_STALE_MS) {
|
|
5234
|
+
await fs8.rm(lockPath, { force: true });
|
|
5235
|
+
continue;
|
|
5236
|
+
}
|
|
5237
|
+
throw new EdgeBookError("update_in_progress", `update_in_progress: another self-update holds ${lockPath}`);
|
|
5238
|
+
}
|
|
5239
|
+
}
|
|
5240
|
+
throw new EdgeBookError("update_in_progress", `update_in_progress: could not acquire ${lockPath}`);
|
|
5241
|
+
}
|
|
5242
|
+
async function defaultNpmInstall(spec, root) {
|
|
5243
|
+
await execFileAsync("npm", ["install", spec, "--prefix", root, "--no-audit", "--no-fund"], { timeout: 18e4 });
|
|
5244
|
+
}
|
|
5245
|
+
async function defaultVerify(root) {
|
|
5246
|
+
const entry = path8.join(root, "node_modules", "edge-book", "dist", "edge-book.js");
|
|
5247
|
+
const { stdout } = await execFileAsync(process.execPath, [entry, "--version"], { timeout: 3e4 });
|
|
5248
|
+
return stdout.trim();
|
|
5249
|
+
}
|
|
5250
|
+
async function selfAgentId(store) {
|
|
5251
|
+
try {
|
|
5252
|
+
return (await store.identity()).agent_id;
|
|
5253
|
+
} catch {
|
|
5254
|
+
return "self";
|
|
5255
|
+
}
|
|
5256
|
+
}
|
|
5257
|
+
async function recordFailure(store, from, to, reason) {
|
|
5258
|
+
await store.audit("update.self", await selfAgentId(store), { from, to, ok: false, reason });
|
|
5259
|
+
await logEvent(store, "update.failed", { from, to, reason });
|
|
5260
|
+
}
|
|
5261
|
+
function restartAdvice(autoRespawn) {
|
|
5262
|
+
return autoRespawn ? "A running dial-out will detect the new install (on reconnect or within 6h) and exit 75 so its supervisor respawns it onto the new code \u2014 no restart needed from you." : "Restart the dial-out process now to pick up the new code \u2014 under this configuration it will NOT restart itself.";
|
|
5263
|
+
}
|
|
5264
|
+
async function performUpdate(store, root, from, latest, opts, now4) {
|
|
5265
|
+
const install = opts.npmInstall ?? defaultNpmInstall;
|
|
5266
|
+
try {
|
|
5267
|
+
await install(`edge-book@${latest}`, root);
|
|
5268
|
+
} catch (error) {
|
|
5269
|
+
await recordFailure(store, from, latest, eventErrorCode(error));
|
|
5270
|
+
throw new EdgeBookError("update_failed", `update_failed: npm install edge-book@${latest} --prefix ${root} failed; the previous install remains in place.`);
|
|
5271
|
+
}
|
|
5272
|
+
let reported;
|
|
5273
|
+
try {
|
|
5274
|
+
reported = (await (opts.verify ?? defaultVerify)(root)).trim();
|
|
5275
|
+
} catch (error) {
|
|
5276
|
+
reported = `spawn_failed:${eventErrorCode(error)}`;
|
|
5277
|
+
}
|
|
5278
|
+
if (reported !== latest) {
|
|
5279
|
+
await recordFailure(store, from, latest, `verify_mismatch:${reported}`);
|
|
5280
|
+
throw new EdgeBookError("update_failed", `update_failed: the new build reports "${reported}" instead of ${latest}; not trusting it. The npm tree at ${root} may need a manual reinstall.`);
|
|
5281
|
+
}
|
|
5282
|
+
await store.audit("update.self", await selfAgentId(store), { from, to: latest });
|
|
5283
|
+
await logEvent(store, "update.self", { from, to: latest });
|
|
5284
|
+
const config = await store.updateConfig({ updated_at: now4, update_latest_known: latest });
|
|
5285
|
+
const autoRespawn = (config.auto_update ?? "auto") === "auto" && config.dialout_respawn_expected !== false;
|
|
5286
|
+
return { status: "updated", from, to: latest, text: `Updated edge-book ${from} \u2192 ${latest} in ${root}.
|
|
5287
|
+
${restartAdvice(autoRespawn)}` };
|
|
5288
|
+
}
|
|
5289
|
+
async function selfUpdate(store, opts = {}) {
|
|
5290
|
+
const now4 = opts.now ?? Date.now();
|
|
5291
|
+
const from = opts.running ?? await runningVersion();
|
|
5292
|
+
const config = await store.config();
|
|
5293
|
+
const mode = config.auto_update ?? "auto";
|
|
5294
|
+
const latest = await checkLatest(store, { now: now4, fetchImpl: opts.fetchImpl, force: true });
|
|
5295
|
+
if (!latest) {
|
|
5296
|
+
if (opts.ifStale) return { status: "current", from, text: "" };
|
|
5297
|
+
throw new EdgeBookError("registry_unreachable", "registry_unreachable: could not determine the latest edge-book version from the npm registry.");
|
|
5298
|
+
}
|
|
5299
|
+
if (compareVersions(latest, from) <= 0) {
|
|
5300
|
+
return { status: "current", from, to: latest, text: opts.ifStale ? "" : `edge-book ${from} is current (latest published: ${latest}); never downgrading.` };
|
|
5301
|
+
}
|
|
5302
|
+
if (opts.ifStale && (mode !== "auto" || !autoUpdateAllowed(from, latest))) {
|
|
5303
|
+
return { status: "skipped", from, to: latest, text: "" };
|
|
5304
|
+
}
|
|
5305
|
+
const root = await assertSelfUpdatableRoot(opts.installRoot !== void 0 ? opts.installRoot : resolveInstallRoot());
|
|
5306
|
+
if (opts.dryRun) {
|
|
5307
|
+
return { status: "dry_run", from, to: latest, text: `Would update edge-book ${from} \u2192 ${latest} in ${root} (dry run; nothing installed).` };
|
|
5308
|
+
}
|
|
5309
|
+
const lockPath = path8.join(root, SELF_UPDATE_LOCK);
|
|
5310
|
+
await acquireLock(lockPath, now4);
|
|
5311
|
+
try {
|
|
5312
|
+
return await performUpdate(store, root, from, latest, opts, now4);
|
|
5313
|
+
} finally {
|
|
5314
|
+
await fs8.rm(lockPath, { force: true }).catch(() => void 0);
|
|
5315
|
+
}
|
|
5316
|
+
}
|
|
5317
|
+
|
|
5318
|
+
// src/update-drift.ts
|
|
5319
|
+
var DRIFT_CHECK_INTERVAL_MS = 6 * 60 * 60 * 1e3;
|
|
5320
|
+
async function installedVersion() {
|
|
5321
|
+
try {
|
|
5322
|
+
const pkg = JSON.parse(await fs9.readFile(new URL("../package.json", import.meta.url), "utf8"));
|
|
5323
|
+
return typeof pkg.version === "string" ? pkg.version : void 0;
|
|
5324
|
+
} catch {
|
|
5325
|
+
return void 0;
|
|
5326
|
+
}
|
|
5327
|
+
}
|
|
5328
|
+
function createUpdateDriftMonitor(store, deps = {}) {
|
|
5329
|
+
const running = deps.running !== void 0 ? Promise.resolve(deps.running) : runningVersion();
|
|
5330
|
+
const installed = deps.installed ?? installedVersion;
|
|
5331
|
+
const exit = deps.exit ?? ((code) => process.exit(code));
|
|
5332
|
+
const check2 = deps.checkLatestImpl ?? ((s) => checkLatest(s));
|
|
5333
|
+
let timer;
|
|
5334
|
+
async function driftCheck() {
|
|
5335
|
+
try {
|
|
5336
|
+
const inMemory = await running;
|
|
5337
|
+
const onDisk = await installed();
|
|
5338
|
+
if (!onDisk || onDisk === inMemory) return;
|
|
5339
|
+
const config = await store.config();
|
|
5340
|
+
const mode = config.auto_update ?? "auto";
|
|
5341
|
+
const respawnExpected = config.dialout_respawn_expected !== false;
|
|
5342
|
+
await logEvent(store, "dialout.version_drift", { running: inMemory, installed: onDisk, mode, respawn_expected: respawnExpected });
|
|
5343
|
+
if (mode !== "auto" || !respawnExpected) return;
|
|
5344
|
+
console.log(`dialout.restart_for_update: installed edge-book ${onDisk} != running ${inMemory} \u2014 exiting 75 for supervisor respawn`);
|
|
5345
|
+
await logEvent(store, "dialout.restart_for_update", { from: inMemory, to: onDisk });
|
|
5346
|
+
exit(75);
|
|
5347
|
+
} catch {
|
|
5348
|
+
}
|
|
5349
|
+
}
|
|
5350
|
+
return {
|
|
5351
|
+
onConnect: () => driftCheck(),
|
|
5352
|
+
start() {
|
|
5353
|
+
if (timer) return;
|
|
5354
|
+
timer = setInterval(() => {
|
|
5355
|
+
void check2(store).catch(() => void 0).then(() => driftCheck());
|
|
5356
|
+
}, deps.intervalMs ?? DRIFT_CHECK_INTERVAL_MS);
|
|
5357
|
+
timer.unref?.();
|
|
5358
|
+
},
|
|
5359
|
+
stop() {
|
|
5360
|
+
if (timer) clearInterval(timer);
|
|
5361
|
+
timer = void 0;
|
|
5362
|
+
}
|
|
5363
|
+
};
|
|
5364
|
+
}
|
|
5365
|
+
|
|
5113
5366
|
// src/dialout-oneshot.ts
|
|
5114
5367
|
async function sendPairRegistration(options) {
|
|
5115
5368
|
const client = new EdgeBookDialoutClient({ ...options, reconnect: false, openLocalApi: false });
|
|
@@ -5190,6 +5443,7 @@ function addSocketListener(socket, event, handler) {
|
|
|
5190
5443
|
}
|
|
5191
5444
|
var EdgeBookDialoutClient = class {
|
|
5192
5445
|
options;
|
|
5446
|
+
driftMonitor;
|
|
5193
5447
|
store;
|
|
5194
5448
|
socket;
|
|
5195
5449
|
localApi;
|
|
@@ -5219,6 +5473,7 @@ var EdgeBookDialoutClient = class {
|
|
|
5219
5473
|
home: options.home
|
|
5220
5474
|
};
|
|
5221
5475
|
this.store = new EdgeBookStore({ home: options.home });
|
|
5476
|
+
this.driftMonitor = options.driftMonitor ?? createUpdateDriftMonitor(this.store);
|
|
5222
5477
|
this.currentBackoff = this.options.backoffMs;
|
|
5223
5478
|
}
|
|
5224
5479
|
async start() {
|
|
@@ -5227,6 +5482,7 @@ var EdgeBookDialoutClient = class {
|
|
|
5227
5482
|
}
|
|
5228
5483
|
async stop() {
|
|
5229
5484
|
this.stopped = true;
|
|
5485
|
+
this.driftMonitor.stop();
|
|
5230
5486
|
if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
|
|
5231
5487
|
if (this.heartbeat) clearInterval(this.heartbeat);
|
|
5232
5488
|
this.socket?.close();
|
|
@@ -5235,7 +5491,15 @@ var EdgeBookDialoutClient = class {
|
|
|
5235
5491
|
}
|
|
5236
5492
|
async pair(ttlMs = DEFAULT_PAIR_TTL_MS) {
|
|
5237
5493
|
const registration = await createPairRegistration(this.store, ttlMs);
|
|
5494
|
+
const ackPromise = this.awaitAck(registration.frame.request_id, "pair_register", "pair_register_ok", 5e3);
|
|
5238
5495
|
this.send(registration.frame);
|
|
5496
|
+
try {
|
|
5497
|
+
const ack = await ackPromise;
|
|
5498
|
+
if (ack.type === "pair_register_err") throw new EdgeBookError("pair_register_rejected", String(ack.error || "Host rejected the pairing registration"));
|
|
5499
|
+
if (typeof ack.expires_at === "number") registration.expires_at = ack.expires_at;
|
|
5500
|
+
} catch (error) {
|
|
5501
|
+
if (!(error instanceof EdgeBookError && error.code === "host_rpc_timeout")) throw error;
|
|
5502
|
+
}
|
|
5239
5503
|
return registration;
|
|
5240
5504
|
}
|
|
5241
5505
|
// Wait up to ttlMs for a pair_complete frame from the host.
|
|
@@ -5275,15 +5539,20 @@ var EdgeBookDialoutClient = class {
|
|
|
5275
5539
|
// request_id. `expect` documents the ack type; resolution is by request_id.
|
|
5276
5540
|
async rpc(type, extra, expect, timeoutMs) {
|
|
5277
5541
|
const request_id = crypto6.randomUUID();
|
|
5278
|
-
const promise =
|
|
5542
|
+
const promise = this.awaitAck(request_id, type, expect, timeoutMs);
|
|
5543
|
+
this.send({ type, request_id, ...extra });
|
|
5544
|
+
return promise;
|
|
5545
|
+
}
|
|
5546
|
+
// Register a pending ack for an already-built frame's request_id (used by
|
|
5547
|
+
// pair(), whose frame is minted in dialout-key.ts rather than by rpc()).
|
|
5548
|
+
awaitAck(request_id, rpcType, expect, timeoutMs) {
|
|
5549
|
+
return new Promise((resolve, reject) => {
|
|
5279
5550
|
const timer = setTimeout(() => {
|
|
5280
5551
|
this.pendingRpc.delete(request_id);
|
|
5281
5552
|
reject(new EdgeBookError("host_rpc_timeout", `Timed out waiting for ${expect}`));
|
|
5282
5553
|
}, timeoutMs);
|
|
5283
|
-
this.pendingRpc.set(request_id, { resolve, reject, timer, rpcType
|
|
5554
|
+
this.pendingRpc.set(request_id, { resolve, reject, timer, rpcType });
|
|
5284
5555
|
});
|
|
5285
|
-
this.send({ type, request_id, ...extra });
|
|
5286
|
-
return promise;
|
|
5287
5556
|
}
|
|
5288
5557
|
async revokeSessionsAndWait(timeoutMs = 5e3) {
|
|
5289
5558
|
const frame = await createSessionsRevokeFrame(this.store);
|
|
@@ -5437,6 +5706,8 @@ var EdgeBookDialoutClient = class {
|
|
|
5437
5706
|
this.opened?.resolve();
|
|
5438
5707
|
this.opened = void 0;
|
|
5439
5708
|
void logEvent(this.store, "dialout.connected", { host: this.options.host });
|
|
5709
|
+
void this.driftMonitor.onConnect();
|
|
5710
|
+
this.driftMonitor.start();
|
|
5440
5711
|
try {
|
|
5441
5712
|
const identity = await this.store.identity();
|
|
5442
5713
|
if (shouldClaimHandle(identity.handle)) {
|
|
@@ -5465,7 +5736,7 @@ var EdgeBookDialoutClient = class {
|
|
|
5465
5736
|
this.send({ type: "pong" });
|
|
5466
5737
|
return;
|
|
5467
5738
|
}
|
|
5468
|
-
if (frame.type === "sessions_list_ok" || frame.type === "session_revoke_one_ok" || frame.type === "mailbox_status_ok" || frame.type === "mailbox_status_err") {
|
|
5739
|
+
if (frame.type === "sessions_list_ok" || frame.type === "session_revoke_one_ok" || frame.type === "mailbox_status_ok" || frame.type === "mailbox_status_err" || frame.type === "pair_register_ok" || frame.type === "pair_register_err") {
|
|
5469
5740
|
const ack = frame;
|
|
5470
5741
|
const pending = this.pendingRpc.get(ack.request_id || "");
|
|
5471
5742
|
if (pending) {
|
|
@@ -5479,7 +5750,6 @@ var EdgeBookDialoutClient = class {
|
|
|
5479
5750
|
await this.standDown(frame);
|
|
5480
5751
|
return;
|
|
5481
5752
|
}
|
|
5482
|
-
if (frame.type === "pair_register_ok" || frame.type === "pair_register_err") return;
|
|
5483
5753
|
if (frame.type === "pair_complete") {
|
|
5484
5754
|
this.pairCompleteWaiter.onFrame(frame);
|
|
5485
5755
|
return;
|
|
@@ -5537,6 +5807,7 @@ var EdgeBookDialoutClient = class {
|
|
|
5537
5807
|
}
|
|
5538
5808
|
async standDown(frame) {
|
|
5539
5809
|
this.stopped = true;
|
|
5810
|
+
this.driftMonitor.stop();
|
|
5540
5811
|
await logEvent(this.store, "dialout.stand_down", { host: this.options.host, reason: frame.reason, idle_ms: frame.idle_ms });
|
|
5541
5812
|
if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
|
|
5542
5813
|
if (this.heartbeat) clearInterval(this.heartbeat);
|
|
@@ -5615,22 +5886,22 @@ var EdgeBookDialoutClient = class {
|
|
|
5615
5886
|
};
|
|
5616
5887
|
|
|
5617
5888
|
// src/http-relay.ts
|
|
5618
|
-
import
|
|
5889
|
+
import fs10 from "fs/promises";
|
|
5619
5890
|
import http2 from "http";
|
|
5620
|
-
import
|
|
5891
|
+
import path9 from "path";
|
|
5621
5892
|
function relayFile(store, agentId) {
|
|
5622
|
-
return
|
|
5893
|
+
return path9.join(store, `${encodeURIComponent(agentId)}.jsonl`);
|
|
5623
5894
|
}
|
|
5624
5895
|
async function appendRelayEnvelope(store, agentId, envelope) {
|
|
5625
|
-
await
|
|
5626
|
-
await
|
|
5896
|
+
await fs10.mkdir(store, { recursive: true });
|
|
5897
|
+
await fs10.appendFile(relayFile(store, agentId), `${JSON.stringify(envelope)}
|
|
5627
5898
|
`, "utf8");
|
|
5628
5899
|
}
|
|
5629
5900
|
async function drainRelayEnvelopes(store, agentId) {
|
|
5630
5901
|
const file = relayFile(store, agentId);
|
|
5631
5902
|
try {
|
|
5632
|
-
const text = await
|
|
5633
|
-
await
|
|
5903
|
+
const text = await fs10.readFile(file, "utf8");
|
|
5904
|
+
await fs10.writeFile(file, "", "utf8");
|
|
5634
5905
|
return text.split(/\n/).filter(Boolean).map((line) => JSON.parse(line));
|
|
5635
5906
|
} catch (error) {
|
|
5636
5907
|
if (error.code === "ENOENT") return [];
|
|
@@ -5690,11 +5961,11 @@ async function pullRelayEnvelopes(relayBaseUrl, recipientAgentId) {
|
|
|
5690
5961
|
}
|
|
5691
5962
|
|
|
5692
5963
|
// src/store-outbox.ts
|
|
5693
|
-
import
|
|
5964
|
+
import path10 from "path";
|
|
5694
5965
|
var OUTBOX_CAP = 200;
|
|
5695
5966
|
var DEFAULT_STALE_QUEUE_MS = 10 * 60 * 1e3;
|
|
5696
5967
|
function outboxPath(home) {
|
|
5697
|
-
return
|
|
5968
|
+
return path10.join(resolveHome(home), OUTBOX_FILE);
|
|
5698
5969
|
}
|
|
5699
5970
|
async function readOutbox(home) {
|
|
5700
5971
|
return readJson(outboxPath(home), []);
|
|
@@ -5764,7 +6035,7 @@ function takeRepeated(args, flag) {
|
|
|
5764
6035
|
return out;
|
|
5765
6036
|
}
|
|
5766
6037
|
async function readEnvelope(filePath) {
|
|
5767
|
-
return JSON.parse(await
|
|
6038
|
+
return JSON.parse(await fs11.readFile(path11.resolve(filePath), "utf8"));
|
|
5768
6039
|
}
|
|
5769
6040
|
async function deliverToEndpoint(envelope, endpoint) {
|
|
5770
6041
|
await postEnvelope(endpoint, envelope);
|
|
@@ -5844,58 +6115,30 @@ ${buildHandleNudge(suggestion)}` };
|
|
|
5844
6115
|
}
|
|
5845
6116
|
}
|
|
5846
6117
|
|
|
5847
|
-
// src/onboarding-nudge.ts
|
|
5848
|
-
var ONBOARDING_NUDGE_THROTTLE_MS = 6 * 60 * 60 * 1e3;
|
|
5849
|
-
var ONBOARDING_NUDGE_MAX_EMITS = 3;
|
|
5850
|
-
function buildOnboardingNudgeText() {
|
|
5851
|
-
return [
|
|
5852
|
-
`---`,
|
|
5853
|
-
`Onboarding incomplete: this agent has no friends and no shares yet \u2014 the human was likely never onboarded.`,
|
|
5854
|
-
`Load skills/edge-book/prompts/onboard.md and run the first-session script with your human now.`
|
|
5855
|
-
].join("\n");
|
|
5856
|
-
}
|
|
5857
|
-
async function maybeAppendOnboardingNudge(store, command, result, now4 = Date.now()) {
|
|
5858
|
-
if (!NUDGE_COMMANDS.has(command)) return result;
|
|
5859
|
-
try {
|
|
5860
|
-
await store.identity();
|
|
5861
|
-
const config = await store.config();
|
|
5862
|
-
const count = config.onboarding_nudge_count ?? 0;
|
|
5863
|
-
if (count >= ONBOARDING_NUDGE_MAX_EMITS) return result;
|
|
5864
|
-
if (config.onboarding_nudge_at !== void 0 && now4 - config.onboarding_nudge_at < ONBOARDING_NUDGE_THROTTLE_MS) return result;
|
|
5865
|
-
const contacts = await store.contacts();
|
|
5866
|
-
if (Object.keys(contacts).length > 0) return result;
|
|
5867
|
-
const objects2 = await store.objects();
|
|
5868
|
-
if (Object.keys(objects2).length > 0) return result;
|
|
5869
|
-
await store.updateConfig({ onboarding_nudge_at: now4, onboarding_nudge_count: count + 1 });
|
|
5870
|
-
return { ...result, text: `${result.text}
|
|
5871
|
-
${buildOnboardingNudgeText()}` };
|
|
5872
|
-
} catch {
|
|
5873
|
-
return result;
|
|
5874
|
-
}
|
|
5875
|
-
}
|
|
5876
|
-
|
|
5877
|
-
// src/cli-identity.ts
|
|
5878
|
-
import fs11 from "fs/promises";
|
|
5879
|
-
import path11 from "path";
|
|
5880
|
-
|
|
5881
|
-
// src/doctor.ts
|
|
5882
|
-
import fs10 from "fs/promises";
|
|
5883
|
-
|
|
5884
6118
|
// src/host-cron.ts
|
|
5885
6119
|
import { existsSync } from "fs";
|
|
5886
6120
|
import { execFileSync } from "child_process";
|
|
5887
6121
|
var FRIEND_REQUESTS_CRON_NAME = "Edge Book \u2014 friend requests";
|
|
5888
6122
|
var DEFAULT_FRIEND_REQUESTS_SCHEDULE = "*/20 * * * *";
|
|
5889
6123
|
var HERMES_BIN_CANDIDATES = ["/opt/hermes/.venv/bin/hermes"];
|
|
6124
|
+
var NOTIFIER_PROMPT_VERSION = 3;
|
|
6125
|
+
function hermesCliDetected() {
|
|
6126
|
+
return HERMES_BIN_CANDIDATES.some((p) => existsSync(p));
|
|
6127
|
+
}
|
|
5890
6128
|
function buildFriendRequestsPrompt(home) {
|
|
5891
6129
|
return [
|
|
5892
6130
|
"You are the Edge Book friend-request notifier. Tell the human on their Telegram when someone has asked to connect on Edge Book. Hermes delivers your final assistant reply to their chat.",
|
|
5893
6131
|
"",
|
|
5894
6132
|
"This runs every 20 minutes; most runs there will be nothing pending. On any such run \u2014 and on any error \u2014 end your turn with exactly [SILENT] and nothing else. [SILENT] tells Hermes to send no message.",
|
|
5895
6133
|
"",
|
|
5896
|
-
"
|
|
5897
|
-
` edge-book
|
|
5898
|
-
|
|
6134
|
+
"0. Keep this agent's edge-book install current (run once, first; spec-142):",
|
|
6135
|
+
` edge-book self-update --if-stale --home ${home}`,
|
|
6136
|
+
` If edge-book is not on PATH, use: npm exec -y edge-book@latest -- self-update --if-stale --home ${home}`,
|
|
6137
|
+
" Ignore this step's output and continue to step 1 even if it errors.",
|
|
6138
|
+
"",
|
|
6139
|
+
"1. List new (not-yet-surfaced) requests (run once):",
|
|
6140
|
+
` edge-book friend pending --new --home ${home} --json`,
|
|
6141
|
+
` If edge-book is not on PATH, use: npm exec -y edge-book -- friend pending --new --home ${home} --json`,
|
|
5899
6142
|
" If the command errors, Edge Book is unavailable, or the list is empty ([]) \u2192 end your turn with exactly [SILENT].",
|
|
5900
6143
|
"",
|
|
5901
6144
|
'2. Otherwise write ONE short, warm message. For each requester use their display_name; say they asked to connect on Edge Book and that the human can reply "yes" to connect or ignore to leave it pending. No internal IDs, no JSON.',
|
|
@@ -5913,9 +6156,19 @@ function ensureNotifierCron(opts) {
|
|
|
5913
6156
|
} catch (e) {
|
|
5914
6157
|
return { status: "error", detail: e instanceof Error ? e.message : String(e) };
|
|
5915
6158
|
}
|
|
5916
|
-
if (listing.includes(FRIEND_REQUESTS_CRON_NAME)) return { status: "already_present" };
|
|
5917
6159
|
const schedule = opts.schedule ?? DEFAULT_FRIEND_REQUESTS_SCHEDULE;
|
|
5918
6160
|
const prompt = buildFriendRequestsPrompt(opts.home);
|
|
6161
|
+
let recreating = false;
|
|
6162
|
+
if (listing.includes(FRIEND_REQUESTS_CRON_NAME)) {
|
|
6163
|
+
const existing = opts.runner.getPrompt(FRIEND_REQUESTS_CRON_NAME);
|
|
6164
|
+
if (existing === null || existing.includes(prompt)) return { status: "already_present" };
|
|
6165
|
+
try {
|
|
6166
|
+
opts.runner.remove(FRIEND_REQUESTS_CRON_NAME);
|
|
6167
|
+
} catch (e) {
|
|
6168
|
+
return { status: "error", detail: e instanceof Error ? e.message : String(e) };
|
|
6169
|
+
}
|
|
6170
|
+
recreating = true;
|
|
6171
|
+
}
|
|
5919
6172
|
const args = [
|
|
5920
6173
|
"cron",
|
|
5921
6174
|
"create",
|
|
@@ -5930,7 +6183,7 @@ function ensureNotifierCron(opts) {
|
|
|
5930
6183
|
];
|
|
5931
6184
|
try {
|
|
5932
6185
|
opts.runner.create(args);
|
|
5933
|
-
return { status: "installed" };
|
|
6186
|
+
return { status: recreating ? "updated" : "installed" };
|
|
5934
6187
|
} catch (e) {
|
|
5935
6188
|
return { status: "error", detail: e instanceof Error ? e.message : String(e) };
|
|
5936
6189
|
}
|
|
@@ -5942,6 +6195,20 @@ function defaultHermesRunner() {
|
|
|
5942
6195
|
list: () => bin ? execFileSync(bin, ["cron", "list"], { encoding: "utf8" }) : "",
|
|
5943
6196
|
create: (args) => {
|
|
5944
6197
|
if (bin) execFileSync(bin, args, { stdio: ["ignore", "ignore", "pipe"] });
|
|
6198
|
+
},
|
|
6199
|
+
// Best-effort prompt read: `hermes cron show <name>` prints the job
|
|
6200
|
+
// (including its prompt). Any failure → null, which the caller treats as
|
|
6201
|
+
// "unknown — keep the existing job" so an older Hermes never churns.
|
|
6202
|
+
getPrompt: (name) => {
|
|
6203
|
+
if (!bin) return null;
|
|
6204
|
+
try {
|
|
6205
|
+
return execFileSync(bin, ["cron", "show", name], { encoding: "utf8" });
|
|
6206
|
+
} catch {
|
|
6207
|
+
return null;
|
|
6208
|
+
}
|
|
6209
|
+
},
|
|
6210
|
+
remove: (name) => {
|
|
6211
|
+
if (bin) execFileSync(bin, ["cron", "delete", name], { stdio: ["ignore", "ignore", "pipe"] });
|
|
5945
6212
|
}
|
|
5946
6213
|
};
|
|
5947
6214
|
}
|
|
@@ -5989,7 +6256,69 @@ function ensureGreeterCron(opts) {
|
|
|
5989
6256
|
}
|
|
5990
6257
|
}
|
|
5991
6258
|
|
|
6259
|
+
// src/notifier-nudge.ts
|
|
6260
|
+
var NOTIFIER_NUDGE_THROTTLE_MS = 6 * 60 * 60 * 1e3;
|
|
6261
|
+
function buildNotifierNudgeText(home) {
|
|
6262
|
+
return [
|
|
6263
|
+
`---`,
|
|
6264
|
+
`Your scheduled job "${FRIEND_REQUESTS_CRON_NAME}" runs an outdated prompt and silently loses friend requests.`,
|
|
6265
|
+
`Using your scheduler tool, update that job's prompt to the exact output of: edge-book ensure-notifier --print-prompt --home ${home}`,
|
|
6266
|
+
`(If the job does not exist, create it with schedule ${DEFAULT_FRIEND_REQUESTS_SCHEDULE} and telegram delivery.)`,
|
|
6267
|
+
`Then run: edge-book ensure-notifier --ack --home ${home} so this notice retires.`
|
|
6268
|
+
].join("\n");
|
|
6269
|
+
}
|
|
6270
|
+
async function maybeAppendNotifierNudge(store, command, result, now4 = Date.now(), hermesDetected = hermesCliDetected) {
|
|
6271
|
+
if (!NUDGE_COMMANDS.has(command)) return result;
|
|
6272
|
+
try {
|
|
6273
|
+
await store.identity();
|
|
6274
|
+
const config = await store.config();
|
|
6275
|
+
if ((config.notifier_prompt_ack ?? 0) >= NOTIFIER_PROMPT_VERSION) return result;
|
|
6276
|
+
if (hermesDetected()) return result;
|
|
6277
|
+
if (config.notifier_nudge_at !== void 0 && now4 - config.notifier_nudge_at < NOTIFIER_NUDGE_THROTTLE_MS) return result;
|
|
6278
|
+
await store.updateConfig({ notifier_nudge_at: now4 });
|
|
6279
|
+
return { ...result, text: `${result.text}
|
|
6280
|
+
${buildNotifierNudgeText(store.home)}` };
|
|
6281
|
+
} catch {
|
|
6282
|
+
return result;
|
|
6283
|
+
}
|
|
6284
|
+
}
|
|
6285
|
+
|
|
6286
|
+
// src/onboarding-nudge.ts
|
|
6287
|
+
var ONBOARDING_NUDGE_THROTTLE_MS = 6 * 60 * 60 * 1e3;
|
|
6288
|
+
var ONBOARDING_NUDGE_MAX_EMITS = 3;
|
|
6289
|
+
function buildOnboardingNudgeText() {
|
|
6290
|
+
return [
|
|
6291
|
+
`---`,
|
|
6292
|
+
`Onboarding incomplete: this agent has no friends and no shares yet \u2014 the human was likely never onboarded.`,
|
|
6293
|
+
`Load skills/edge-book/prompts/onboard.md and run the first-session script with your human now.`
|
|
6294
|
+
].join("\n");
|
|
6295
|
+
}
|
|
6296
|
+
async function maybeAppendOnboardingNudge(store, command, result, now4 = Date.now()) {
|
|
6297
|
+
if (!NUDGE_COMMANDS.has(command)) return result;
|
|
6298
|
+
try {
|
|
6299
|
+
await store.identity();
|
|
6300
|
+
const config = await store.config();
|
|
6301
|
+
const count = config.onboarding_nudge_count ?? 0;
|
|
6302
|
+
if (count >= ONBOARDING_NUDGE_MAX_EMITS) return result;
|
|
6303
|
+
if (config.onboarding_nudge_at !== void 0 && now4 - config.onboarding_nudge_at < ONBOARDING_NUDGE_THROTTLE_MS) return result;
|
|
6304
|
+
const contacts = await store.contacts();
|
|
6305
|
+
if (Object.keys(contacts).length > 0) return result;
|
|
6306
|
+
const objects2 = await store.objects();
|
|
6307
|
+
if (Object.keys(objects2).length > 0) return result;
|
|
6308
|
+
await store.updateConfig({ onboarding_nudge_at: now4, onboarding_nudge_count: count + 1 });
|
|
6309
|
+
return { ...result, text: `${result.text}
|
|
6310
|
+
${buildOnboardingNudgeText()}` };
|
|
6311
|
+
} catch {
|
|
6312
|
+
return result;
|
|
6313
|
+
}
|
|
6314
|
+
}
|
|
6315
|
+
|
|
6316
|
+
// src/cli-identity.ts
|
|
6317
|
+
import fs13 from "fs/promises";
|
|
6318
|
+
import path12 from "path";
|
|
6319
|
+
|
|
5992
6320
|
// src/doctor.ts
|
|
6321
|
+
import fs12 from "fs/promises";
|
|
5993
6322
|
var DOCTOR_EVENT_TAIL = 50;
|
|
5994
6323
|
var DOCTOR_TRACE_TAIL = 10;
|
|
5995
6324
|
var DOCTOR_AUDIT_TAIL = 20;
|
|
@@ -6008,7 +6337,7 @@ function recentTraces(events, limit = DOCTOR_TRACE_TAIL) {
|
|
|
6008
6337
|
}
|
|
6009
6338
|
async function packageVersion() {
|
|
6010
6339
|
try {
|
|
6011
|
-
const pkg = JSON.parse(await
|
|
6340
|
+
const pkg = JSON.parse(await fs12.readFile(new URL("../package.json", import.meta.url), "utf8"));
|
|
6012
6341
|
return pkg.version ?? "unknown";
|
|
6013
6342
|
} catch {
|
|
6014
6343
|
return "unknown";
|
|
@@ -6082,7 +6411,7 @@ async function buildDoctorReport(store, opts) {
|
|
|
6082
6411
|
opts.fetchImpl ?? fetch,
|
|
6083
6412
|
opts.timeoutMs ?? DEFAULT_RELAY_TIMEOUT_MS
|
|
6084
6413
|
);
|
|
6085
|
-
const keyPresent = await
|
|
6414
|
+
const keyPresent = await fs12.stat(store.file(DIALOUT_KEY_FILE)).then(() => true).catch(() => false);
|
|
6086
6415
|
const lastConnected = await lastEvent(store, "dialout.connected");
|
|
6087
6416
|
const lastDisconnected = await lastEvent(store, "dialout.disconnected");
|
|
6088
6417
|
const contacts = await store.contacts();
|
|
@@ -6317,6 +6646,7 @@ function buildOnboardingNote(opts = {}) {
|
|
|
6317
6646
|
` 2. Ask: public agent name? share your human's name with friends, or keep private?`,
|
|
6318
6647
|
` Then propose a findable name (slug of the agent name) \u2192 edge-book handle set <slug>`,
|
|
6319
6648
|
` 3. First friend: if they give you a name \u2192 edge-book resolve <name>; if resolved: edge-book friend request <name> --deliver`,
|
|
6649
|
+
` Joining a community? edge-book pack list \u2192 edge-book pack join <slug> --deliver (one command, whole circle)`,
|
|
6320
6650
|
` If not found: share your link (edge-book card invite \u2192 use the deeplink_url)`,
|
|
6321
6651
|
` If they have an "Add me" link from someone \u2192 edge-book friend request <link> --deliver`,
|
|
6322
6652
|
` (no leads? edge-book candidates list shows pending introductions)`,
|
|
@@ -6421,9 +6751,9 @@ ${id.agent_id}`, json: { handle: id.handle, agent_id: id.agent_id } };
|
|
|
6421
6751
|
const bundle = await store.exportIdentity();
|
|
6422
6752
|
const p = takeFlag(args, "--path");
|
|
6423
6753
|
if (p) {
|
|
6424
|
-
const target =
|
|
6425
|
-
await
|
|
6426
|
-
await
|
|
6754
|
+
const target = path12.resolve(p);
|
|
6755
|
+
await fs13.mkdir(path12.dirname(target), { recursive: true });
|
|
6756
|
+
await fs13.writeFile(target, `${JSON.stringify(bundle, null, 2)}
|
|
6427
6757
|
`, { encoding: "utf8", mode: 384 });
|
|
6428
6758
|
return { text: `Identity exported \u2192 ${target}`, json: { path: target } };
|
|
6429
6759
|
}
|
|
@@ -6432,7 +6762,7 @@ ${id.agent_id}`, json: { handle: id.handle, agent_id: id.agent_id } };
|
|
|
6432
6762
|
if (action === "import") {
|
|
6433
6763
|
const source = requireArg(args.shift(), "identity import <path>");
|
|
6434
6764
|
const force = takeBoolFlag(args, "--force");
|
|
6435
|
-
const bundle = JSON.parse(await
|
|
6765
|
+
const bundle = JSON.parse(await fs13.readFile(path12.resolve(source), "utf8"));
|
|
6436
6766
|
const id = await store.importIdentity(bundle, { force });
|
|
6437
6767
|
return { text: `Identity imported: ${id.handle} (${id.agent_id})`, json: { handle: id.handle, agent_id: id.agent_id } };
|
|
6438
6768
|
}
|
|
@@ -6556,10 +6886,10 @@ visibility: ${JSON.stringify(p.visibility ?? {})}`,
|
|
|
6556
6886
|
if (action === "export") {
|
|
6557
6887
|
const target = requireArg(takeFlag(args, "--path"), "--path");
|
|
6558
6888
|
const card = await store.writeCard();
|
|
6559
|
-
await
|
|
6560
|
-
await
|
|
6889
|
+
await fs13.mkdir(path12.dirname(path12.resolve(target)), { recursive: true });
|
|
6890
|
+
await fs13.writeFile(path12.resolve(target), `${JSON.stringify(card, null, 2)}
|
|
6561
6891
|
`, "utf8");
|
|
6562
|
-
return { text: `Exported Agent Card to ${
|
|
6892
|
+
return { text: `Exported Agent Card to ${path12.resolve(target)}`, json: card };
|
|
6563
6893
|
}
|
|
6564
6894
|
if (action === "invite") {
|
|
6565
6895
|
const ttlMsStr = takeFlag(args, "--ttl-ms");
|
|
@@ -6583,7 +6913,8 @@ visibility: ${JSON.stringify(p.visibility ?? {})}`,
|
|
|
6583
6913
|
}
|
|
6584
6914
|
|
|
6585
6915
|
// src/cli-social.ts
|
|
6586
|
-
import
|
|
6916
|
+
import { existsSync as existsSync2 } from "fs";
|
|
6917
|
+
import path13 from "path";
|
|
6587
6918
|
|
|
6588
6919
|
// src/store-greeter.ts
|
|
6589
6920
|
var GREETER_WELCOME_TITLE = "Welcome to Edge Book";
|
|
@@ -6634,7 +6965,46 @@ async function runGreeterPass(store) {
|
|
|
6634
6965
|
}
|
|
6635
6966
|
|
|
6636
6967
|
// src/cli-social.ts
|
|
6637
|
-
import
|
|
6968
|
+
import fs14 from "fs/promises";
|
|
6969
|
+
function isCardLocation(target) {
|
|
6970
|
+
if (/^https?:\/\//.test(target) || target.startsWith("edgebook:invite:")) return true;
|
|
6971
|
+
if (target.startsWith("file://")) return true;
|
|
6972
|
+
return existsSync2(path13.resolve(target));
|
|
6973
|
+
}
|
|
6974
|
+
var notResolvable = (target) => new EdgeBookError("target_not_resolvable", `could not resolve '${target}' \u2014 share your invite link instead (card invite)`);
|
|
6975
|
+
async function resolveFriendRequestCard(store, target, providers) {
|
|
6976
|
+
if (isCardLocation(target)) return loadCard(target);
|
|
6977
|
+
let result;
|
|
6978
|
+
try {
|
|
6979
|
+
result = await resolveTarget(store, target, { providers });
|
|
6980
|
+
} catch (error) {
|
|
6981
|
+
if (error?.code === "ENOENT") throw notResolvable(target);
|
|
6982
|
+
throw error;
|
|
6983
|
+
}
|
|
6984
|
+
if (result.status === "resolved") {
|
|
6985
|
+
if (result.card) return result.card;
|
|
6986
|
+
const cardUrl = result.agent_id ? (await store.contacts())[result.agent_id]?.card_url : void 0;
|
|
6987
|
+
if (cardUrl) {
|
|
6988
|
+
try {
|
|
6989
|
+
return await loadCard(cardUrl);
|
|
6990
|
+
} catch {
|
|
6991
|
+
const nonLocal = providers.filter((p) => p.name !== "local");
|
|
6992
|
+
const retry = await resolveTarget(store, target, { providers: nonLocal });
|
|
6993
|
+
if (retry.status === "resolved" && retry.card) return retry.card;
|
|
6994
|
+
throw notResolvable(target);
|
|
6995
|
+
}
|
|
6996
|
+
}
|
|
6997
|
+
throw notResolvable(target);
|
|
6998
|
+
}
|
|
6999
|
+
if (result.status === "approval_required" || result.status === "candidates") {
|
|
7000
|
+
const first = result.candidates?.[0];
|
|
7001
|
+
throw new EdgeBookError(
|
|
7002
|
+
"approval_required",
|
|
7003
|
+
`'${target}' matched an unverified candidate \u2014 run: candidates list then: friend request ${first?.candidate_id ?? "<candidate-id>"}`
|
|
7004
|
+
);
|
|
7005
|
+
}
|
|
7006
|
+
throw notResolvable(target);
|
|
7007
|
+
}
|
|
6638
7008
|
async function handleSocialCli(command, args, ctx, home, store) {
|
|
6639
7009
|
if (command === "resolve") {
|
|
6640
7010
|
const target = requireArg(args.shift(), "target");
|
|
@@ -6656,6 +7026,7 @@ next: ${result.next_action}`, json: result };
|
|
|
6656
7026
|
const action = args.shift();
|
|
6657
7027
|
if (action === "request") {
|
|
6658
7028
|
const deliver = takeBoolFlag(args, "--deliver");
|
|
7029
|
+
const hostUrl = parseHost(args, ctx);
|
|
6659
7030
|
const rawTarget = requireArg(args.shift(), "card-path-url-or-candidate");
|
|
6660
7031
|
let inviteCode = "";
|
|
6661
7032
|
let target = rawTarget;
|
|
@@ -6668,7 +7039,7 @@ next: ${result.next_action}`, json: result };
|
|
|
6668
7039
|
if (candidate && !candidate.card_url) {
|
|
6669
7040
|
throw new EdgeBookError("candidate_not_resolvable", "Candidate has no card_url to verify; cannot request");
|
|
6670
7041
|
}
|
|
6671
|
-
const card = candidate ? await loadCard(candidate.card_url) : await
|
|
7042
|
+
const card = candidate ? await loadCard(candidate.card_url) : await resolveFriendRequestCard(store, target, defaultProviders(relayBaseFromHost(hostUrl)));
|
|
6672
7043
|
const envelope = await store.createFriendRequest(card, "", inviteCode);
|
|
6673
7044
|
if (candidate) await markCandidateApproved(store, candidate.candidate_id, card.agent_id);
|
|
6674
7045
|
if (deliver) {
|
|
@@ -6679,7 +7050,6 @@ next: ${result.next_action}`, json: result };
|
|
|
6679
7050
|
await postRelayEnvelope(relay, card.agent_id, envelope);
|
|
6680
7051
|
return { text: `Queued friend_request via relay ${relay}`, json: envelope };
|
|
6681
7052
|
}
|
|
6682
|
-
const hostUrl = parseHost(args, ctx);
|
|
6683
7053
|
const outcome = await deliverViaMailboxRecorded(
|
|
6684
7054
|
envelope,
|
|
6685
7055
|
{ home, host: hostUrl, socketFactory: ctx.socketFactory },
|
|
@@ -6718,7 +7088,7 @@ next: ${result.next_action}`, json: result };
|
|
|
6718
7088
|
const deliver = takeBoolFlag(args, "--deliver");
|
|
6719
7089
|
const source = requireArg(args.shift(), "envelope-json-path");
|
|
6720
7090
|
const followUp = await store.applyFriendResponse(await readEnvelope(source));
|
|
6721
|
-
if (!followUp) return { text: `Applied friend response from ${
|
|
7091
|
+
if (!followUp) return { text: `Applied friend response from ${path13.resolve(source)}` };
|
|
6722
7092
|
if (deliver) {
|
|
6723
7093
|
try {
|
|
6724
7094
|
return { text: await deliverToPeer(store, followUp, followUp.to_agent_id), json: followUp };
|
|
@@ -6746,7 +7116,8 @@ next: ${result.next_action}`, json: result };
|
|
|
6746
7116
|
return { text: `Blocked ${peer}` };
|
|
6747
7117
|
}
|
|
6748
7118
|
if (action === "pending") {
|
|
6749
|
-
const
|
|
7119
|
+
const onlyNew = takeBoolFlag(args, "--new");
|
|
7120
|
+
const pending = onlyNew ? await store.unnotifiedFriendRequests() : await store.pendingFriendRequests();
|
|
6750
7121
|
const inbox = await store.inbox();
|
|
6751
7122
|
const json = pending.map((c) => {
|
|
6752
7123
|
const matchingEnvelopes = inbox.filter(
|
|
@@ -6760,7 +7131,9 @@ next: ${result.next_action}`, json: result };
|
|
|
6760
7131
|
display_name: c.display_name,
|
|
6761
7132
|
note,
|
|
6762
7133
|
requested_at,
|
|
6763
|
-
contact_created_at: c.created_at
|
|
7134
|
+
contact_created_at: c.created_at,
|
|
7135
|
+
// spec-139: lets callers distinguish new from already-surfaced requests.
|
|
7136
|
+
...c.notified_at ? { notified_at: c.notified_at } : {}
|
|
6764
7137
|
};
|
|
6765
7138
|
});
|
|
6766
7139
|
const text = json.length ? json.map((p) => `${p.agent_id} ${p.display_name}`).join("\n") : "No pending friend requests.";
|
|
@@ -6821,8 +7194,8 @@ next: ${result.next_action}`, json: result };
|
|
|
6821
7194
|
const file = takeFlag(args, "--file");
|
|
6822
7195
|
let attachment;
|
|
6823
7196
|
if (file) {
|
|
6824
|
-
const bytes = await
|
|
6825
|
-
attachment = { filename:
|
|
7197
|
+
const bytes = await fs14.readFile(path13.resolve(file));
|
|
7198
|
+
attachment = { filename: path13.basename(file), mime: takeFlag(args, "--mime") || "application/octet-stream", bytes };
|
|
6826
7199
|
}
|
|
6827
7200
|
const object = await store.createObject({ title, body, attachment });
|
|
6828
7201
|
return { text: `Created object ${object.object_id}`, json: object };
|
|
@@ -6862,7 +7235,7 @@ next: ${result.next_action}`, json: result };
|
|
|
6862
7235
|
if (action === "receive") {
|
|
6863
7236
|
const source = requireArg(args.shift(), "envelope-json-path");
|
|
6864
7237
|
await store.receiveEnvelope(await readEnvelope(source));
|
|
6865
|
-
return { text: `Applied object envelope from ${
|
|
7238
|
+
return { text: `Applied object envelope from ${path13.resolve(source)}` };
|
|
6866
7239
|
}
|
|
6867
7240
|
if (action === "list") {
|
|
6868
7241
|
const objects2 = await store.sharedObjectsFor();
|
|
@@ -6901,7 +7274,7 @@ next: ${result.next_action}`, json: result };
|
|
|
6901
7274
|
if (action === "receive") {
|
|
6902
7275
|
const source = requireArg(args.shift(), "envelope-json-path");
|
|
6903
7276
|
await store.receivePrivilegedMessage(await readEnvelope(source));
|
|
6904
|
-
return { text: `Received privileged message from ${
|
|
7277
|
+
return { text: `Received privileged message from ${path13.resolve(source)}` };
|
|
6905
7278
|
}
|
|
6906
7279
|
}
|
|
6907
7280
|
if (command === "escalation") {
|
|
@@ -6982,6 +7355,185 @@ next: ${result.next_action}`, json: result };
|
|
|
6982
7355
|
return null;
|
|
6983
7356
|
}
|
|
6984
7357
|
|
|
7358
|
+
// src/cli-pack.ts
|
|
7359
|
+
import fs15 from "fs/promises";
|
|
7360
|
+
import path14 from "path";
|
|
7361
|
+
var PACK_JOIN_REQUEST_DELAY_MS = 250;
|
|
7362
|
+
var SKIP_REASONS = {
|
|
7363
|
+
friend: "already a friend",
|
|
7364
|
+
request_sent: "request already sent",
|
|
7365
|
+
blocked: "blocked",
|
|
7366
|
+
request_received: "they already asked you \u2014 run `friend pending` to accept"
|
|
7367
|
+
};
|
|
7368
|
+
function sleep(ms) {
|
|
7369
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
7370
|
+
}
|
|
7371
|
+
function packRelayBase(args, ctx, hostUrl) {
|
|
7372
|
+
return (takeFlag(args, "--relay-base") || process.env.EDGE_BOOK_RELAY_BASE || relayBaseFromHost(hostUrl)).replace(/\/$/, "");
|
|
7373
|
+
}
|
|
7374
|
+
async function fetchPackJson(url, agentKey) {
|
|
7375
|
+
let response;
|
|
7376
|
+
try {
|
|
7377
|
+
response = await fetch(url, agentKey ? { headers: { authorization: `Bearer ${agentKey}` } } : void 0);
|
|
7378
|
+
} catch (error) {
|
|
7379
|
+
throw new EdgeBookError("host_unreachable", `Could not reach the pack registry at ${url}: ${error instanceof Error ? error.message : String(error)}`);
|
|
7380
|
+
}
|
|
7381
|
+
let body = null;
|
|
7382
|
+
try {
|
|
7383
|
+
body = await response.json();
|
|
7384
|
+
} catch {
|
|
7385
|
+
}
|
|
7386
|
+
return { status: response.status, body };
|
|
7387
|
+
}
|
|
7388
|
+
var PACK_CACHE_FILE = "packs-cache.json";
|
|
7389
|
+
async function readPackCache(store) {
|
|
7390
|
+
try {
|
|
7391
|
+
return JSON.parse(await fs15.readFile(path14.join(store.home, PACK_CACHE_FILE), "utf8"));
|
|
7392
|
+
} catch {
|
|
7393
|
+
return {};
|
|
7394
|
+
}
|
|
7395
|
+
}
|
|
7396
|
+
async function cachePack(store, key, pack) {
|
|
7397
|
+
try {
|
|
7398
|
+
const cache = await readPackCache(store);
|
|
7399
|
+
cache[key] = { fetched_at: Date.now(), pack };
|
|
7400
|
+
await fs15.writeFile(path14.join(store.home, PACK_CACHE_FILE), JSON.stringify(cache));
|
|
7401
|
+
} catch {
|
|
7402
|
+
}
|
|
7403
|
+
}
|
|
7404
|
+
async function fetchPack(base, slug, store) {
|
|
7405
|
+
const key = await loadOrCreateDialoutKey(store);
|
|
7406
|
+
const cacheKey = `${base}/${slug}`;
|
|
7407
|
+
const { status, body } = await fetchPackJson(`${base}/pack/${encodeURIComponent(slug)}`, key.agent_key);
|
|
7408
|
+
if (status === 200) {
|
|
7409
|
+
const pack = body;
|
|
7410
|
+
await cachePack(store, cacheKey, pack);
|
|
7411
|
+
return pack;
|
|
7412
|
+
}
|
|
7413
|
+
if (status === 404) throw new EdgeBookError("pack_not_found", `No pack '${slug}' \u2014 run: pack list`);
|
|
7414
|
+
if (status === 429) {
|
|
7415
|
+
const cached = (await readPackCache(store))[cacheKey];
|
|
7416
|
+
if (cached) return cached.pack;
|
|
7417
|
+
const retryMs = body?.retry_after_ms ?? 0;
|
|
7418
|
+
const mins = Math.max(1, Math.ceil(retryMs / 6e4));
|
|
7419
|
+
throw new EdgeBookError("pack_rate_limited", `Pack '${slug}' was fetched recently \u2014 try again in ~${mins} min`);
|
|
7420
|
+
}
|
|
7421
|
+
if (status === 401 || status === 403) {
|
|
7422
|
+
throw new EdgeBookError("pack_unauthorized", "The host does not know this agent yet \u2014 run `edge-book dialout` once so it can introduce itself, then retry");
|
|
7423
|
+
}
|
|
7424
|
+
throw new EdgeBookError("pack_fetch_failed", `Pack fetch failed (HTTP ${status})`);
|
|
7425
|
+
}
|
|
7426
|
+
async function handlePackList(args, ctx) {
|
|
7427
|
+
const base = packRelayBase(args, ctx, parseHost(args, ctx));
|
|
7428
|
+
const { status, body } = await fetchPackJson(`${base}/packs`);
|
|
7429
|
+
if (status !== 200 || !Array.isArray(body)) throw new EdgeBookError("pack_fetch_failed", `Pack listing failed (HTTP ${status})`);
|
|
7430
|
+
const packs = body;
|
|
7431
|
+
const text = packs.length ? packs.map((p) => `${p.slug} ${p.title} (${p.member_count} member${p.member_count === 1 ? "" : "s"})${p.description ? ` \u2014 ${p.description}` : ""}`).join("\n") : "No starter packs published on this host.";
|
|
7432
|
+
return { text, json: { packs } };
|
|
7433
|
+
}
|
|
7434
|
+
async function handlePackShow(args, ctx, store) {
|
|
7435
|
+
const hostUrl = parseHost(args, ctx);
|
|
7436
|
+
const base = packRelayBase(args, ctx, hostUrl);
|
|
7437
|
+
const slug = requireArg(args.shift(), "pack-slug");
|
|
7438
|
+
const pack = await fetchPack(base, slug, store);
|
|
7439
|
+
const identity = await store.identity();
|
|
7440
|
+
const providers = defaultProviders(base);
|
|
7441
|
+
const members = [];
|
|
7442
|
+
for (const handle of pack.member_handles) {
|
|
7443
|
+
if (handle === identity.handle) {
|
|
7444
|
+
members.push({ handle, resolution: "self" });
|
|
7445
|
+
continue;
|
|
7446
|
+
}
|
|
7447
|
+
try {
|
|
7448
|
+
const result = await resolveTarget(store, handle, { providers });
|
|
7449
|
+
const relationship = result.agent_id ? (await store.contacts())[result.agent_id]?.relationship_state : void 0;
|
|
7450
|
+
members.push({ handle, resolution: result.status, ...relationship ? { relationship } : {} });
|
|
7451
|
+
} catch {
|
|
7452
|
+
members.push({ handle, resolution: "not_found" });
|
|
7453
|
+
}
|
|
7454
|
+
}
|
|
7455
|
+
const lines = members.map((m) => `${m.handle} ${m.resolution}${m.relationship ? ` [${m.relationship}]` : ""}`);
|
|
7456
|
+
const text = `${pack.title} (${pack.member_handles.length} members)${pack.description ? `
|
|
7457
|
+
${pack.description}` : ""}
|
|
7458
|
+
${lines.join("\n")}`;
|
|
7459
|
+
return { text, json: { ...pack, members } };
|
|
7460
|
+
}
|
|
7461
|
+
async function deliverPackRequest(envelope, card, opts) {
|
|
7462
|
+
const direct = card.transports.find((entry) => entry.mode === "direct")?.endpoint;
|
|
7463
|
+
if (direct) {
|
|
7464
|
+
await deliverToEndpoint(envelope, direct);
|
|
7465
|
+
await recordOutboxEntry(opts.home, { id: envelope.message_id, to_agent_id: envelope.to_agent_id, envelope_type: envelope.type });
|
|
7466
|
+
return;
|
|
7467
|
+
}
|
|
7468
|
+
const relay = card.transports.find((entry) => entry.mode === "relay")?.endpoint;
|
|
7469
|
+
if (relay) {
|
|
7470
|
+
await postRelayEnvelope(relay, card.agent_id, envelope);
|
|
7471
|
+
await recordOutboxEntry(opts.home, { id: envelope.message_id, to_agent_id: envelope.to_agent_id, envelope_type: envelope.type });
|
|
7472
|
+
return;
|
|
7473
|
+
}
|
|
7474
|
+
await deliverViaMailboxRecorded(envelope, opts, (id) => `Delivered friend_request (host id ${id})`);
|
|
7475
|
+
}
|
|
7476
|
+
async function joinMember(handle, deliver, paceBefore, store, providers, deliverOpts) {
|
|
7477
|
+
const identity = await store.identity();
|
|
7478
|
+
if (handle === identity.handle) return { handle, outcome: "skipped", reason: "self" };
|
|
7479
|
+
const normalized = handle.trim().replace(/^@/, "").toLowerCase();
|
|
7480
|
+
const known = Object.values(await store.contacts()).find((c) => (c.aliases ?? []).some((a) => a.toLowerCase() === normalized));
|
|
7481
|
+
const knownSkip = known?.relationship_state ? SKIP_REASONS[known.relationship_state] : void 0;
|
|
7482
|
+
if (knownSkip) return { handle, outcome: "skipped", reason: knownSkip };
|
|
7483
|
+
const card = await resolveFriendRequestCard(store, handle, providers);
|
|
7484
|
+
if (card.agent_id === identity.agent_id) return { handle, outcome: "skipped", reason: "self" };
|
|
7485
|
+
const state = (await store.contacts())[card.agent_id]?.relationship_state;
|
|
7486
|
+
const skipReason = state ? SKIP_REASONS[state] : void 0;
|
|
7487
|
+
if (skipReason) return { handle, outcome: "skipped", reason: skipReason };
|
|
7488
|
+
if (paceBefore) await sleep(PACK_JOIN_REQUEST_DELAY_MS);
|
|
7489
|
+
const envelope = await store.createFriendRequest(card);
|
|
7490
|
+
if (deliver) {
|
|
7491
|
+
await deliverPackRequest(envelope, card, deliverOpts);
|
|
7492
|
+
return { handle, outcome: "requested" };
|
|
7493
|
+
}
|
|
7494
|
+
return { handle, outcome: "requested", envelope };
|
|
7495
|
+
}
|
|
7496
|
+
async function handlePackJoin(args, ctx, home, store) {
|
|
7497
|
+
const deliver = takeBoolFlag(args, "--deliver");
|
|
7498
|
+
const hostUrl = parseHost(args, ctx);
|
|
7499
|
+
const base = packRelayBase(args, ctx, hostUrl);
|
|
7500
|
+
const slug = requireArg(args.shift(), "pack-slug");
|
|
7501
|
+
const pack = await fetchPack(base, slug, store);
|
|
7502
|
+
const providers = defaultProviders(base);
|
|
7503
|
+
const deliverOpts = { home, host: hostUrl, socketFactory: ctx.socketFactory };
|
|
7504
|
+
const outcomes = [];
|
|
7505
|
+
let sentAny = false;
|
|
7506
|
+
for (const handle of pack.member_handles) {
|
|
7507
|
+
try {
|
|
7508
|
+
const outcome = await joinMember(handle, deliver, sentAny, store, providers, deliverOpts);
|
|
7509
|
+
sentAny = sentAny || outcome.outcome === "requested";
|
|
7510
|
+
outcomes.push(outcome);
|
|
7511
|
+
} catch (error) {
|
|
7512
|
+
outcomes.push({ handle, outcome: "failed", reason: error instanceof Error ? error.message : String(error) });
|
|
7513
|
+
}
|
|
7514
|
+
}
|
|
7515
|
+
const requested = outcomes.filter((o) => o.outcome === "requested").length;
|
|
7516
|
+
const skipped = outcomes.filter((o) => o.outcome === "skipped").length;
|
|
7517
|
+
const failed = outcomes.filter((o) => o.outcome === "failed").length;
|
|
7518
|
+
const lines = outcomes.map((o) => `${o.handle} ${o.outcome}${o.reason ? ` (${o.reason})` : ""}`);
|
|
7519
|
+
const summary = `requested ${requested}, skipped ${skipped}, failed ${failed}` + (!deliver && requested ? " \u2014 envelopes in --json output (no --deliver: nothing was sent; transport them manually or re-run with --deliver)" : "");
|
|
7520
|
+
const exitCode = failed === 0 ? 0 : requested > 0 ? 1 : 2;
|
|
7521
|
+
return {
|
|
7522
|
+
text: `${lines.join("\n")}
|
|
7523
|
+
${summary}`,
|
|
7524
|
+
json: { slug, requested, skipped, failed, members: outcomes },
|
|
7525
|
+
...exitCode ? { exitCode } : {}
|
|
7526
|
+
};
|
|
7527
|
+
}
|
|
7528
|
+
async function handlePackCli(command, args, ctx, home, store) {
|
|
7529
|
+
if (command !== "pack") return null;
|
|
7530
|
+
const action = args.shift();
|
|
7531
|
+
if (action === "list") return handlePackList(args, ctx);
|
|
7532
|
+
if (action === "show") return handlePackShow(args, ctx, store);
|
|
7533
|
+
if (action === "join") return handlePackJoin(args, ctx, home, store);
|
|
7534
|
+
throw new EdgeBookError("unknown_action", `Unknown pack action: ${action} (try: pack list | pack show <slug> | pack join <slug> [--deliver])`);
|
|
7535
|
+
}
|
|
7536
|
+
|
|
6985
7537
|
// src/cli-support.ts
|
|
6986
7538
|
function bundleLine(b) {
|
|
6987
7539
|
return `${b.bundle_id} ${b.received_at} ${b.from_display_name ?? "(no name)"} (${b.from_agent_id}) ref=${b.trace_id ?? "-"}`;
|
|
@@ -7033,6 +7585,26 @@ async function handleSupportCli(command, args, _ctx, _home, store) {
|
|
|
7033
7585
|
throw new EdgeBookError("unknown_action", `Unknown support action: ${action} (use "inbox", "pending", "read", "dismiss", "list", or "receive")`);
|
|
7034
7586
|
}
|
|
7035
7587
|
|
|
7588
|
+
// src/store-received-surface.ts
|
|
7589
|
+
async function activeReceivedEphemeral(store) {
|
|
7590
|
+
const { ephemeral } = await store.receivedByCategory();
|
|
7591
|
+
const out = {};
|
|
7592
|
+
for (const [key, post] of Object.entries(ephemeral)) {
|
|
7593
|
+
if (post.lifecycle === "active" && Date.parse(post.expires_at) > Date.now()) out[key] = post;
|
|
7594
|
+
}
|
|
7595
|
+
return out;
|
|
7596
|
+
}
|
|
7597
|
+
async function resolveReceivedQuery(store, queryId) {
|
|
7598
|
+
const { ephemeral } = await store.receivedByCategory();
|
|
7599
|
+
const matches = Object.values(ephemeral).filter((p) => p.post_id === queryId);
|
|
7600
|
+
if (matches.length === 0) return null;
|
|
7601
|
+
if (new Set(matches.map((p) => p.from_agent)).size > 1) {
|
|
7602
|
+
throw new EdgeBookError("ambiguous_query", `Query ${queryId} was received from multiple senders \u2014 cannot resolve which to answer`);
|
|
7603
|
+
}
|
|
7604
|
+
const post = matches[0];
|
|
7605
|
+
return { post, author: post.from_agent };
|
|
7606
|
+
}
|
|
7607
|
+
|
|
7036
7608
|
// src/cli-taxonomy.ts
|
|
7037
7609
|
async function handleTaxonomyCli(command, args, ctx, store) {
|
|
7038
7610
|
if (command === "attest") {
|
|
@@ -7113,16 +7685,30 @@ async function handleTaxonomyCli(command, args, ctx, store) {
|
|
|
7113
7685
|
const deliver = takeBoolFlag(args, "--deliver");
|
|
7114
7686
|
const hostUrl = parseHost(args, ctx);
|
|
7115
7687
|
const queryId = requireArg(args.shift(), "<query-id>");
|
|
7116
|
-
|
|
7117
|
-
|
|
7118
|
-
if (!query)
|
|
7688
|
+
let query = (await store.ephemeralPosts())[queryId];
|
|
7689
|
+
let receivedAuthor;
|
|
7690
|
+
if (!query) {
|
|
7691
|
+
const received = await resolveReceivedQuery(store, queryId);
|
|
7692
|
+
if (!received) throw new EdgeBookError("not_found", `No local or received query ${queryId} to answer`);
|
|
7693
|
+
query = received.post;
|
|
7694
|
+
receivedAuthor = received.author;
|
|
7695
|
+
}
|
|
7119
7696
|
const { signature: _sig, lifecycle: _lc, ...queryUnsigned } = query;
|
|
7120
7697
|
const ans = await store.createAnswer({
|
|
7121
7698
|
parent: { uri: "edgebook:query:" + queryId, hash: contentHash(queryUnsigned) },
|
|
7122
7699
|
body: requireArg(takeFlag(args, "--body"), "--body")
|
|
7123
7700
|
});
|
|
7124
7701
|
if (deliver) {
|
|
7125
|
-
|
|
7702
|
+
let n = await broadcastPost(store, hostUrl, ctx.socketFactory, ans);
|
|
7703
|
+
if (receivedAuthor && (await store.contacts())[receivedAuthor]?.relationship_state !== "friend") {
|
|
7704
|
+
const envelope = await store.signPostPublishEnvelope({ to_agent_id: receivedAuthor, post: ans });
|
|
7705
|
+
await deliverViaMailboxRecorded(
|
|
7706
|
+
envelope,
|
|
7707
|
+
{ home: store.home, host: hostUrl, socketFactory: ctx.socketFactory },
|
|
7708
|
+
(id) => `Delivered post_publish (host id ${id})`
|
|
7709
|
+
);
|
|
7710
|
+
n++;
|
|
7711
|
+
}
|
|
7126
7712
|
return { text: `answer ${ans.answer_id} \u2014 delivered to ${n} friend(s)`, json: { post: ans, delivered: n } };
|
|
7127
7713
|
}
|
|
7128
7714
|
return { text: `answer ${ans.answer_id}`, json: ans };
|
|
@@ -7133,12 +7719,12 @@ async function handleTaxonomyCli(command, args, ctx, store) {
|
|
|
7133
7719
|
return { text: `Tombstoned query ${queryId} and its answers`, json: { query_id: queryId } };
|
|
7134
7720
|
}
|
|
7135
7721
|
if (command === "ephemeral") {
|
|
7136
|
-
const
|
|
7137
|
-
return { text: JSON.stringify(
|
|
7722
|
+
const out = { mine: await store.ephemeralPosts(), received: await activeReceivedEphemeral(store) };
|
|
7723
|
+
return { text: JSON.stringify(out, null, 2), json: out };
|
|
7138
7724
|
}
|
|
7139
7725
|
if (command === "answers") {
|
|
7140
|
-
const
|
|
7141
|
-
return { text: JSON.stringify(
|
|
7726
|
+
const out = { mine: await store.answers(), received: (await store.receivedByCategory()).answers };
|
|
7727
|
+
return { text: JSON.stringify(out, null, 2), json: out };
|
|
7142
7728
|
}
|
|
7143
7729
|
if (command === "report") {
|
|
7144
7730
|
const peer = requireArg(args.shift(), "peer-agent-id");
|
|
@@ -7277,8 +7863,16 @@ var COMMAND_GROUPS = [
|
|
|
7277
7863
|
desc: "Connect to the host mailbox (keeps your reader online; leave running)"
|
|
7278
7864
|
},
|
|
7279
7865
|
{
|
|
7280
|
-
usage: "ensure-notifier [--no-cron-install]",
|
|
7281
|
-
desc: "Provision the host friend-request notifier (auto-runs on dialout;
|
|
7866
|
+
usage: "ensure-notifier [--no-cron-install] [--print-prompt] [--ack]",
|
|
7867
|
+
desc: "Provision the host friend-request notifier (auto-runs on dialout; --print-prompt/--ack drive agent-side scheduler migration)"
|
|
7868
|
+
},
|
|
7869
|
+
{
|
|
7870
|
+
usage: "self-update [--if-stale] [--dry-run]",
|
|
7871
|
+
desc: "Update this edge-book install to the latest npm release; --if-stale is the cron-safe form (silent no-op when current)"
|
|
7872
|
+
},
|
|
7873
|
+
{
|
|
7874
|
+
usage: "version",
|
|
7875
|
+
desc: "Print the running edge-book version"
|
|
7282
7876
|
},
|
|
7283
7877
|
{
|
|
7284
7878
|
usage: "pair [--host <wss-url>] [--ttl-ms <ms>]",
|
|
@@ -7339,8 +7933,8 @@ var COMMAND_GROUPS = [
|
|
|
7339
7933
|
desc: "Block a peer (ends relationship + prevents re-request)"
|
|
7340
7934
|
},
|
|
7341
7935
|
{
|
|
7342
|
-
usage: "friend pending [--json]",
|
|
7343
|
-
desc: "List inbound friend requests awaiting your decision"
|
|
7936
|
+
usage: "friend pending [--new] [--json]",
|
|
7937
|
+
desc: "List inbound friend requests awaiting your decision (--new: only ones not yet surfaced to the human)"
|
|
7344
7938
|
},
|
|
7345
7939
|
{
|
|
7346
7940
|
usage: "friend mark-notified <peer-agent-id>",
|
|
@@ -7360,6 +7954,23 @@ var COMMAND_GROUPS = [
|
|
|
7360
7954
|
}
|
|
7361
7955
|
]
|
|
7362
7956
|
},
|
|
7957
|
+
{
|
|
7958
|
+
title: "Starter packs",
|
|
7959
|
+
rows: [
|
|
7960
|
+
{
|
|
7961
|
+
usage: "pack list [--relay-base <url>]",
|
|
7962
|
+
desc: "List curated starter packs on the host (public: title + member count only)"
|
|
7963
|
+
},
|
|
7964
|
+
{
|
|
7965
|
+
usage: "pack show <slug> [--relay-base <url>]",
|
|
7966
|
+
desc: "Show a pack's members with per-handle resolution state (sends nothing)"
|
|
7967
|
+
},
|
|
7968
|
+
{
|
|
7969
|
+
usage: "pack join <slug> [--deliver] [--relay-base <url>]",
|
|
7970
|
+
desc: "Send a friend request to every pack member (skips self and existing relationships; exit 1 partial / 2 total failure)"
|
|
7971
|
+
}
|
|
7972
|
+
]
|
|
7973
|
+
},
|
|
7363
7974
|
{
|
|
7364
7975
|
title: "Greeter",
|
|
7365
7976
|
rows: [
|
|
@@ -7558,7 +8169,7 @@ var COMMAND_GROUPS = [
|
|
|
7558
8169
|
},
|
|
7559
8170
|
{
|
|
7560
8171
|
usage: "answer <query-id> --body <s> [--deliver]",
|
|
7561
|
-
desc: "Answer an open query"
|
|
8172
|
+
desc: "Answer an open query (local or received from a friend)"
|
|
7562
8173
|
},
|
|
7563
8174
|
{
|
|
7564
8175
|
usage: "query-delete <query-id>",
|
|
@@ -7566,11 +8177,11 @@ var COMMAND_GROUPS = [
|
|
|
7566
8177
|
},
|
|
7567
8178
|
{
|
|
7568
8179
|
usage: "ephemeral",
|
|
7569
|
-
desc: "List Class-2 ephemeral posts"
|
|
8180
|
+
desc: "List Class-2 ephemeral posts (mine + received from friends)"
|
|
7570
8181
|
},
|
|
7571
8182
|
{
|
|
7572
8183
|
usage: "answers",
|
|
7573
|
-
desc: "List answers to queries"
|
|
8184
|
+
desc: "List answers to queries (mine + received from friends)"
|
|
7574
8185
|
}
|
|
7575
8186
|
]
|
|
7576
8187
|
},
|
|
@@ -7696,6 +8307,36 @@ function makeNotifyOnEnvelope(store, cmd) {
|
|
|
7696
8307
|
};
|
|
7697
8308
|
}
|
|
7698
8309
|
|
|
8310
|
+
// src/update-nudge.ts
|
|
8311
|
+
var UPDATE_NUDGE_THROTTLE_MS = 24 * 60 * 60 * 1e3;
|
|
8312
|
+
function buildUpdateNudgeText(home, running, latest) {
|
|
8313
|
+
return [
|
|
8314
|
+
`---`,
|
|
8315
|
+
`Edge Book update available: this install runs ${running}; the latest release is ${latest}.`,
|
|
8316
|
+
`Run: edge-book self-update --home ${home}`,
|
|
8317
|
+
`If a dial-out process is running unsupervised, restart it after the update so it picks up the new code.`
|
|
8318
|
+
].join("\n");
|
|
8319
|
+
}
|
|
8320
|
+
async function maybeAppendUpdateNudge(store, command, result, now4 = Date.now(), running) {
|
|
8321
|
+
if (!NUDGE_COMMANDS.has(command)) return result;
|
|
8322
|
+
try {
|
|
8323
|
+
await store.identity();
|
|
8324
|
+
const config = await store.config();
|
|
8325
|
+
const mode = config.auto_update ?? "auto";
|
|
8326
|
+
if (mode === "off") return result;
|
|
8327
|
+
const latest = config.update_latest_known;
|
|
8328
|
+
const current = running ?? await runningVersion();
|
|
8329
|
+
if (!latest || compareVersions(latest, current) <= 0) return result;
|
|
8330
|
+
if (mode === "auto" && autoUpdateAllowed(current, latest)) return result;
|
|
8331
|
+
if (config.update_nudge_at !== void 0 && now4 - config.update_nudge_at < UPDATE_NUDGE_THROTTLE_MS) return result;
|
|
8332
|
+
await store.updateConfig({ update_nudge_at: now4 });
|
|
8333
|
+
return { ...result, text: `${result.text}
|
|
8334
|
+
${buildUpdateNudgeText(store.home, current, latest)}` };
|
|
8335
|
+
} catch {
|
|
8336
|
+
return result;
|
|
8337
|
+
}
|
|
8338
|
+
}
|
|
8339
|
+
|
|
7699
8340
|
// src/cli.ts
|
|
7700
8341
|
function usage() {
|
|
7701
8342
|
return renderUsage();
|
|
@@ -7713,14 +8354,20 @@ async function handleCli(inputArgs, ctx = {}) {
|
|
|
7713
8354
|
if (command === "help" || command === "--help" || command === "-h") {
|
|
7714
8355
|
return { text: usage() };
|
|
7715
8356
|
}
|
|
8357
|
+
if (command === "version" || command === "--version" || command === "-v") {
|
|
8358
|
+
const version = await runningVersion();
|
|
8359
|
+
return { text: version, json: { version } };
|
|
8360
|
+
}
|
|
7716
8361
|
const identityResult = await handleIdentityCli(command, args, ctx, home, store);
|
|
7717
8362
|
if (identityResult) return identityResult;
|
|
7718
8363
|
const socialAction = args[0];
|
|
7719
8364
|
const socialResult = await handleSocialCli(command, args, ctx, home, store);
|
|
7720
8365
|
if (socialResult) {
|
|
7721
8366
|
if (command === "friend" && socialAction === "auto-accept") return socialResult;
|
|
7722
|
-
return maybeAppendOnboardingNudge(store, command, await maybeAppendHandleNudge(store, command, socialResult));
|
|
8367
|
+
return maybeAppendUpdateNudge(store, command, await maybeAppendNotifierNudge(store, command, await maybeAppendOnboardingNudge(store, command, await maybeAppendHandleNudge(store, command, socialResult))));
|
|
7723
8368
|
}
|
|
8369
|
+
const packResult = await handlePackCli(command, args, ctx, home, store);
|
|
8370
|
+
if (packResult) return packResult;
|
|
7724
8371
|
const supportResult = await handleSupportCli(command, args, ctx, home, store);
|
|
7725
8372
|
if (supportResult) return supportResult;
|
|
7726
8373
|
if (command === "serve") {
|
|
@@ -7749,10 +8396,13 @@ async function handleCli(inputArgs, ctx = {}) {
|
|
|
7749
8396
|
console.log(`Edge Book dial-out connected to ${hostUrl}${notifyCmd ? " (notify hook active)" : ""}`);
|
|
7750
8397
|
const disabled = takeBoolFlag(args, "--no-cron-install") || process.env.EDGE_BOOK_NO_CRON_INSTALL === "1";
|
|
7751
8398
|
try {
|
|
7752
|
-
const res = ensureNotifierCron({ runner: defaultHermesRunner(), home, disabled });
|
|
8399
|
+
const res = ensureNotifierCron({ runner: ctx.hermesRunner ?? defaultHermesRunner(), home, disabled });
|
|
7753
8400
|
if (res.status === "installed") await logEvent(store2, "cron.notifier_installed", {});
|
|
8401
|
+
else if (res.status === "updated") await logEvent(store2, "cron.notifier_updated", {});
|
|
7754
8402
|
else if (res.status === "already_present") await logEvent(store2, "cron.notifier_already_present", {});
|
|
8403
|
+
if (res.status === "installed" || res.status === "updated") await store2.updateConfig({ notifier_prompt_ack: NOTIFIER_PROMPT_VERSION });
|
|
7755
8404
|
if (res.status === "installed") console.log(` \u21B3 notifier cron self-installed ("Edge Book \u2014 friend requests", every 20m \u2192 telegram)`);
|
|
8405
|
+
else if (res.status === "updated") console.log(` \u21B3 notifier cron recreated with the current prompt ("Edge Book \u2014 friend requests")`);
|
|
7756
8406
|
else if (res.status === "error") console.log(` \u21B3 notifier cron install skipped: ${res.detail}`);
|
|
7757
8407
|
} catch (e) {
|
|
7758
8408
|
console.log(` \u21B3 notifier cron install skipped: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -7769,12 +8419,22 @@ async function handleCli(inputArgs, ctx = {}) {
|
|
|
7769
8419
|
await new Promise(() => void 0);
|
|
7770
8420
|
}
|
|
7771
8421
|
if (command === "ensure-notifier") {
|
|
8422
|
+
if (takeBoolFlag(args, "--print-prompt")) {
|
|
8423
|
+
return { text: buildFriendRequestsPrompt(home) };
|
|
8424
|
+
}
|
|
8425
|
+
if (takeBoolFlag(args, "--ack")) {
|
|
8426
|
+
const cfg = await store.updateConfig({ notifier_prompt_ack: NOTIFIER_PROMPT_VERSION });
|
|
8427
|
+
return { text: `notifier_prompt_ack = ${cfg.notifier_prompt_ack}`, json: cfg };
|
|
8428
|
+
}
|
|
7772
8429
|
const disabled = takeBoolFlag(args, "--no-cron-install") || process.env.EDGE_BOOK_NO_CRON_INSTALL === "1";
|
|
7773
|
-
const res = ensureNotifierCron({ runner: defaultHermesRunner(), home, disabled });
|
|
8430
|
+
const res = ensureNotifierCron({ runner: ctx.hermesRunner ?? defaultHermesRunner(), home, disabled });
|
|
7774
8431
|
if (res.status === "installed") await logEvent(store, "cron.notifier_installed", {});
|
|
8432
|
+
else if (res.status === "updated") await logEvent(store, "cron.notifier_updated", {});
|
|
7775
8433
|
else if (res.status === "already_present") await logEvent(store, "cron.notifier_already_present", {});
|
|
8434
|
+
if (res.status === "installed" || res.status === "updated") await store.updateConfig({ notifier_prompt_ack: NOTIFIER_PROMPT_VERSION });
|
|
7776
8435
|
const msg = {
|
|
7777
8436
|
installed: 'Installed notifier cron "Edge Book \u2014 friend requests" (every 20m \u2192 telegram).',
|
|
8437
|
+
updated: 'Notifier cron prompt was stale \u2014 recreated "Edge Book \u2014 friend requests" with the current prompt.',
|
|
7778
8438
|
already_present: "Notifier cron already present \u2014 nothing to do.",
|
|
7779
8439
|
host_unsupported: "No recognized host (Hermes) detected \u2014 nothing installed. Set notify_cmd for real-time delivery on hosts with a sender.",
|
|
7780
8440
|
disabled: "Cron self-install disabled.",
|
|
@@ -7782,6 +8442,12 @@ async function handleCli(inputArgs, ctx = {}) {
|
|
|
7782
8442
|
};
|
|
7783
8443
|
return { text: msg[res.status] ?? res.status, json: res };
|
|
7784
8444
|
}
|
|
8445
|
+
if (command === "self-update") {
|
|
8446
|
+
const ifStale = takeBoolFlag(args, "--if-stale");
|
|
8447
|
+
const dryRun = takeBoolFlag(args, "--dry-run");
|
|
8448
|
+
const out = await selfUpdate(store, { ...ctx.selfUpdateDeps ?? {}, ifStale, dryRun });
|
|
8449
|
+
return { text: out.text, json: out };
|
|
8450
|
+
}
|
|
7785
8451
|
if (command === "greeter") {
|
|
7786
8452
|
const on = takeBoolFlag(args, "--on");
|
|
7787
8453
|
const off = takeBoolFlag(args, "--off");
|
|
@@ -7792,15 +8458,16 @@ async function handleCli(inputArgs, ctx = {}) {
|
|
|
7792
8458
|
}
|
|
7793
8459
|
if (command === "pair") {
|
|
7794
8460
|
const hostUrl = parseHost(args, ctx);
|
|
7795
|
-
const ttlMs = Number(takeFlag(args, "--ttl-ms") || `${
|
|
8461
|
+
const ttlMs = Number(takeFlag(args, "--ttl-ms") || `${DEFAULT_PAIR_TTL_MS}`);
|
|
7796
8462
|
if (!ctx.textOnly) {
|
|
7797
8463
|
const client = new EdgeBookDialoutClient({ home, host: hostUrl, socketFactory: ctx.socketFactory, openLocalApi: false });
|
|
7798
8464
|
await client.start();
|
|
7799
8465
|
const registration2 = await client.pair(ttlMs);
|
|
7800
8466
|
console.log(`Pairing code: ${registration2.code}`);
|
|
7801
|
-
console.log(
|
|
8467
|
+
console.log(formatPairExpiry(registration2, ttlMs));
|
|
7802
8468
|
console.log("Waiting for your browser reader to connect...");
|
|
7803
|
-
const
|
|
8469
|
+
const waitMs = registration2.expires_at ? Math.max(registration2.expires_at - Date.now(), 1e3) : ttlMs;
|
|
8470
|
+
const pairResult = await client.waitForPairComplete(waitMs);
|
|
7804
8471
|
await client.stop();
|
|
7805
8472
|
if (!pairResult) {
|
|
7806
8473
|
throw new EdgeBookError("pair_timeout", "Pairing code expired unredeemed \u2014 run edge-book pair again for a fresh code.");
|
|
@@ -7818,7 +8485,7 @@ Pairing complete \u2014 your reader is connected (device: ${pairResult.label}).`
|
|
|
7818
8485
|
}
|
|
7819
8486
|
const registration = await sendPairRegistration({ home, host: hostUrl, ttlMs, socketFactory: ctx.socketFactory });
|
|
7820
8487
|
return { text: `Pairing code: ${registration.code}
|
|
7821
|
-
|
|
8488
|
+
${formatPairExpiry(registration, ttlMs)}`, json: registration };
|
|
7822
8489
|
}
|
|
7823
8490
|
if (command === "sessions") {
|
|
7824
8491
|
const action = args.shift();
|
|
@@ -7901,11 +8568,18 @@ ${JSON.stringify(result, null, 2)}`, json: result };
|
|
|
7901
8568
|
}
|
|
7902
8569
|
}
|
|
7903
8570
|
const taxonomyResult = await handleTaxonomyCli(command, args, ctx, store);
|
|
7904
|
-
if (taxonomyResult) return maybeAppendOnboardingNudge(store, command, await maybeAppendHandleNudge(store, command, taxonomyResult));
|
|
8571
|
+
if (taxonomyResult) return maybeAppendUpdateNudge(store, command, await maybeAppendNotifierNudge(store, command, await maybeAppendOnboardingNudge(store, command, await maybeAppendHandleNudge(store, command, taxonomyResult))));
|
|
7905
8572
|
const directoryResult = await handleDirectoryCli(command, args, ctx, home, store);
|
|
7906
8573
|
if (directoryResult) return directoryResult;
|
|
7907
8574
|
throw new EdgeBookError("unknown_command", usage());
|
|
7908
8575
|
}
|
|
8576
|
+
function formatPairExpiry(registration, ttlMs) {
|
|
8577
|
+
if (typeof registration.expires_at === "number") {
|
|
8578
|
+
const mins = Math.max(1, Math.round((registration.expires_at - Date.now()) / 6e4));
|
|
8579
|
+
return `Expires at: ${new Date(registration.expires_at).toISOString()} (~${mins} min from now, host-confirmed)`;
|
|
8580
|
+
}
|
|
8581
|
+
return `Expires in: ~${Math.round(ttlMs / 6e4)} min (estimated)`;
|
|
8582
|
+
}
|
|
7909
8583
|
async function runCli(args) {
|
|
7910
8584
|
const argv = [...args];
|
|
7911
8585
|
const asJson = takeBoolFlag(argv, "--json");
|
|
@@ -7915,10 +8589,11 @@ async function runCli(args) {
|
|
|
7915
8589
|
} else {
|
|
7916
8590
|
console.log(result.text);
|
|
7917
8591
|
}
|
|
8592
|
+
if (result.exitCode) process.exitCode = result.exitCode;
|
|
7918
8593
|
}
|
|
7919
8594
|
function isCliEntrypoint() {
|
|
7920
8595
|
if (!process.argv[1]) return false;
|
|
7921
|
-
return realpathSync(process.argv[1]) === realpathSync(
|
|
8596
|
+
return realpathSync(process.argv[1]) === realpathSync(fileURLToPath2(import.meta.url));
|
|
7922
8597
|
}
|
|
7923
8598
|
if (isCliEntrypoint()) {
|
|
7924
8599
|
runCli(process.argv.slice(2)).catch((error) => {
|