claudemesh-cli 1.24.0 → 1.25.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.
@@ -103,7 +103,7 @@ __export(exports_urls, {
103
103
  VERSION: () => VERSION,
104
104
  URLS: () => URLS
105
105
  });
106
- var URLS, VERSION = "1.24.0", env;
106
+ var URLS, VERSION = "1.25.0", env;
107
107
  var init_urls = __esm(() => {
108
108
  URLS = {
109
109
  BROKER: process.env.CLAUDEMESH_BROKER_URL ?? "wss://ic.claudemesh.com/ws",
@@ -1026,6 +1026,12 @@ var init_file_crypto = __esm(() => {
1026
1026
  });
1027
1027
 
1028
1028
  // src/services/crypto/box.ts
1029
+ var exports_box = {};
1030
+ __export(exports_box, {
1031
+ isDirectTarget: () => isDirectTarget,
1032
+ encryptDirect: () => encryptDirect,
1033
+ decryptDirect: () => decryptDirect
1034
+ });
1029
1035
  function isDirectTarget(targetSpec) {
1030
1036
  return HEX_PUBKEY.test(targetSpec);
1031
1037
  }
@@ -6790,105 +6796,6 @@ var init_client3 = __esm(() => {
6790
6796
  init_protocol();
6791
6797
  });
6792
6798
 
6793
- // src/commands/peers.ts
6794
- var exports_peers = {};
6795
- __export(exports_peers, {
6796
- runPeers: () => runPeers
6797
- });
6798
- function projectFields(record, fields) {
6799
- const out = {};
6800
- for (const f of fields) {
6801
- const sourceKey = FIELD_ALIAS[f] ?? f;
6802
- out[f] = record[sourceKey];
6803
- }
6804
- return out;
6805
- }
6806
- async function listPeersForMesh(slug) {
6807
- const config = readConfig();
6808
- const joined = config.meshes.find((m) => m.slug === slug);
6809
- const selfMemberPubkey = joined?.pubkey ?? null;
6810
- const bridged = await tryBridge(slug, "peers");
6811
- if (bridged && bridged.ok) {
6812
- const peers = bridged.result;
6813
- return peers.map((p) => annotateSelf(p, selfMemberPubkey, null));
6814
- }
6815
- let result = [];
6816
- await withMesh({ meshSlug: slug }, async (client) => {
6817
- const all = await client.listPeers();
6818
- const selfSessionPubkey = client.getSessionPubkey();
6819
- result = all.map((p) => annotateSelf(p, selfMemberPubkey, selfSessionPubkey));
6820
- });
6821
- return result;
6822
- }
6823
- function annotateSelf(peer, selfMemberPubkey, selfSessionPubkey) {
6824
- const isSelf = !!(selfMemberPubkey && peer.memberPubkey && peer.memberPubkey === selfMemberPubkey);
6825
- const isThisSession = !!(isSelf && selfSessionPubkey && peer.pubkey === selfSessionPubkey);
6826
- return { ...peer, isSelf, isThisSession };
6827
- }
6828
- async function runPeers(flags) {
6829
- const config = readConfig();
6830
- const slugs = flags.mesh ? [flags.mesh] : config.meshes.map((m) => m.slug);
6831
- if (slugs.length === 0) {
6832
- render.err("No meshes joined.");
6833
- render.hint("claudemesh <invite-url> # join + launch");
6834
- process.exit(1);
6835
- }
6836
- const fieldList = typeof flags.json === "string" && flags.json.length > 0 ? flags.json.split(",").map((s) => s.trim()).filter(Boolean) : null;
6837
- const wantsJson = flags.json !== undefined && flags.json !== false;
6838
- const allJson = [];
6839
- for (const slug of slugs) {
6840
- try {
6841
- const peers = await listPeersForMesh(slug);
6842
- if (wantsJson) {
6843
- const projected = fieldList ? peers.map((p) => projectFields(p, fieldList)) : peers;
6844
- allJson.push({ mesh: slug, peers: projected });
6845
- continue;
6846
- }
6847
- render.section(`peers on ${slug} (${peers.length})`);
6848
- if (peers.length === 0) {
6849
- render.info(dim(" (no peers connected)"));
6850
- continue;
6851
- }
6852
- for (const p of peers) {
6853
- const groups = p.groups.length ? " [" + p.groups.map((g) => `@${g.name}${g.role ? `:${g.role}` : ""}`).join(", ") + "]" : "";
6854
- const statusDot = p.status === "working" ? yellow("●") : green("●");
6855
- const name = bold(p.displayName);
6856
- const meta = [];
6857
- if (p.peerType)
6858
- meta.push(p.peerType);
6859
- if (p.channel)
6860
- meta.push(p.channel);
6861
- if (p.model)
6862
- meta.push(p.model);
6863
- const metaStr = meta.length ? dim(` (${meta.join(", ")})`) : "";
6864
- const summary = p.summary ? dim(` — ${p.summary}`) : "";
6865
- const pubkeyTag = dim(` · ${p.pubkey.slice(0, 16)}…`);
6866
- const selfTag = p.isThisSession ? dim(" ") + yellow("(this session)") : p.isSelf ? dim(" ") + yellow("(your other session)") : "";
6867
- render.info(`${statusDot} ${name}${selfTag}${groups}${metaStr}${pubkeyTag}${summary}`);
6868
- if (p.cwd)
6869
- render.info(dim(` cwd: ${p.cwd}`));
6870
- }
6871
- } catch (e) {
6872
- render.err(`${slug}: ${e instanceof Error ? e.message : String(e)}`);
6873
- }
6874
- }
6875
- if (wantsJson) {
6876
- process.stdout.write(JSON.stringify(slugs.length === 1 ? allJson[0]?.peers : allJson, null, 2) + `
6877
- `);
6878
- }
6879
- }
6880
- var FIELD_ALIAS;
6881
- var init_peers = __esm(() => {
6882
- init_connect();
6883
- init_facade();
6884
- init_client3();
6885
- init_render();
6886
- init_styles();
6887
- FIELD_ALIAS = {
6888
- name: "displayName"
6889
- };
6890
- });
6891
-
6892
6799
  // src/daemon/local-token.ts
6893
6800
  import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "node:fs";
6894
6801
  import { dirname as dirname3 } from "node:path";
@@ -6970,7 +6877,61 @@ var init_client4 = __esm(() => {
6970
6877
  });
6971
6878
 
6972
6879
  // src/services/bridge/daemon-route.ts
6880
+ var exports_daemon_route = {};
6881
+ __export(exports_daemon_route, {
6882
+ trySendViaDaemon: () => trySendViaDaemon,
6883
+ tryListSkillsViaDaemon: () => tryListSkillsViaDaemon,
6884
+ tryListPeersViaDaemon: () => tryListPeersViaDaemon,
6885
+ tryGetSkillViaDaemon: () => tryGetSkillViaDaemon
6886
+ });
6973
6887
  import { existsSync as existsSync7 } from "node:fs";
6888
+ async function tryListPeersViaDaemon() {
6889
+ if (!existsSync7(DAEMON_PATHS.SOCK_FILE))
6890
+ return null;
6891
+ try {
6892
+ const res = await ipc({ path: "/v1/peers", timeoutMs: 3000 });
6893
+ if (res.status !== 200)
6894
+ return null;
6895
+ return Array.isArray(res.body.peers) ? res.body.peers : [];
6896
+ } catch (err) {
6897
+ const msg = String(err);
6898
+ if (/ENOENT|ECONNREFUSED|ipc_timeout/.test(msg))
6899
+ return null;
6900
+ return null;
6901
+ }
6902
+ }
6903
+ async function tryListSkillsViaDaemon() {
6904
+ if (!existsSync7(DAEMON_PATHS.SOCK_FILE))
6905
+ return null;
6906
+ try {
6907
+ const res = await ipc({ path: "/v1/skills", timeoutMs: 3000 });
6908
+ if (res.status !== 200)
6909
+ return null;
6910
+ return Array.isArray(res.body.skills) ? res.body.skills : [];
6911
+ } catch (err) {
6912
+ const msg = String(err);
6913
+ if (/ENOENT|ECONNREFUSED|ipc_timeout/.test(msg))
6914
+ return null;
6915
+ return null;
6916
+ }
6917
+ }
6918
+ async function tryGetSkillViaDaemon(name) {
6919
+ if (!existsSync7(DAEMON_PATHS.SOCK_FILE))
6920
+ return null;
6921
+ try {
6922
+ const res = await ipc({
6923
+ path: `/v1/skills/${encodeURIComponent(name)}`,
6924
+ timeoutMs: 3000
6925
+ });
6926
+ if (res.status === 404)
6927
+ return null;
6928
+ if (res.status !== 200)
6929
+ return null;
6930
+ return res.body.skill ?? null;
6931
+ } catch {
6932
+ return null;
6933
+ }
6934
+ }
6974
6935
  async function trySendViaDaemon(args) {
6975
6936
  if (!existsSync7(DAEMON_PATHS.SOCK_FILE))
6976
6937
  return null;
@@ -7007,6 +6968,112 @@ var init_daemon_route = __esm(() => {
7007
6968
  init_paths2();
7008
6969
  });
7009
6970
 
6971
+ // src/commands/peers.ts
6972
+ var exports_peers = {};
6973
+ __export(exports_peers, {
6974
+ runPeers: () => runPeers
6975
+ });
6976
+ function projectFields(record, fields) {
6977
+ const out = {};
6978
+ for (const f of fields) {
6979
+ const sourceKey = FIELD_ALIAS[f] ?? f;
6980
+ out[f] = record[sourceKey];
6981
+ }
6982
+ return out;
6983
+ }
6984
+ async function listPeersForMesh(slug) {
6985
+ const config = readConfig();
6986
+ const joined = config.meshes.find((m) => m.slug === slug);
6987
+ const selfMemberPubkey = joined?.pubkey ?? null;
6988
+ try {
6989
+ const { tryListPeersViaDaemon: tryListPeersViaDaemon2 } = await Promise.resolve().then(() => (init_daemon_route(), exports_daemon_route));
6990
+ const dr = await tryListPeersViaDaemon2();
6991
+ if (dr !== null) {
6992
+ return dr.map((p) => annotateSelf(p, selfMemberPubkey, null));
6993
+ }
6994
+ } catch {}
6995
+ const bridged = await tryBridge(slug, "peers");
6996
+ if (bridged && bridged.ok) {
6997
+ const peers = bridged.result;
6998
+ return peers.map((p) => annotateSelf(p, selfMemberPubkey, null));
6999
+ }
7000
+ let result = [];
7001
+ await withMesh({ meshSlug: slug }, async (client) => {
7002
+ const all = await client.listPeers();
7003
+ const selfSessionPubkey = client.getSessionPubkey();
7004
+ result = all.map((p) => annotateSelf(p, selfMemberPubkey, selfSessionPubkey));
7005
+ });
7006
+ return result;
7007
+ }
7008
+ function annotateSelf(peer, selfMemberPubkey, selfSessionPubkey) {
7009
+ const isSelf = !!(selfMemberPubkey && peer.memberPubkey && peer.memberPubkey === selfMemberPubkey);
7010
+ const isThisSession = !!(isSelf && selfSessionPubkey && peer.pubkey === selfSessionPubkey);
7011
+ return { ...peer, isSelf, isThisSession };
7012
+ }
7013
+ async function runPeers(flags) {
7014
+ const config = readConfig();
7015
+ const slugs = flags.mesh ? [flags.mesh] : config.meshes.map((m) => m.slug);
7016
+ if (slugs.length === 0) {
7017
+ render.err("No meshes joined.");
7018
+ render.hint("claudemesh <invite-url> # join + launch");
7019
+ process.exit(1);
7020
+ }
7021
+ const fieldList = typeof flags.json === "string" && flags.json.length > 0 ? flags.json.split(",").map((s) => s.trim()).filter(Boolean) : null;
7022
+ const wantsJson = flags.json !== undefined && flags.json !== false;
7023
+ const allJson = [];
7024
+ for (const slug of slugs) {
7025
+ try {
7026
+ const peers = await listPeersForMesh(slug);
7027
+ if (wantsJson) {
7028
+ const projected = fieldList ? peers.map((p) => projectFields(p, fieldList)) : peers;
7029
+ allJson.push({ mesh: slug, peers: projected });
7030
+ continue;
7031
+ }
7032
+ render.section(`peers on ${slug} (${peers.length})`);
7033
+ if (peers.length === 0) {
7034
+ render.info(dim(" (no peers connected)"));
7035
+ continue;
7036
+ }
7037
+ for (const p of peers) {
7038
+ const groups = p.groups.length ? " [" + p.groups.map((g) => `@${g.name}${g.role ? `:${g.role}` : ""}`).join(", ") + "]" : "";
7039
+ const statusDot = p.status === "working" ? yellow("●") : green("●");
7040
+ const name = bold(p.displayName);
7041
+ const meta = [];
7042
+ if (p.peerType)
7043
+ meta.push(p.peerType);
7044
+ if (p.channel)
7045
+ meta.push(p.channel);
7046
+ if (p.model)
7047
+ meta.push(p.model);
7048
+ const metaStr = meta.length ? dim(` (${meta.join(", ")})`) : "";
7049
+ const summary = p.summary ? dim(` — ${p.summary}`) : "";
7050
+ const pubkeyTag = dim(` · ${p.pubkey.slice(0, 16)}…`);
7051
+ const selfTag = p.isThisSession ? dim(" ") + yellow("(this session)") : p.isSelf ? dim(" ") + yellow("(your other session)") : "";
7052
+ render.info(`${statusDot} ${name}${selfTag}${groups}${metaStr}${pubkeyTag}${summary}`);
7053
+ if (p.cwd)
7054
+ render.info(dim(` cwd: ${p.cwd}`));
7055
+ }
7056
+ } catch (e) {
7057
+ render.err(`${slug}: ${e instanceof Error ? e.message : String(e)}`);
7058
+ }
7059
+ }
7060
+ if (wantsJson) {
7061
+ process.stdout.write(JSON.stringify(slugs.length === 1 ? allJson[0]?.peers : allJson, null, 2) + `
7062
+ `);
7063
+ }
7064
+ }
7065
+ var FIELD_ALIAS;
7066
+ var init_peers = __esm(() => {
7067
+ init_connect();
7068
+ init_facade();
7069
+ init_client3();
7070
+ init_render();
7071
+ init_styles();
7072
+ FIELD_ALIAS = {
7073
+ name: "displayName"
7074
+ };
7075
+ });
7076
+
7010
7077
  // src/commands/send.ts
7011
7078
  var exports_send = {};
7012
7079
  __export(exports_send, {
@@ -8441,12 +8508,32 @@ function migrateOutbox(db) {
8441
8508
  CREATE INDEX IF NOT EXISTS outbox_aborted
8442
8509
  ON outbox(status, aborted_at) WHERE status = 'aborted';
8443
8510
  `);
8511
+ const hasMesh = columnExists(db, "outbox", "mesh");
8512
+ const hasTargetSpec = columnExists(db, "outbox", "target_spec");
8513
+ const hasNonce = columnExists(db, "outbox", "nonce");
8514
+ const hasCiphertext = columnExists(db, "outbox", "ciphertext");
8515
+ const hasPriority = columnExists(db, "outbox", "priority");
8516
+ if (!hasMesh)
8517
+ db.exec(`ALTER TABLE outbox ADD COLUMN mesh TEXT`);
8518
+ if (!hasTargetSpec)
8519
+ db.exec(`ALTER TABLE outbox ADD COLUMN target_spec TEXT`);
8520
+ if (!hasNonce)
8521
+ db.exec(`ALTER TABLE outbox ADD COLUMN nonce TEXT`);
8522
+ if (!hasCiphertext)
8523
+ db.exec(`ALTER TABLE outbox ADD COLUMN ciphertext TEXT`);
8524
+ if (!hasPriority)
8525
+ db.exec(`ALTER TABLE outbox ADD COLUMN priority TEXT`);
8526
+ }
8527
+ function columnExists(db, table, column) {
8528
+ const rows = db.prepare(`PRAGMA table_info(${table})`).all();
8529
+ return rows.some((r) => r.name === column);
8444
8530
  }
8445
8531
  function findByClientId(db, clientMessageId) {
8446
8532
  const row = db.prepare(`
8447
8533
  SELECT id, client_message_id, request_fingerprint, payload, enqueued_at,
8448
8534
  attempts, next_attempt_at, status, last_error, delivered_at,
8449
- broker_message_id, aborted_at, aborted_by, superseded_by
8535
+ broker_message_id, aborted_at, aborted_by, superseded_by,
8536
+ mesh, target_spec, nonce, ciphertext, priority
8450
8537
  FROM outbox WHERE client_message_id = ?
8451
8538
  `).get(clientMessageId);
8452
8539
  return row ?? null;
@@ -8455,9 +8542,10 @@ function insertPending(db, input) {
8455
8542
  db.prepare(`
8456
8543
  INSERT INTO outbox (
8457
8544
  id, client_message_id, request_fingerprint, payload,
8458
- enqueued_at, attempts, next_attempt_at, status
8459
- ) VALUES (?, ?, ?, ?, ?, 0, ?, 'pending')
8460
- `).run(input.id, input.client_message_id, input.request_fingerprint, input.payload, input.now, input.now);
8545
+ enqueued_at, attempts, next_attempt_at, status,
8546
+ mesh, target_spec, nonce, ciphertext, priority
8547
+ ) VALUES (?, ?, ?, ?, ?, 0, ?, 'pending', ?, ?, ?, ?, ?)
8548
+ `).run(input.id, input.client_message_id, input.request_fingerprint, input.payload, input.now, input.now, input.mesh ?? null, input.target_spec ?? null, input.nonce ?? null, input.ciphertext ?? null, input.priority ?? null);
8461
8549
  }
8462
8550
  function fingerprintsEqual(a, b) {
8463
8551
  if (a.length !== b.length)
@@ -8477,7 +8565,8 @@ function listOutbox(db, p = {}) {
8477
8565
  const sql = `
8478
8566
  SELECT id, client_message_id, request_fingerprint, payload, enqueued_at,
8479
8567
  attempts, next_attempt_at, status, last_error, delivered_at,
8480
- broker_message_id, aborted_at, aborted_by, superseded_by
8568
+ broker_message_id, aborted_at, aborted_by, superseded_by,
8569
+ mesh, target_spec, nonce, ciphertext, priority
8481
8570
  FROM outbox
8482
8571
  ${where.length ? "WHERE " + where.join(" AND ") : ""}
8483
8572
  ORDER BY enqueued_at DESC
@@ -8490,7 +8579,8 @@ function findById(db, id) {
8490
8579
  return db.prepare(`
8491
8580
  SELECT id, client_message_id, request_fingerprint, payload, enqueued_at,
8492
8581
  attempts, next_attempt_at, status, last_error, delivered_at,
8493
- broker_message_id, aborted_at, aborted_by, superseded_by
8582
+ broker_message_id, aborted_at, aborted_by, superseded_by,
8583
+ mesh, target_spec, nonce, ciphertext, priority
8494
8584
  FROM outbox WHERE id = ?
8495
8585
  `).get(id) ?? null;
8496
8586
  }
@@ -8633,7 +8723,12 @@ function acceptSend(req, deps) {
8633
8723
  client_message_id: clientId,
8634
8724
  request_fingerprint: fingerprint,
8635
8725
  payload: body,
8636
- now
8726
+ now,
8727
+ mesh: req.mesh,
8728
+ target_spec: req.target_spec,
8729
+ nonce: req.nonce,
8730
+ ciphertext: req.ciphertext,
8731
+ priority: req.priority
8637
8732
  });
8638
8733
  return { kind: "accepted_pending", status: 202, client_message_id: clientId };
8639
8734
  }
@@ -8814,7 +8909,9 @@ function startIpcServer(opts) {
8814
8909
  inboxDb: opts.inboxDb,
8815
8910
  bus: opts.bus,
8816
8911
  broker: opts.broker,
8817
- onPendingInserted: opts.onPendingInserted
8912
+ onPendingInserted: opts.onPendingInserted,
8913
+ meshSecretKey: opts.meshSecretKey,
8914
+ meshSlug: opts.meshSlug
8818
8915
  });
8819
8916
  if (existsSync9(DAEMON_PATHS.SOCK_FILE)) {
8820
8917
  try {
@@ -9120,6 +9217,18 @@ function makeHandler(opts) {
9120
9217
  respond(res, 400, { error: parsed.error });
9121
9218
  return;
9122
9219
  }
9220
+ if (opts.broker && opts.meshSecretKey) {
9221
+ try {
9222
+ const routed = await resolveAndEncrypt(parsed.req, opts.broker, opts.meshSecretKey, opts.meshSlug ?? null);
9223
+ parsed.req.target_spec = routed.target_spec;
9224
+ parsed.req.ciphertext = routed.ciphertext;
9225
+ parsed.req.nonce = routed.nonce;
9226
+ parsed.req.mesh = routed.mesh;
9227
+ } catch (e) {
9228
+ respond(res, 502, { error: "route_failed", detail: String(e) });
9229
+ return;
9230
+ }
9231
+ }
9123
9232
  const outcome = acceptSend(parsed.req, { db: opts.outboxDb });
9124
9233
  switch (outcome.kind) {
9125
9234
  case "accepted_pending":
@@ -9225,6 +9334,48 @@ function parseSendRequest(body, idempotencyHeader) {
9225
9334
  }
9226
9335
  };
9227
9336
  }
9337
+ async function resolveAndEncrypt(req, broker, meshSecretKey, meshSlug) {
9338
+ const { encryptDirect: encryptDirect2 } = await Promise.resolve().then(() => (init_box(), exports_box));
9339
+ const { randomBytes: randomBytes5 } = await import("node:crypto");
9340
+ const to = req.to.trim();
9341
+ if (to.startsWith("#") && /^#[0-9a-z_-]{20,}$/i.test(to)) {
9342
+ const ciphertext = Buffer.from(req.message, "utf8").toString("base64");
9343
+ const nonce = randomBytes5(24).toString("base64");
9344
+ return { target_spec: to, ciphertext, nonce, mesh: meshSlug ?? "" };
9345
+ }
9346
+ if (to.startsWith("@") || to === "*") {
9347
+ const ciphertext = Buffer.from(req.message, "utf8").toString("base64");
9348
+ const nonce = randomBytes5(24).toString("base64");
9349
+ return { target_spec: to, ciphertext, nonce, mesh: meshSlug ?? "" };
9350
+ }
9351
+ if (/^[0-9a-f]{64}$/i.test(to)) {
9352
+ const sessionKeys2 = broker.getSessionKeys();
9353
+ const senderSecret2 = sessionKeys2?.sessionSecretKey ?? meshSecretKey;
9354
+ const env3 = await encryptDirect2(req.message, to, senderSecret2);
9355
+ return { target_spec: to, ciphertext: env3.ciphertext, nonce: env3.nonce, mesh: meshSlug ?? "" };
9356
+ }
9357
+ const peers = await broker.listPeers().catch(() => []);
9358
+ if (/^[0-9a-f]{16,63}$/i.test(to)) {
9359
+ const matches2 = peers.filter((p) => p.pubkey.toLowerCase().startsWith(to.toLowerCase()) || (p.memberPubkey ?? "").toLowerCase().startsWith(to.toLowerCase()));
9360
+ if (matches2.length === 0)
9361
+ throw new Error(`no peer matching prefix "${to}"`);
9362
+ if (matches2.length > 1)
9363
+ throw new Error(`prefix "${to}" is ambiguous (${matches2.length} matches)`);
9364
+ const recipient2 = matches2[0].pubkey;
9365
+ const sessionKeys2 = broker.getSessionKeys();
9366
+ const senderSecret2 = sessionKeys2?.sessionSecretKey ?? meshSecretKey;
9367
+ const env3 = await encryptDirect2(req.message, recipient2, senderSecret2);
9368
+ return { target_spec: recipient2, ciphertext: env3.ciphertext, nonce: env3.nonce, mesh: meshSlug ?? "" };
9369
+ }
9370
+ const match = peers.find((p) => p.displayName.toLowerCase() === to.toLowerCase());
9371
+ if (!match)
9372
+ throw new Error(`peer "${to}" not found`);
9373
+ const recipient = match.pubkey;
9374
+ const sessionKeys = broker.getSessionKeys();
9375
+ const senderSecret = sessionKeys?.sessionSecretKey ?? meshSecretKey;
9376
+ const env2 = await encryptDirect2(req.message, recipient, senderSecret);
9377
+ return { target_spec: recipient, ciphertext: env2.ciphertext, nonce: env2.nonce, mesh: meshSlug ?? "" };
9378
+ }
9228
9379
  function respond(res, status, body) {
9229
9380
  const json = JSON.stringify(body);
9230
9381
  res.statusCode = status;
@@ -9632,7 +9783,8 @@ function startDrainWorker(opts) {
9632
9783
  async function drainOnce(opts, log2) {
9633
9784
  const now = Date.now();
9634
9785
  const rows = opts.db.prepare(`
9635
- SELECT id, client_message_id, request_fingerprint, payload, attempts
9786
+ SELECT id, client_message_id, request_fingerprint, payload, attempts,
9787
+ target_spec, nonce, ciphertext, priority, mesh
9636
9788
  FROM outbox
9637
9789
  WHERE status = 'pending' AND next_attempt_at <= ?
9638
9790
  ORDER BY enqueued_at
@@ -9644,15 +9796,26 @@ async function drainOnce(opts, log2) {
9644
9796
  if (markInflight(opts.db, row.id, now) === 0)
9645
9797
  continue;
9646
9798
  const fpHex = bufferToHex(row.request_fingerprint);
9647
- const sessionKeys = opts.broker.getSessionKeys();
9648
- const targetSpec = "*";
9649
- const nonce = await randomNonce2();
9650
- const ciphertext = Buffer.from(row.payload).toString("base64");
9799
+ let targetSpec;
9800
+ let nonce;
9801
+ let ciphertext;
9802
+ let priority;
9803
+ if (row.target_spec && row.nonce && row.ciphertext) {
9804
+ targetSpec = row.target_spec;
9805
+ nonce = row.nonce;
9806
+ ciphertext = row.ciphertext;
9807
+ priority = row.priority === "now" || row.priority === "low" ? row.priority : "next";
9808
+ } else {
9809
+ targetSpec = "*";
9810
+ nonce = await randomNonce2();
9811
+ ciphertext = Buffer.from(row.payload).toString("base64");
9812
+ priority = "next";
9813
+ }
9651
9814
  let res;
9652
9815
  try {
9653
9816
  res = await opts.broker.send({
9654
9817
  targetSpec,
9655
- priority: "next",
9818
+ priority,
9656
9819
  nonce,
9657
9820
  ciphertext,
9658
9821
  client_message_id: row.client_message_id,
@@ -10066,7 +10229,9 @@ async function runDaemon(opts = {}) {
10066
10229
  inboxDb,
10067
10230
  bus,
10068
10231
  broker,
10069
- onPendingInserted: () => drain?.wake()
10232
+ onPendingInserted: () => drain?.wake(),
10233
+ meshSecretKey: mesh.secretKey,
10234
+ meshSlug: mesh.slug
10070
10235
  });
10071
10236
  try {
10072
10237
  await ipc2.ready;
@@ -13263,6 +13428,32 @@ async function runSqlSchema(opts) {
13263
13428
  });
13264
13429
  }
13265
13430
  async function runSkillList(opts) {
13431
+ try {
13432
+ const { tryListSkillsViaDaemon: tryListSkillsViaDaemon2 } = await Promise.resolve().then(() => (init_daemon_route(), exports_daemon_route));
13433
+ const dr = await tryListSkillsViaDaemon2();
13434
+ if (dr !== null) {
13435
+ const skills = dr;
13436
+ if (opts.json) {
13437
+ emitJson(skills);
13438
+ return EXIT.SUCCESS;
13439
+ }
13440
+ if (skills.length === 0) {
13441
+ render.info(dim("(no skills)"));
13442
+ return EXIT.SUCCESS;
13443
+ }
13444
+ render.section(`mesh skills (${skills.length})`);
13445
+ for (const s of skills) {
13446
+ process.stdout.write(` ${bold(s.name)} ${dim("· by " + s.author)}
13447
+ `);
13448
+ process.stdout.write(` ${s.description}
13449
+ `);
13450
+ if (s.tags?.length)
13451
+ process.stdout.write(` ${dim("tags: " + s.tags.join(", "))}
13452
+ `);
13453
+ }
13454
+ return EXIT.SUCCESS;
13455
+ }
13456
+ } catch {}
13266
13457
  return await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
13267
13458
  const skills = await client.listSkills(opts.query);
13268
13459
  if (opts.json) {
@@ -13291,6 +13482,29 @@ async function runSkillGet(name, opts) {
13291
13482
  render.err("Usage: claudemesh skill get <name>");
13292
13483
  return EXIT.INVALID_ARGS;
13293
13484
  }
13485
+ try {
13486
+ const { tryGetSkillViaDaemon: tryGetSkillViaDaemon2 } = await Promise.resolve().then(() => (init_daemon_route(), exports_daemon_route));
13487
+ const dr = await tryGetSkillViaDaemon2(name);
13488
+ if (dr !== null) {
13489
+ const skill = dr;
13490
+ if (opts.json) {
13491
+ emitJson(skill);
13492
+ return EXIT.SUCCESS;
13493
+ }
13494
+ render.section(skill.name);
13495
+ render.kv([
13496
+ ["author", skill.author],
13497
+ ["created", skill.createdAt],
13498
+ ["tags", skill.tags?.join(", ") || dim("(none)")]
13499
+ ]);
13500
+ render.blank();
13501
+ render.info(skill.description);
13502
+ render.blank();
13503
+ process.stdout.write(skill.instructions + `
13504
+ `);
13505
+ return EXIT.SUCCESS;
13506
+ }
13507
+ } catch {}
13294
13508
  return await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
13295
13509
  const skill = await client.getSkill(name);
13296
13510
  if (!skill) {
@@ -13808,7 +14022,7 @@ claudemesh send "<from_name>" "..." --mesh "<mesh_slug>"
13808
14022
 
13809
14023
  If the parent Claude session was launched via \`claudemesh launch\`, an MCP push-pipe is running and holds the per-mesh WS connection. CLI invocations dial \`~/.claudemesh/sockets/<mesh-slug>.sock\` and reuse that warm connection (~200ms total round-trip including Node.js startup). If no push-pipe is running (cron, scripts, hooks fired outside a session), the CLI opens its own WS, which takes ~500-700ms cold. **You don't manage this** — every verb auto-detects and falls through.
13810
14024
 
13811
- ### Daemon path (v0.9.0, opt-in, fastest)
14025
+ ### Daemon path (v1.24.0+, REQUIRED for in-Claude-Code use)
13812
14026
 
13813
14027
  \`claudemesh daemon up [--mesh <slug>]\` starts a persistent per-user runtime that holds the broker WS, a durable SQLite outbox/inbox, and listens on \`~/.claudemesh/daemon/daemon.sock\` (UDS) plus an optional loopback TCP. When the daemon socket is present, every verb routes through it first (~1ms IPC) before falling back to bridge / cold paths. The send envelope carries a caller-stable \`client_message_id\`, so a \`claudemesh send\` that started before a daemon crash survives the restart via the on-disk outbox.
13814
14028
 
@@ -13823,11 +14037,15 @@ claudemesh daemon outbox requeue <id> # re-enqueue an aborted/d
13823
14037
  claudemesh daemon down # SIGTERM + wait
13824
14038
  \`\`\`
13825
14039
 
13826
- \`claudemesh install\` (MCP + hooks registration) and the daemon are independent install does not start the daemon, and the daemon does not require install. Run both for the warmest path: install gives you the in-session push-pipe, daemon gives you cross-invocation persistence and a survivable outbox.
14040
+ As of 1.24.0 \`claudemesh install\` registers the MCP entry **and** installs/starts the daemon service for the user's primary mesh. The MCP shim hard-requires the daemon to be running — it bails at boot with actionable instructions if the socket isn't present. There is no fallback. CLI verbs (\`send\`, \`peer list\`, \`inbox\`, \`skill list/get\`, etc.) keep working without a daemon via bridge or cold paths, but for any in-Claude-Code use the daemon must be up.
14041
+
14042
+ ### Ambient mode (1.25.0+)
14043
+
14044
+ Once \`claudemesh install\` has run (registers MCP entry + starts daemon service), **raw \`claude\` Just Works** for the daemon's attached mesh. No \`claudemesh launch\` ceremony, no manual flags, no per-session keypair. Channel push, slash commands, and resources all flow through the daemon-backed MCP shim. Use \`claudemesh launch\` only when you need to override defaults (different mesh, custom display name, system-prompt injection, headless modes).
13827
14045
 
13828
14046
  ## Spawning new sessions (no wizard)
13829
14047
 
13830
- \`claudemesh launch\` is the canonical way to start a new Claude Code session connected to claudemesh. Pass every required flag up front so no interactive prompt fires — that's what makes the verb scriptable from tmux send-keys, AppleScript/iTerm spawn helpers, hooks, cron, and the \`claudemesh launch\` you call from inside another session. **Always use this verb, never \`claude\` directly with hand-rolled flags** — it sets up the per-session ed25519 keypair, exports \`CLAUDEMESH_DISPLAY_NAME\`, isolates the mesh config in a tmpdir, and passes the \`--dangerously-load-development-channels server:claudemesh\` plumbing that the MCP push-pipe needs.
14048
+ \`claudemesh launch\` remains useful for non-default cases: explicit mesh selection, fresh display name, headless \`--quiet\` runs, system-prompt injection, multi-mesh users with one daemon attached to mesh A who want to spawn into mesh B. For the common case (single joined mesh, daemon installed), prefer raw \`claude\`. Pass every required flag up front so no interactive prompt fires — that's what makes the verb scriptable from tmux send-keys, AppleScript/iTerm spawn helpers, hooks, cron, and the \`claudemesh launch\` you call from inside another session.
13831
14049
 
13832
14050
  ### Full flag surface
13833
14051
 
@@ -18376,4 +18594,4 @@ main().catch((err) => {
18376
18594
  process.exit(EXIT.INTERNAL_ERROR);
18377
18595
  });
18378
18596
 
18379
- //# debugId=BAEC2C4FE7977E7264756E2164756E21
18597
+ //# debugId=EC115E4068A7B5F664756E2164756E21