claudemesh-cli 1.34.7 → 1.34.9
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/entrypoints/cli.js +235 -26
- package/dist/entrypoints/cli.js.map +15 -14
- package/dist/entrypoints/mcp.js +63 -9
- package/dist/entrypoints/mcp.js.map +3 -3
- package/package.json +1 -1
- package/skills/claudemesh/SKILL.md +8 -1
package/dist/entrypoints/cli.js
CHANGED
|
@@ -104,7 +104,7 @@ __export(exports_urls, {
|
|
|
104
104
|
VERSION: () => VERSION,
|
|
105
105
|
URLS: () => URLS
|
|
106
106
|
});
|
|
107
|
-
var URLS, VERSION = "1.34.
|
|
107
|
+
var URLS, VERSION = "1.34.9", env;
|
|
108
108
|
var init_urls = __esm(() => {
|
|
109
109
|
URLS = {
|
|
110
110
|
BROKER: process.env.CLAUDEMESH_BROKER_URL ?? "wss://ic.claudemesh.com/ws",
|
|
@@ -4052,11 +4052,18 @@ async function tryListPeersViaDaemon(mesh) {
|
|
|
4052
4052
|
return null;
|
|
4053
4053
|
}
|
|
4054
4054
|
}
|
|
4055
|
-
async function tryListInboxViaDaemon(mesh, limit = 100) {
|
|
4055
|
+
async function tryListInboxViaDaemon(mesh, limit = 100, opts = {}) {
|
|
4056
4056
|
if (!await daemonReachable())
|
|
4057
4057
|
return null;
|
|
4058
4058
|
try {
|
|
4059
|
-
const
|
|
4059
|
+
const params = [`limit=${limit}`];
|
|
4060
|
+
if (mesh)
|
|
4061
|
+
params.push(`mesh=${encodeURIComponent(mesh)}`);
|
|
4062
|
+
if (opts.unreadOnly)
|
|
4063
|
+
params.push("unread_only=true");
|
|
4064
|
+
if (opts.markSeen === false)
|
|
4065
|
+
params.push("mark_seen=false");
|
|
4066
|
+
const path = `/v1/inbox?${params.join("&")}`;
|
|
4060
4067
|
const res = await ipc({ path, timeoutMs: 3000 });
|
|
4061
4068
|
if (res.status !== 200)
|
|
4062
4069
|
return null;
|
|
@@ -4324,6 +4331,7 @@ async function ensureDaemonRunning(meshSlug, quiet) {
|
|
|
4324
4331
|
if (res.state === "up") {
|
|
4325
4332
|
if (!quiet)
|
|
4326
4333
|
render.ok("daemon already running");
|
|
4334
|
+
await warnIfDaemonStale(quiet);
|
|
4327
4335
|
return;
|
|
4328
4336
|
}
|
|
4329
4337
|
if (res.state === "started") {
|
|
@@ -4333,6 +4341,21 @@ async function ensureDaemonRunning(meshSlug, quiet) {
|
|
|
4333
4341
|
}
|
|
4334
4342
|
render.warn(`daemon ${res.state}${res.reason ? `: ${res.reason}` : ""}`, "Run `claudemesh daemon up --mesh " + meshSlug + "` manually, then re-launch.");
|
|
4335
4343
|
}
|
|
4344
|
+
async function warnIfDaemonStale(quiet) {
|
|
4345
|
+
if (quiet)
|
|
4346
|
+
return;
|
|
4347
|
+
try {
|
|
4348
|
+
const { ipc: ipc2 } = await Promise.resolve().then(() => (init_client3(), exports_client));
|
|
4349
|
+
const { VERSION: VERSION2 } = await Promise.resolve().then(() => (init_urls(), exports_urls));
|
|
4350
|
+
const res = await ipc2({ path: "/v1/version", timeoutMs: 1500 });
|
|
4351
|
+
if (res.status !== 200)
|
|
4352
|
+
return;
|
|
4353
|
+
const daemonVersion = res.body.daemon_version ?? "";
|
|
4354
|
+
if (!daemonVersion || daemonVersion === VERSION2)
|
|
4355
|
+
return;
|
|
4356
|
+
render.warn(`daemon is ${daemonVersion}, CLI is ${VERSION2} — restart to pick up new fixes.`, "Run: `claudemesh daemon down && claudemesh daemon up` (or restart the launchd / systemd-user unit).");
|
|
4357
|
+
} catch {}
|
|
4358
|
+
}
|
|
4336
4359
|
function parseGroupsString(raw) {
|
|
4337
4360
|
return raw.split(",").map((s) => s.trim()).filter(Boolean).map((token) => {
|
|
4338
4361
|
const idx = token.indexOf(":");
|
|
@@ -8380,7 +8403,10 @@ function formatMessage(msg, includeMesh) {
|
|
|
8380
8403
|
}
|
|
8381
8404
|
async function runInbox(flags) {
|
|
8382
8405
|
const meshSlug = flags.mesh;
|
|
8383
|
-
const items = await tryListInboxViaDaemon(meshSlug, flags.limit ?? 100
|
|
8406
|
+
const items = await tryListInboxViaDaemon(meshSlug, flags.limit ?? 100, {
|
|
8407
|
+
unreadOnly: flags.unread === true,
|
|
8408
|
+
markSeen: true
|
|
8409
|
+
});
|
|
8384
8410
|
if (items === null) {
|
|
8385
8411
|
if (flags.json) {
|
|
8386
8412
|
process.stdout.write(`[]
|
|
@@ -8397,10 +8423,12 @@ async function runInbox(flags) {
|
|
|
8397
8423
|
}
|
|
8398
8424
|
if (items.length === 0) {
|
|
8399
8425
|
const scope = meshSlug ? `mesh "${meshSlug}"` : "any mesh";
|
|
8400
|
-
|
|
8426
|
+
const filter = flags.unread ? "unread " : "";
|
|
8427
|
+
render.info(dim(`No ${filter}messages on ${scope}.`));
|
|
8401
8428
|
return;
|
|
8402
8429
|
}
|
|
8403
|
-
const
|
|
8430
|
+
const filterTag = flags.unread ? " unread" : "";
|
|
8431
|
+
const heading = meshSlug ? `inbox — ${meshSlug} (${items.length}${filterTag} message${items.length === 1 ? "" : "s"})` : `inbox (${items.length}${filterTag} message${items.length === 1 ? "" : "s"})`;
|
|
8404
8432
|
render.section(heading);
|
|
8405
8433
|
for (const msg of items) {
|
|
8406
8434
|
process.stdout.write(formatMessage(msg, !meshSlug) + `
|
|
@@ -9582,6 +9610,11 @@ function migrateInbox(db) {
|
|
|
9582
9610
|
CREATE INDEX IF NOT EXISTS inbox_topic ON inbox(topic);
|
|
9583
9611
|
CREATE INDEX IF NOT EXISTS inbox_sender ON inbox(sender_pubkey);
|
|
9584
9612
|
`);
|
|
9613
|
+
const cols = db.prepare(`PRAGMA table_info(inbox)`).all();
|
|
9614
|
+
if (!cols.some((c) => c.name === "seen_at")) {
|
|
9615
|
+
db.exec(`ALTER TABLE inbox ADD COLUMN seen_at INTEGER`);
|
|
9616
|
+
db.exec(`CREATE INDEX IF NOT EXISTS inbox_seen_at ON inbox(seen_at)`);
|
|
9617
|
+
}
|
|
9585
9618
|
}
|
|
9586
9619
|
function insertIfNew(db, row) {
|
|
9587
9620
|
const before = db.prepare(`SELECT id FROM inbox WHERE client_message_id = ?`).get(row.client_message_id);
|
|
@@ -9616,9 +9649,12 @@ function listInbox(db, p) {
|
|
|
9616
9649
|
where.push("mesh = ?");
|
|
9617
9650
|
args.push(p.mesh);
|
|
9618
9651
|
}
|
|
9652
|
+
if (p.unreadOnly === true) {
|
|
9653
|
+
where.push("seen_at IS NULL");
|
|
9654
|
+
}
|
|
9619
9655
|
const sql = `
|
|
9620
9656
|
SELECT id, client_message_id, broker_message_id, mesh, topic,
|
|
9621
|
-
sender_pubkey, sender_name, body, meta, received_at, reply_to_id
|
|
9657
|
+
sender_pubkey, sender_name, body, meta, received_at, reply_to_id, seen_at
|
|
9622
9658
|
FROM inbox
|
|
9623
9659
|
${where.length ? "WHERE " + where.join(" AND ") : ""}
|
|
9624
9660
|
ORDER BY received_at DESC
|
|
@@ -9627,6 +9663,17 @@ function listInbox(db, p) {
|
|
|
9627
9663
|
args.push(Math.min(Math.max(p.limit ?? 100, 1), 1000));
|
|
9628
9664
|
return db.prepare(sql).all(...args);
|
|
9629
9665
|
}
|
|
9666
|
+
function markInboxSeen(db, ids, now = Date.now()) {
|
|
9667
|
+
if (ids.length === 0)
|
|
9668
|
+
return 0;
|
|
9669
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
9670
|
+
const r = db.prepare(`UPDATE inbox SET seen_at = ? WHERE seen_at IS NULL AND id IN (${placeholders})`).run(now, ...ids);
|
|
9671
|
+
return Number(r.changes);
|
|
9672
|
+
}
|
|
9673
|
+
function pruneInboxBefore(db, cutoffMs) {
|
|
9674
|
+
const r = db.prepare(`DELETE FROM inbox WHERE received_at < ?`).run(cutoffMs);
|
|
9675
|
+
return Number(r.changes);
|
|
9676
|
+
}
|
|
9630
9677
|
function deleteInboxRow(db, id) {
|
|
9631
9678
|
const r = db.prepare(`DELETE FROM inbox WHERE id = ?`).run(id);
|
|
9632
9679
|
return Number(r.changes) > 0;
|
|
@@ -10387,13 +10434,23 @@ function makeHandler(opts) {
|
|
|
10387
10434
|
const limitRaw = url.searchParams.get("limit");
|
|
10388
10435
|
const limit = limitRaw ? Number.parseInt(limitRaw, 10) : undefined;
|
|
10389
10436
|
const meshFilter = meshFromCtx(url.searchParams.get("mesh")) ?? undefined;
|
|
10437
|
+
const unreadOnly = url.searchParams.get("unread_only") === "true";
|
|
10438
|
+
const markSeen = url.searchParams.get("mark_seen") !== "false";
|
|
10390
10439
|
const rows = listInbox(opts.inboxDb, {
|
|
10391
10440
|
since: Number.isFinite(since) ? since : undefined,
|
|
10392
10441
|
topic,
|
|
10393
10442
|
fromPubkey,
|
|
10394
10443
|
...meshFilter ? { mesh: meshFilter } : {},
|
|
10444
|
+
unreadOnly,
|
|
10395
10445
|
limit: Number.isFinite(limit ?? NaN) ? limit : undefined
|
|
10396
10446
|
});
|
|
10447
|
+
let flippedCount = 0;
|
|
10448
|
+
if (markSeen) {
|
|
10449
|
+
const unreadIds = rows.filter((r) => r.seen_at == null).map((r) => r.id);
|
|
10450
|
+
if (unreadIds.length > 0) {
|
|
10451
|
+
flippedCount = markInboxSeen(opts.inboxDb, unreadIds);
|
|
10452
|
+
}
|
|
10453
|
+
}
|
|
10397
10454
|
respond(res, 200, {
|
|
10398
10455
|
items: rows.map((r) => ({
|
|
10399
10456
|
id: r.id,
|
|
@@ -10405,11 +10462,32 @@ function makeHandler(opts) {
|
|
|
10405
10462
|
sender_name: r.sender_name,
|
|
10406
10463
|
body: r.body,
|
|
10407
10464
|
received_at: new Date(r.received_at).toISOString(),
|
|
10408
|
-
reply_to_id: r.reply_to_id
|
|
10409
|
-
|
|
10465
|
+
reply_to_id: r.reply_to_id,
|
|
10466
|
+
seen_at: r.seen_at ? new Date(r.seen_at).toISOString() : null
|
|
10467
|
+
})),
|
|
10468
|
+
marked_seen: flippedCount
|
|
10410
10469
|
});
|
|
10411
10470
|
return;
|
|
10412
10471
|
}
|
|
10472
|
+
if (req.method === "POST" && url.pathname === "/v1/inbox/seen") {
|
|
10473
|
+
if (!opts.inboxDb) {
|
|
10474
|
+
respond(res, 503, { error: "inbox not initialised" });
|
|
10475
|
+
return;
|
|
10476
|
+
}
|
|
10477
|
+
try {
|
|
10478
|
+
const body = await readJsonBody(req, 64 * 1024);
|
|
10479
|
+
const ids = Array.isArray(body?.ids) ? body.ids.filter((x) => typeof x === "string") : [];
|
|
10480
|
+
if (ids.length === 0) {
|
|
10481
|
+
respond(res, 400, { error: "missing 'ids' (string[])" });
|
|
10482
|
+
return;
|
|
10483
|
+
}
|
|
10484
|
+
const flipped = markInboxSeen(opts.inboxDb, ids);
|
|
10485
|
+
respond(res, 200, { marked_seen: flipped });
|
|
10486
|
+
} catch (e) {
|
|
10487
|
+
respond(res, 400, { error: String(e) });
|
|
10488
|
+
}
|
|
10489
|
+
return;
|
|
10490
|
+
}
|
|
10413
10491
|
if (req.method === "DELETE" && url.pathname === "/v1/inbox") {
|
|
10414
10492
|
if (!opts.inboxDb) {
|
|
10415
10493
|
respond(res, 503, { error: "inbox not initialised" });
|
|
@@ -11376,6 +11454,13 @@ class SessionBrokerClient {
|
|
|
11376
11454
|
return;
|
|
11377
11455
|
}
|
|
11378
11456
|
if (msg.type === "push" || msg.type === "inbound") {
|
|
11457
|
+
if (msg.subtype === "system")
|
|
11458
|
+
return;
|
|
11459
|
+
const senderPubkey = String(msg.senderPubkey ?? "").toLowerCase();
|
|
11460
|
+
if (senderPubkey && senderPubkey === this.opts.sessionPubkey.toLowerCase()) {
|
|
11461
|
+
this.log("info", "self_echo_dropped", { sender: senderPubkey.slice(0, 12) });
|
|
11462
|
+
return;
|
|
11463
|
+
}
|
|
11379
11464
|
this.opts.onPush?.(msg);
|
|
11380
11465
|
return;
|
|
11381
11466
|
}
|
|
@@ -11667,14 +11752,58 @@ function defaultLog4(level, msg, meta) {
|
|
|
11667
11752
|
var POLL_INTERVAL_MS = 500, MAX_ATTEMPTS_PER_ROW = 25, BACKOFF_BASE_MS = 500, BACKOFF_CAP_MS = 30000;
|
|
11668
11753
|
var init_drain = () => {};
|
|
11669
11754
|
|
|
11755
|
+
// src/daemon/inbox-pruner.ts
|
|
11756
|
+
function startInboxPruner(opts) {
|
|
11757
|
+
const retentionMs = opts.retentionMs ?? DEFAULT_RETENTION_MS;
|
|
11758
|
+
const intervalMs = opts.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
11759
|
+
const log2 = opts.log ?? defaultLog5;
|
|
11760
|
+
const tick = () => {
|
|
11761
|
+
try {
|
|
11762
|
+
const cutoff = Date.now() - retentionMs;
|
|
11763
|
+
const removed = pruneInboxBefore(opts.db, cutoff);
|
|
11764
|
+
if (removed > 0) {
|
|
11765
|
+
log2("info", "inbox_prune_completed", {
|
|
11766
|
+
removed,
|
|
11767
|
+
retention_days: Math.round(retentionMs / (24 * 60 * 60 * 1000))
|
|
11768
|
+
});
|
|
11769
|
+
}
|
|
11770
|
+
} catch (e) {
|
|
11771
|
+
log2("warn", "inbox_prune_failed", { err: String(e) });
|
|
11772
|
+
}
|
|
11773
|
+
};
|
|
11774
|
+
tick();
|
|
11775
|
+
const handle = setInterval(tick, intervalMs);
|
|
11776
|
+
if (typeof handle.unref === "function")
|
|
11777
|
+
handle.unref();
|
|
11778
|
+
return { stop: () => clearInterval(handle) };
|
|
11779
|
+
}
|
|
11780
|
+
function defaultLog5(level, msg, meta) {
|
|
11781
|
+
const line = JSON.stringify({ level, msg, ...meta, ts: new Date().toISOString() });
|
|
11782
|
+
if (level === "info")
|
|
11783
|
+
process.stdout.write(line + `
|
|
11784
|
+
`);
|
|
11785
|
+
else
|
|
11786
|
+
process.stderr.write(line + `
|
|
11787
|
+
`);
|
|
11788
|
+
}
|
|
11789
|
+
var DEFAULT_RETENTION_MS, DEFAULT_INTERVAL_MS;
|
|
11790
|
+
var init_inbox_pruner = __esm(() => {
|
|
11791
|
+
DEFAULT_RETENTION_MS = 30 * 24 * 60 * 60 * 1000;
|
|
11792
|
+
DEFAULT_INTERVAL_MS = 60 * 60 * 1000;
|
|
11793
|
+
});
|
|
11794
|
+
|
|
11670
11795
|
// src/daemon/inbound.ts
|
|
11671
11796
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
11672
11797
|
async function handleBrokerPush(msg, ctx) {
|
|
11673
11798
|
if (msg.subtype === "system" && typeof msg.event === "string") {
|
|
11799
|
+
const eventData = msg.eventData ?? {};
|
|
11800
|
+
const eventPubkey = typeof eventData.pubkey === "string" ? eventData.pubkey : "";
|
|
11801
|
+
if (eventPubkey && ctx.isOwnPubkey?.(eventPubkey))
|
|
11802
|
+
return;
|
|
11674
11803
|
ctx.bus.publish(mapSystemEventKind(msg.event), {
|
|
11675
11804
|
mesh: ctx.meshSlug,
|
|
11676
11805
|
event: msg.event,
|
|
11677
|
-
...
|
|
11806
|
+
...eventData
|
|
11678
11807
|
});
|
|
11679
11808
|
return;
|
|
11680
11809
|
}
|
|
@@ -11966,6 +12095,8 @@ async function runDaemon(opts = {}) {
|
|
|
11966
12095
|
} else {
|
|
11967
12096
|
meshes = cfg.meshes;
|
|
11968
12097
|
}
|
|
12098
|
+
const sessionBrokers = new Map;
|
|
12099
|
+
const sessionBrokersByPubkey = new Map;
|
|
11969
12100
|
const brokers = new Map;
|
|
11970
12101
|
const meshConfigs = new Map;
|
|
11971
12102
|
for (const mesh of meshes) {
|
|
@@ -11983,12 +12114,23 @@ async function runDaemon(opts = {}) {
|
|
|
11983
12114
|
bus.publish("broker_status", { mesh: mesh.slug, status: s });
|
|
11984
12115
|
},
|
|
11985
12116
|
onPush: (m) => {
|
|
12117
|
+
const senderMemberPk = String(m.senderMemberPubkey ?? "").toLowerCase();
|
|
12118
|
+
const ownMember = mesh.pubkey.toLowerCase();
|
|
12119
|
+
if (senderMemberPk && senderMemberPk === ownMember) {
|
|
12120
|
+
return;
|
|
12121
|
+
}
|
|
11986
12122
|
handleBrokerPush(m, {
|
|
11987
12123
|
db: inboxDb,
|
|
11988
12124
|
bus,
|
|
11989
12125
|
meshSlug: mesh.slug,
|
|
11990
12126
|
recipientSecretKeyHex: mesh.secretKey,
|
|
11991
|
-
ackClientMessage: (cmid, bmid) => broker.sendClientAck(cmid, bmid)
|
|
12127
|
+
ackClientMessage: (cmid, bmid) => broker.sendClientAck(cmid, bmid),
|
|
12128
|
+
isOwnPubkey: (pubkey) => {
|
|
12129
|
+
const lower = pubkey.toLowerCase();
|
|
12130
|
+
if (lower === ownMember)
|
|
12131
|
+
return true;
|
|
12132
|
+
return sessionBrokersByPubkey.has(lower);
|
|
12133
|
+
}
|
|
11992
12134
|
});
|
|
11993
12135
|
}
|
|
11994
12136
|
});
|
|
@@ -11996,14 +12138,13 @@ async function runDaemon(opts = {}) {
|
|
|
11996
12138
|
`));
|
|
11997
12139
|
brokers.set(mesh.slug, broker);
|
|
11998
12140
|
}
|
|
11999
|
-
const sessionBrokers = new Map;
|
|
12000
|
-
const sessionBrokersByPubkey = new Map;
|
|
12001
12141
|
let drain = null;
|
|
12002
12142
|
drain = startDrainWorker({
|
|
12003
12143
|
db: outboxDb,
|
|
12004
12144
|
brokers,
|
|
12005
12145
|
getSessionBrokerByPubkey: (pubkey) => sessionBrokersByPubkey.get(pubkey)
|
|
12006
12146
|
});
|
|
12147
|
+
const inboxPruner = startInboxPruner({ db: inboxDb });
|
|
12007
12148
|
setRegistryHooks({
|
|
12008
12149
|
onRegister: (info) => {
|
|
12009
12150
|
if (!info.presence)
|
|
@@ -12107,6 +12248,7 @@ async function runDaemon(opts = {}) {
|
|
|
12107
12248
|
shuttingDown = true;
|
|
12108
12249
|
process.stdout.write(JSON.stringify({ msg: "daemon_shutdown", signal: sig, ts: new Date().toISOString() }) + `
|
|
12109
12250
|
`);
|
|
12251
|
+
inboxPruner.stop();
|
|
12110
12252
|
if (drain)
|
|
12111
12253
|
await drain.close();
|
|
12112
12254
|
for (const b of brokers.values()) {
|
|
@@ -12143,6 +12285,7 @@ var init_run = __esm(() => {
|
|
|
12143
12285
|
init_broker();
|
|
12144
12286
|
init_session_broker();
|
|
12145
12287
|
init_drain();
|
|
12288
|
+
init_inbox_pruner();
|
|
12146
12289
|
init_inbound();
|
|
12147
12290
|
init_identity();
|
|
12148
12291
|
init_facade();
|
|
@@ -16270,7 +16413,8 @@ claudemesh message send <p> "..." --priority low # pull-only
|
|
|
16270
16413
|
claudemesh inbox # all attached meshes, last 100
|
|
16271
16414
|
claudemesh inbox --mesh <slug> # scoped to one mesh
|
|
16272
16415
|
claudemesh inbox --mesh <slug> --limit 20 # custom cap
|
|
16273
|
-
claudemesh inbox --json # full row (sender_pubkey, mesh, body, received_at, …)
|
|
16416
|
+
claudemesh inbox --json # full row (sender_pubkey, mesh, body, received_at, seen_at, …)
|
|
16417
|
+
claudemesh inbox --unread # 1.34.8+ only rows whose seen_at IS NULL
|
|
16274
16418
|
|
|
16275
16419
|
# inbox flush + delete — 1.34.7+
|
|
16276
16420
|
claudemesh inbox flush --mesh <slug> # delete all rows on one mesh
|
|
@@ -16286,6 +16430,12 @@ claudemesh message status <message-id> --json
|
|
|
16286
16430
|
|
|
16287
16431
|
**Inbox source (1.34.0+):** \`claudemesh inbox\` queries the daemon's persistent \`~/.claudemesh/daemon/inbox.db\` over IPC — it is NOT a fresh broker-WS buffer drain. Rows survive daemon restarts. Sender attribution is the actual session pubkey of the launched session that originated the send (NOT the stable member pubkey of the sender's daemon), so two sibling sessions of the same human appear as distinct rows.
|
|
16288
16432
|
|
|
16433
|
+
**Read-state (1.34.8+):** every inbox row carries a \`seen_at\` timestamp. \`null\` = never surfaced; an ISO string = first surfaced at that moment. The flag flips automatically when (a) the row is returned by an interactive \`claudemesh inbox\` listing, or (b) the MCP server emits a live \`<channel>\` reminder for it. The launch welcome push uses \`unread_only=true\` to surface only rows the user hasn't seen — so a session relaunched a day later sees what it actually missed, not the same 24h batch every time. Use \`claudemesh inbox --unread\` to get the same filter from the CLI.
|
|
16434
|
+
|
|
16435
|
+
**Self-echo guard (1.34.8+):** broker fan-out paths sometimes mirror an outbound DM back to the originating session-WS. The daemon now drops those at the WS boundary (matching on \`senderPubkey === own.session_pubkey\`), so the sender no longer sees their own \`claudemesh send\` arrive as a \`← claudemesh: <self>: ...\` channel push immediately after dispatching it.
|
|
16436
|
+
|
|
16437
|
+
**Inbox TTL (1.34.8+):** the daemon runs an hourly prune that deletes rows older than 30 days. Without this the inbox grew unbounded; now it self-trims while preserving "I went on holiday and want to see what I missed" recovery for a generous window. No CLI knob — it's a built-in retention policy. To override, manually \`claudemesh inbox flush --before <iso>\`.
|
|
16438
|
+
|
|
16289
16439
|
\`send\` JSON output: \`{"ok": true, "messageId": "...", "target": "..."}\`. Errors: \`{"ok": false, "error": "..."}\`.
|
|
16290
16440
|
|
|
16291
16441
|
### \`state\` — shared per-mesh key-value store
|
|
@@ -18389,6 +18539,32 @@ function daemonGet(path2, opts = {}) {
|
|
|
18389
18539
|
req.end();
|
|
18390
18540
|
});
|
|
18391
18541
|
}
|
|
18542
|
+
function daemonMarkSeen(ids, sessionToken) {
|
|
18543
|
+
return new Promise((resolve3) => {
|
|
18544
|
+
if (ids.length === 0) {
|
|
18545
|
+
resolve3();
|
|
18546
|
+
return;
|
|
18547
|
+
}
|
|
18548
|
+
const body = JSON.stringify({ ids });
|
|
18549
|
+
const headers = {
|
|
18550
|
+
"Content-Type": "application/json",
|
|
18551
|
+
"Content-Length": String(Buffer.byteLength(body))
|
|
18552
|
+
};
|
|
18553
|
+
if (sessionToken)
|
|
18554
|
+
headers.Authorization = `ClaudeMesh-Session ${sessionToken}`;
|
|
18555
|
+
const req = httpRequest2({ socketPath: DAEMON_PATHS.SOCK_FILE, path: "/v1/inbox/seen", method: "POST", timeout: 3000, headers }, (res) => {
|
|
18556
|
+
res.on("data", () => {});
|
|
18557
|
+
res.on("end", () => resolve3());
|
|
18558
|
+
});
|
|
18559
|
+
req.on("error", () => resolve3());
|
|
18560
|
+
req.on("timeout", () => {
|
|
18561
|
+
req.destroy();
|
|
18562
|
+
resolve3();
|
|
18563
|
+
});
|
|
18564
|
+
req.write(body);
|
|
18565
|
+
req.end();
|
|
18566
|
+
});
|
|
18567
|
+
}
|
|
18392
18568
|
function subscribeEvents(onEvent) {
|
|
18393
18569
|
let active = true;
|
|
18394
18570
|
let req = null;
|
|
@@ -18601,6 +18777,8 @@ ${mf.allowed_tools.map((t) => ` - ${t}`).join(`
|
|
|
18601
18777
|
} catch {}
|
|
18602
18778
|
};
|
|
18603
18779
|
mcpLog("mcp_started", { version: VERSION });
|
|
18780
|
+
const { readSessionTokenFromEnv: readSessionTokenFromEnv2 } = await Promise.resolve().then(() => (init_token(), exports_token));
|
|
18781
|
+
const sessionTokenForSeen = readSessionTokenFromEnv2();
|
|
18604
18782
|
const sub = subscribeEvents(async (ev) => {
|
|
18605
18783
|
mcpLog("sse_event_received", { kind: ev.kind });
|
|
18606
18784
|
if (ev.kind === "message") {
|
|
@@ -18633,6 +18811,10 @@ ${mf.allowed_tools.map((t) => ` - ${t}`).join(`
|
|
|
18633
18811
|
}
|
|
18634
18812
|
});
|
|
18635
18813
|
mcpLog("channel_emitted", { content_preview: content.slice(0, 80), mesh: String(d.mesh ?? "") });
|
|
18814
|
+
const inboxRowId = String(d.id ?? "");
|
|
18815
|
+
if (inboxRowId) {
|
|
18816
|
+
daemonMarkSeen([inboxRowId], sessionTokenForSeen).catch(() => {});
|
|
18817
|
+
}
|
|
18636
18818
|
} catch (err) {
|
|
18637
18819
|
mcpLog("channel_emit_failed", { err: String(err) });
|
|
18638
18820
|
process.stderr.write(`[claudemesh-mcp] channel emit failed: ${err}
|
|
@@ -18641,11 +18823,23 @@ ${mf.allowed_tools.map((t) => ` - ${t}`).join(`
|
|
|
18641
18823
|
} else if (ev.kind === "peer_join" || ev.kind === "peer_leave" || ev.kind === "system") {
|
|
18642
18824
|
const d = ev.data;
|
|
18643
18825
|
const eventName = String(d.event ?? ev.kind);
|
|
18826
|
+
const renderPeerLine = (verb) => {
|
|
18827
|
+
const name = String(d.name ?? "unknown");
|
|
18828
|
+
const pubkey = String(d.pubkey ?? "");
|
|
18829
|
+
const pubkeyTag = pubkey ? ` (${pubkey.slice(0, 8)})` : "";
|
|
18830
|
+
const groups = Array.isArray(d.groups) ? d.groups : [];
|
|
18831
|
+
const groupNames = groups.map((g) => typeof g === "object" && g !== null && ("name" in g) ? String(g.name) : typeof g === "string" ? g : "").filter(Boolean);
|
|
18832
|
+
const groupsTag = groupNames.length > 0 ? ` [${groupNames.join(", ")}]` : "";
|
|
18833
|
+
const lastSeen = typeof d.lastSeenAt === "string" ? d.lastSeenAt : null;
|
|
18834
|
+
const summary = typeof d.summary === "string" && d.summary.trim() ? d.summary.trim() : null;
|
|
18835
|
+
const returningTail = lastSeen ? ` — last seen ${new Date(lastSeen).toLocaleTimeString()}${summary ? ` · "${summary.slice(0, 80)}"` : ""}` : "";
|
|
18836
|
+
return `[system] Peer "${name}"${pubkeyTag}${groupsTag} ${verb} the mesh${returningTail}`;
|
|
18837
|
+
};
|
|
18644
18838
|
let content;
|
|
18645
18839
|
if (ev.kind === "peer_join") {
|
|
18646
|
-
content =
|
|
18840
|
+
content = renderPeerLine(eventName === "peer_returned" ? "returned to" : "joined");
|
|
18647
18841
|
} else if (ev.kind === "peer_leave") {
|
|
18648
|
-
content =
|
|
18842
|
+
content = renderPeerLine("left");
|
|
18649
18843
|
} else {
|
|
18650
18844
|
content = `[system] ${eventName}: ${JSON.stringify(d).slice(0, 240)}`;
|
|
18651
18845
|
}
|
|
@@ -18657,7 +18851,12 @@ ${mf.allowed_tools.map((t) => ` - ${t}`).join(`
|
|
|
18657
18851
|
meta: {
|
|
18658
18852
|
kind: "system",
|
|
18659
18853
|
event: eventName,
|
|
18660
|
-
mesh_slug: String(d.mesh ?? "")
|
|
18854
|
+
mesh_slug: String(d.mesh ?? ""),
|
|
18855
|
+
...typeof d.name === "string" ? { peer_name: d.name } : {},
|
|
18856
|
+
...typeof d.pubkey === "string" ? { peer_pubkey: d.pubkey } : {},
|
|
18857
|
+
...Array.isArray(d.groups) ? { peer_groups: JSON.stringify(d.groups) } : {},
|
|
18858
|
+
...typeof d.lastSeenAt === "string" ? { peer_last_seen_at: d.lastSeenAt } : {},
|
|
18859
|
+
...typeof d.summary === "string" ? { peer_summary: d.summary } : {}
|
|
18661
18860
|
}
|
|
18662
18861
|
}
|
|
18663
18862
|
});
|
|
@@ -18732,8 +18931,7 @@ async function emitMeshWelcome(server, mcpLog) {
|
|
|
18732
18931
|
} catch (e) {
|
|
18733
18932
|
mcpLog("welcome_peers_lookup_failed", { err: String(e) });
|
|
18734
18933
|
}
|
|
18735
|
-
const
|
|
18736
|
-
const inboxPath = selfMeshSlug ? `/v1/inbox?mesh=${encodeURIComponent(selfMeshSlug)}&since=${encodeURIComponent(sinceIso)}&limit=20` : `/v1/inbox?since=${encodeURIComponent(sinceIso)}&limit=20`;
|
|
18934
|
+
const inboxPath = selfMeshSlug ? `/v1/inbox?mesh=${encodeURIComponent(selfMeshSlug)}&unread_only=true&mark_seen=false&limit=50` : `/v1/inbox?unread_only=true&mark_seen=false&limit=50`;
|
|
18737
18935
|
let inboxItems = [];
|
|
18738
18936
|
try {
|
|
18739
18937
|
const { status, body } = await daemonGet(inboxPath, { sessionToken });
|
|
@@ -18757,9 +18955,9 @@ async function emitMeshWelcome(server, mcpLog) {
|
|
|
18757
18955
|
lines.push(`\uD83D\uDC65 Peer list unavailable (daemon query failed).`);
|
|
18758
18956
|
}
|
|
18759
18957
|
if (inboxItems.length === 0) {
|
|
18760
|
-
lines.push(`\uD83D\uDCE5
|
|
18958
|
+
lines.push(`\uD83D\uDCE5 No unread messages.`);
|
|
18761
18959
|
} else {
|
|
18762
|
-
lines.push(`\uD83D\uDCE5 ${inboxItems.length} message${inboxItems.length === 1 ? "" : "s"}
|
|
18960
|
+
lines.push(`\uD83D\uDCE5 ${inboxItems.length} unread message${inboxItems.length === 1 ? "" : "s"}:`);
|
|
18763
18961
|
for (const it of inboxItems.slice(0, 3)) {
|
|
18764
18962
|
const sender = String(it.sender_name ?? "unknown");
|
|
18765
18963
|
const senderPub = String(it.sender_pubkey ?? "").slice(0, 8);
|
|
@@ -18798,6 +18996,12 @@ async function emitMeshWelcome(server, mcpLog) {
|
|
|
18798
18996
|
peer_count: peerCount,
|
|
18799
18997
|
unread_count: inboxItems.length
|
|
18800
18998
|
});
|
|
18999
|
+
if (inboxItems.length > 0) {
|
|
19000
|
+
const ids = inboxItems.map((it) => String(it.id ?? "")).filter(Boolean);
|
|
19001
|
+
if (ids.length > 0) {
|
|
19002
|
+
daemonMarkSeen(ids, sessionToken).catch(() => {});
|
|
19003
|
+
}
|
|
19004
|
+
}
|
|
18801
19005
|
} catch (err) {
|
|
18802
19006
|
mcpLog("welcome_emit_failed", { err: String(err) });
|
|
18803
19007
|
}
|
|
@@ -19060,7 +19264,8 @@ var BOOLEAN_FLAGS = new Set([
|
|
|
19060
19264
|
"force",
|
|
19061
19265
|
"dry-run",
|
|
19062
19266
|
"verbose",
|
|
19063
|
-
"skip-service"
|
|
19267
|
+
"skip-service",
|
|
19268
|
+
"unread"
|
|
19064
19269
|
]);
|
|
19065
19270
|
function parseArgv(argv) {
|
|
19066
19271
|
const args = argv.slice(2);
|
|
@@ -19673,8 +19878,10 @@ Message (resource form)
|
|
|
19673
19878
|
fans out to every sibling session of your member)
|
|
19674
19879
|
[--json] (machine-readable result)
|
|
19675
19880
|
claudemesh message inbox read persisted inbox (alias: inbox)
|
|
19676
|
-
flags: [--mesh <slug>] [--limit N] [--json]
|
|
19881
|
+
flags: [--mesh <slug>] [--limit N] [--unread] [--json]
|
|
19677
19882
|
reads ~/.claudemesh/daemon/inbox.db via daemon
|
|
19883
|
+
--unread → only rows never surfaced before (seen_at IS NULL);
|
|
19884
|
+
listing stamps returned rows seen as a side effect
|
|
19678
19885
|
claudemesh inbox flush bulk-delete inbox rows
|
|
19679
19886
|
flags: [--mesh <slug>] [--before <iso-timestamp>] [--all]
|
|
19680
19887
|
--all required when neither --mesh nor --before is set
|
|
@@ -20052,7 +20259,8 @@ async function main() {
|
|
|
20052
20259
|
await runInbox2({
|
|
20053
20260
|
mesh: flags.mesh,
|
|
20054
20261
|
json: !!flags.json,
|
|
20055
|
-
limit: typeof flags.limit === "number" ? flags.limit : typeof flags.limit === "string" ? Number.parseInt(flags.limit, 10) : undefined
|
|
20262
|
+
limit: typeof flags.limit === "number" ? flags.limit : typeof flags.limit === "string" ? Number.parseInt(flags.limit, 10) : undefined,
|
|
20263
|
+
unread: !!flags.unread
|
|
20056
20264
|
});
|
|
20057
20265
|
}
|
|
20058
20266
|
break;
|
|
@@ -20325,7 +20533,8 @@ async function main() {
|
|
|
20325
20533
|
await runInbox2({
|
|
20326
20534
|
mesh: flags.mesh,
|
|
20327
20535
|
json: !!flags.json,
|
|
20328
|
-
limit: typeof flags.limit === "number" ? flags.limit : typeof flags.limit === "string" ? Number.parseInt(flags.limit, 10) : undefined
|
|
20536
|
+
limit: typeof flags.limit === "number" ? flags.limit : typeof flags.limit === "string" ? Number.parseInt(flags.limit, 10) : undefined,
|
|
20537
|
+
unread: !!flags.unread
|
|
20329
20538
|
});
|
|
20330
20539
|
}
|
|
20331
20540
|
} else if (sub === "status") {
|
|
@@ -20880,4 +21089,4 @@ main().catch((err) => {
|
|
|
20880
21089
|
process.exit(EXIT.INTERNAL_ERROR);
|
|
20881
21090
|
});
|
|
20882
21091
|
|
|
20883
|
-
//# debugId=
|
|
21092
|
+
//# debugId=7232363C9624744664756E2164756E21
|