claudemesh-cli 1.23.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.
@@ -4,6 +4,7 @@ var __create = Object.create;
4
4
  var __getProtoOf = Object.getPrototypeOf;
5
5
  var __defProp = Object.defineProperty;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
9
  var __toESM = (mod, isNodeMode, target) => {
9
10
  target = mod != null ? __create(__getProtoOf(mod)) : {};
@@ -16,6 +17,20 @@ var __toESM = (mod, isNodeMode, target) => {
16
17
  });
17
18
  return to;
18
19
  };
20
+ var __moduleCache = /* @__PURE__ */ new WeakMap;
21
+ var __toCommonJS = (from) => {
22
+ var entry = __moduleCache.get(from), desc;
23
+ if (entry)
24
+ return entry;
25
+ entry = __defProp({}, "__esModule", { value: true });
26
+ if (from && typeof from === "object" || typeof from === "function")
27
+ __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
28
+ get: () => from[key],
29
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
30
+ }));
31
+ __moduleCache.set(from, entry);
32
+ return entry;
33
+ };
19
34
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
20
35
  var __export = (target, all) => {
21
36
  for (var name in all)
@@ -88,7 +103,7 @@ __export(exports_urls, {
88
103
  VERSION: () => VERSION,
89
104
  URLS: () => URLS
90
105
  });
91
- var URLS, VERSION = "1.23.0", env;
106
+ var URLS, VERSION = "1.25.0", env;
92
107
  var init_urls = __esm(() => {
93
108
  URLS = {
94
109
  BROKER: process.env.CLAUDEMESH_BROKER_URL ?? "wss://ic.claudemesh.com/ws",
@@ -1011,6 +1026,12 @@ var init_file_crypto = __esm(() => {
1011
1026
  });
1012
1027
 
1013
1028
  // src/services/crypto/box.ts
1029
+ var exports_box = {};
1030
+ __export(exports_box, {
1031
+ isDirectTarget: () => isDirectTarget,
1032
+ encryptDirect: () => encryptDirect,
1033
+ decryptDirect: () => decryptDirect
1034
+ });
1014
1035
  function isDirectTarget(targetSpec) {
1015
1036
  return HEX_PUBKEY.test(targetSpec);
1016
1037
  }
@@ -3308,44 +3329,10 @@ var init_ws_client = __esm(() => {
3308
3329
  });
3309
3330
 
3310
3331
  // src/services/broker/manager.ts
3311
- async function ensureClient(mesh) {
3312
- const existing = clients.get(mesh.meshId);
3313
- if (existing)
3314
- return existing;
3315
- const isDebug2 = process.env.CLAUDEMESH_DEBUG === "1" || process.env.CLAUDEMESH_DEBUG === "true";
3316
- const client = new BrokerClient(mesh, { debug: isDebug2, displayName: configDisplayName });
3317
- clients.set(mesh.meshId, client);
3318
- try {
3319
- await client.connect();
3320
- for (const g of configGroups ?? []) {
3321
- try {
3322
- await client.joinGroup(g.name, g.role);
3323
- } catch {}
3324
- }
3325
- } catch (err) {
3326
- process.stderr.write(`[claudemesh] broker connect failed for ${mesh.slug}: ${err instanceof Error ? err.message : err} (will retry)
3327
- `);
3328
- }
3329
- return client;
3330
- }
3331
- async function startClients(config) {
3332
- configDisplayName = config.displayName;
3333
- configGroups = config.groups ?? [];
3334
- await Promise.allSettled(config.meshes.map(ensureClient));
3335
- }
3336
- function allClients() {
3337
- return [...clients.values()];
3338
- }
3339
- function stopAll() {
3340
- for (const c of clients.values())
3341
- c.close();
3342
- clients.clear();
3343
- }
3344
- var clients, configDisplayName, configGroups;
3332
+ var clients;
3345
3333
  var init_manager = __esm(() => {
3346
3334
  init_ws_client();
3347
3335
  clients = new Map;
3348
- configGroups = [];
3349
3336
  });
3350
3337
 
3351
3338
  // src/services/broker/envelope.ts
@@ -3600,6 +3587,42 @@ var init_spinner = __esm(() => {
3600
3587
  FRAME_HEIGHT = H;
3601
3588
  });
3602
3589
 
3590
+ // src/daemon/paths.ts
3591
+ var exports_paths = {};
3592
+ __export(exports_paths, {
3593
+ DAEMON_TCP_HOST: () => DAEMON_TCP_HOST,
3594
+ DAEMON_TCP_DEFAULT_PORT: () => DAEMON_TCP_DEFAULT_PORT,
3595
+ DAEMON_PATHS: () => DAEMON_PATHS
3596
+ });
3597
+ import { join as join3 } from "node:path";
3598
+ var DAEMON_PATHS, DAEMON_TCP_HOST = "127.0.0.1", DAEMON_TCP_DEFAULT_PORT = 47823;
3599
+ var init_paths2 = __esm(() => {
3600
+ init_paths();
3601
+ DAEMON_PATHS = {
3602
+ get DAEMON_DIR() {
3603
+ return join3(PATHS.CONFIG_DIR, "daemon");
3604
+ },
3605
+ get PID_FILE() {
3606
+ return join3(this.DAEMON_DIR, "daemon.pid");
3607
+ },
3608
+ get SOCK_FILE() {
3609
+ return join3(this.DAEMON_DIR, "daemon.sock");
3610
+ },
3611
+ get TOKEN_FILE() {
3612
+ return join3(this.DAEMON_DIR, "local-token");
3613
+ },
3614
+ get OUTBOX_DB() {
3615
+ return join3(this.DAEMON_DIR, "outbox.db");
3616
+ },
3617
+ get INBOX_DB() {
3618
+ return join3(this.DAEMON_DIR, "inbox.db");
3619
+ },
3620
+ get LOG_FILE() {
3621
+ return join3(this.DAEMON_DIR, "daemon.log");
3622
+ }
3623
+ };
3624
+ });
3625
+
3603
3626
  // src/commands/launch.ts
3604
3627
  var exports_launch = {};
3605
3628
  __export(exports_launch, {
@@ -3609,8 +3632,41 @@ import { spawnSync as spawnSync2 } from "node:child_process";
3609
3632
  import { randomUUID } from "node:crypto";
3610
3633
  import { mkdtempSync, writeFileSync as writeFileSync4, rmSync, readdirSync, statSync, existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
3611
3634
  import { tmpdir, hostname as hostname2, homedir as homedir3 } from "node:os";
3612
- import { join as join3 } from "node:path";
3635
+ import { join as join4 } from "node:path";
3613
3636
  import { createInterface as createInterface4 } from "node:readline";
3637
+ async function ensureDaemonRunning(meshSlug, quiet) {
3638
+ const { DAEMON_PATHS: DAEMON_PATHS2 } = await Promise.resolve().then(() => (init_paths2(), exports_paths));
3639
+ if (existsSync5(DAEMON_PATHS2.SOCK_FILE))
3640
+ return;
3641
+ if (!quiet)
3642
+ render.info("starting claudemesh daemon…");
3643
+ const { spawn } = await import("node:child_process");
3644
+ const argv0 = process.argv[1] ?? "claudemesh";
3645
+ let binary = argv0;
3646
+ if (/\.ts$/.test(binary) || /node_modules|src\/entrypoints/.test(binary)) {
3647
+ try {
3648
+ const { execSync } = await import("node:child_process");
3649
+ binary = execSync("which claudemesh", { encoding: "utf8" }).trim();
3650
+ } catch {
3651
+ binary = "claudemesh";
3652
+ }
3653
+ }
3654
+ const child = spawn(binary, ["daemon", "up", "--mesh", meshSlug], {
3655
+ detached: true,
3656
+ stdio: "ignore"
3657
+ });
3658
+ child.unref();
3659
+ const start = Date.now();
3660
+ while (Date.now() - start < 1e4) {
3661
+ if (existsSync5(DAEMON_PATHS2.SOCK_FILE)) {
3662
+ if (!quiet)
3663
+ render.ok("daemon ready");
3664
+ return;
3665
+ }
3666
+ await new Promise((r) => setTimeout(r, 200));
3667
+ }
3668
+ render.warn("daemon failed to start within 10s", "Run `claudemesh daemon up --mesh " + meshSlug + "` manually, then re-launch.");
3669
+ }
3614
3670
  function parseGroupsString(raw) {
3615
3671
  return raw.split(",").map((s) => s.trim()).filter(Boolean).map((token) => {
3616
3672
  const idx = token.indexOf(":");
@@ -3930,14 +3986,15 @@ async function runLaunch(flags, rawArgs) {
3930
3986
  for (const entry of readdirSync(tmpBase)) {
3931
3987
  if (!entry.startsWith("claudemesh-"))
3932
3988
  continue;
3933
- const full = join3(tmpBase, entry);
3989
+ const full = join4(tmpBase, entry);
3934
3990
  const age = Date.now() - statSync(full).mtimeMs;
3935
3991
  if (age > 3600000)
3936
3992
  rmSync(full, { recursive: true, force: true });
3937
3993
  }
3938
3994
  } catch {}
3995
+ await ensureDaemonRunning(mesh.slug, args.quiet);
3939
3996
  try {
3940
- const claudeConfigPath = join3(homedir3(), ".claude.json");
3997
+ const claudeConfigPath = join4(homedir3(), ".claude.json");
3941
3998
  if (existsSync5(claudeConfigPath)) {
3942
3999
  const claudeConfig = JSON.parse(readFileSync4(claudeConfigPath, "utf-8"));
3943
4000
  const mcpServers = claudeConfig.mcpServers ?? {};
@@ -3974,7 +4031,7 @@ async function runLaunch(flags, rawArgs) {
3974
4031
  console.log(" (Could not fetch service catalog — mesh services won't be natively available)");
3975
4032
  }
3976
4033
  }
3977
- const tmpDir = mkdtempSync(join3(tmpdir(), "claudemesh-"));
4034
+ const tmpDir = mkdtempSync(join4(tmpdir(), "claudemesh-"));
3978
4035
  const sessionConfig = {
3979
4036
  version: 1,
3980
4037
  meshes: [mesh],
@@ -3983,14 +4040,14 @@ async function runLaunch(flags, rawArgs) {
3983
4040
  ...parsedGroups.length > 0 ? { groups: parsedGroups } : {},
3984
4041
  messageMode
3985
4042
  };
3986
- writeFileSync4(join3(tmpDir, "config.json"), JSON.stringify(sessionConfig, null, 2) + `
4043
+ writeFileSync4(join4(tmpDir, "config.json"), JSON.stringify(sessionConfig, null, 2) + `
3987
4044
  `, "utf-8");
3988
4045
  if (!args.quiet) {
3989
4046
  printBanner(displayName, mesh.slug, role, parsedGroups, messageMode);
3990
4047
  }
3991
4048
  const meshMcpEntries = [];
3992
4049
  if (serviceCatalog.length > 0) {
3993
- const claudeConfigPath = join3(homedir3(), ".claude.json");
4050
+ const claudeConfigPath = join4(homedir3(), ".claude.json");
3994
4051
  let claudeConfig = {};
3995
4052
  try {
3996
4053
  claudeConfig = JSON.parse(readFileSync4(claudeConfigPath, "utf-8"));
@@ -4057,9 +4114,9 @@ async function runLaunch(flags, rawArgs) {
4057
4114
  let claudeBin = "claude";
4058
4115
  if (!isWindows2) {
4059
4116
  const candidates = [
4060
- join3(homedir3(), ".local", "bin", "claude"),
4117
+ join4(homedir3(), ".local", "bin", "claude"),
4061
4118
  "/usr/local/bin/claude",
4062
- join3(homedir3(), ".claude", "bin", "claude")
4119
+ join4(homedir3(), ".claude", "bin", "claude")
4063
4120
  ];
4064
4121
  for (const c of candidates) {
4065
4122
  if (existsSync5(c)) {
@@ -4071,7 +4128,7 @@ async function runLaunch(flags, rawArgs) {
4071
4128
  const cleanup = () => {
4072
4129
  if (meshMcpEntries.length > 0) {
4073
4130
  try {
4074
- const claudeConfigPath = join3(homedir3(), ".claude.json");
4131
+ const claudeConfigPath = join4(homedir3(), ".claude.json");
4075
4132
  const claudeConfig = JSON.parse(readFileSync4(claudeConfigPath, "utf-8"));
4076
4133
  const mcpServers = claudeConfig.mcpServers ?? {};
4077
4134
  for (const { key } of meshMcpEntries) {
@@ -4776,7 +4833,7 @@ __export(exports_join, {
4776
4833
  });
4777
4834
  import sodium3 from "libsodium-wrappers";
4778
4835
  import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync3 } from "node:fs";
4779
- import { join as join4, dirname as dirname2 } from "node:path";
4836
+ import { join as join5, dirname as dirname2 } from "node:path";
4780
4837
  import { homedir as homedir4, hostname as hostname3 } from "node:os";
4781
4838
  function deriveAppBaseUrl() {
4782
4839
  const override = process.env.CLAUDEMESH_APP_URL;
@@ -4896,8 +4953,8 @@ async function runJoin(args) {
4896
4953
  joinedAt: new Date().toISOString()
4897
4954
  });
4898
4955
  writeConfig(config);
4899
- const configDir = env.CLAUDEMESH_CONFIG_DIR ?? join4(homedir4(), ".claudemesh");
4900
- const inviteFile = join4(configDir, `invite-${payload.mesh_slug}.txt`);
4956
+ const configDir = env.CLAUDEMESH_CONFIG_DIR ?? join5(homedir4(), ".claudemesh");
4957
+ const inviteFile = join5(configDir, `invite-${payload.mesh_slug}.txt`);
4901
4958
  try {
4902
4959
  mkdirSync3(dirname2(inviteFile), { recursive: true });
4903
4960
  writeFileSync5(inviteFile, link, "utf-8");
@@ -6640,12 +6697,9 @@ var init_ban = __esm(() => {
6640
6697
 
6641
6698
  // src/services/bridge/protocol.ts
6642
6699
  import { homedir as homedir5 } from "node:os";
6643
- import { join as join5 } from "node:path";
6700
+ import { join as join6 } from "node:path";
6644
6701
  function socketPath(meshSlug) {
6645
- return join5(homedir5(), ".claudemesh", "sockets", `${meshSlug}.sock`);
6646
- }
6647
- function socketDir() {
6648
- return join5(homedir5(), ".claudemesh", "sockets");
6702
+ return join6(homedir5(), ".claudemesh", "sockets", `${meshSlug}.sock`);
6649
6703
  }
6650
6704
  function frame(obj) {
6651
6705
  return JSON.stringify(obj) + `
@@ -6742,135 +6796,6 @@ var init_client3 = __esm(() => {
6742
6796
  init_protocol();
6743
6797
  });
6744
6798
 
6745
- // src/commands/peers.ts
6746
- var exports_peers = {};
6747
- __export(exports_peers, {
6748
- runPeers: () => runPeers
6749
- });
6750
- function projectFields(record, fields) {
6751
- const out = {};
6752
- for (const f of fields) {
6753
- const sourceKey = FIELD_ALIAS[f] ?? f;
6754
- out[f] = record[sourceKey];
6755
- }
6756
- return out;
6757
- }
6758
- async function listPeersForMesh(slug) {
6759
- const config = readConfig();
6760
- const joined = config.meshes.find((m) => m.slug === slug);
6761
- const selfMemberPubkey = joined?.pubkey ?? null;
6762
- const bridged = await tryBridge(slug, "peers");
6763
- if (bridged && bridged.ok) {
6764
- const peers = bridged.result;
6765
- return peers.map((p) => annotateSelf(p, selfMemberPubkey, null));
6766
- }
6767
- let result = [];
6768
- await withMesh({ meshSlug: slug }, async (client) => {
6769
- const all = await client.listPeers();
6770
- const selfSessionPubkey = client.getSessionPubkey();
6771
- result = all.map((p) => annotateSelf(p, selfMemberPubkey, selfSessionPubkey));
6772
- });
6773
- return result;
6774
- }
6775
- function annotateSelf(peer, selfMemberPubkey, selfSessionPubkey) {
6776
- const isSelf = !!(selfMemberPubkey && peer.memberPubkey && peer.memberPubkey === selfMemberPubkey);
6777
- const isThisSession = !!(isSelf && selfSessionPubkey && peer.pubkey === selfSessionPubkey);
6778
- return { ...peer, isSelf, isThisSession };
6779
- }
6780
- async function runPeers(flags) {
6781
- const config = readConfig();
6782
- const slugs = flags.mesh ? [flags.mesh] : config.meshes.map((m) => m.slug);
6783
- if (slugs.length === 0) {
6784
- render.err("No meshes joined.");
6785
- render.hint("claudemesh <invite-url> # join + launch");
6786
- process.exit(1);
6787
- }
6788
- const fieldList = typeof flags.json === "string" && flags.json.length > 0 ? flags.json.split(",").map((s) => s.trim()).filter(Boolean) : null;
6789
- const wantsJson = flags.json !== undefined && flags.json !== false;
6790
- const allJson = [];
6791
- for (const slug of slugs) {
6792
- try {
6793
- const peers = await listPeersForMesh(slug);
6794
- if (wantsJson) {
6795
- const projected = fieldList ? peers.map((p) => projectFields(p, fieldList)) : peers;
6796
- allJson.push({ mesh: slug, peers: projected });
6797
- continue;
6798
- }
6799
- render.section(`peers on ${slug} (${peers.length})`);
6800
- if (peers.length === 0) {
6801
- render.info(dim(" (no peers connected)"));
6802
- continue;
6803
- }
6804
- for (const p of peers) {
6805
- const groups = p.groups.length ? " [" + p.groups.map((g) => `@${g.name}${g.role ? `:${g.role}` : ""}`).join(", ") + "]" : "";
6806
- const statusDot = p.status === "working" ? yellow("●") : green("●");
6807
- const name = bold(p.displayName);
6808
- const meta = [];
6809
- if (p.peerType)
6810
- meta.push(p.peerType);
6811
- if (p.channel)
6812
- meta.push(p.channel);
6813
- if (p.model)
6814
- meta.push(p.model);
6815
- const metaStr = meta.length ? dim(` (${meta.join(", ")})`) : "";
6816
- const summary = p.summary ? dim(` — ${p.summary}`) : "";
6817
- const pubkeyTag = dim(` · ${p.pubkey.slice(0, 16)}…`);
6818
- const selfTag = p.isThisSession ? dim(" ") + yellow("(this session)") : p.isSelf ? dim(" ") + yellow("(your other session)") : "";
6819
- render.info(`${statusDot} ${name}${selfTag}${groups}${metaStr}${pubkeyTag}${summary}`);
6820
- if (p.cwd)
6821
- render.info(dim(` cwd: ${p.cwd}`));
6822
- }
6823
- } catch (e) {
6824
- render.err(`${slug}: ${e instanceof Error ? e.message : String(e)}`);
6825
- }
6826
- }
6827
- if (wantsJson) {
6828
- process.stdout.write(JSON.stringify(slugs.length === 1 ? allJson[0]?.peers : allJson, null, 2) + `
6829
- `);
6830
- }
6831
- }
6832
- var FIELD_ALIAS;
6833
- var init_peers = __esm(() => {
6834
- init_connect();
6835
- init_facade();
6836
- init_client3();
6837
- init_render();
6838
- init_styles();
6839
- FIELD_ALIAS = {
6840
- name: "displayName"
6841
- };
6842
- });
6843
-
6844
- // src/daemon/paths.ts
6845
- import { join as join6 } from "node:path";
6846
- var DAEMON_PATHS, DAEMON_TCP_HOST = "127.0.0.1", DAEMON_TCP_DEFAULT_PORT = 47823;
6847
- var init_paths2 = __esm(() => {
6848
- init_paths();
6849
- DAEMON_PATHS = {
6850
- get DAEMON_DIR() {
6851
- return join6(PATHS.CONFIG_DIR, "daemon");
6852
- },
6853
- get PID_FILE() {
6854
- return join6(this.DAEMON_DIR, "daemon.pid");
6855
- },
6856
- get SOCK_FILE() {
6857
- return join6(this.DAEMON_DIR, "daemon.sock");
6858
- },
6859
- get TOKEN_FILE() {
6860
- return join6(this.DAEMON_DIR, "local-token");
6861
- },
6862
- get OUTBOX_DB() {
6863
- return join6(this.DAEMON_DIR, "outbox.db");
6864
- },
6865
- get INBOX_DB() {
6866
- return join6(this.DAEMON_DIR, "inbox.db");
6867
- },
6868
- get LOG_FILE() {
6869
- return join6(this.DAEMON_DIR, "daemon.log");
6870
- }
6871
- };
6872
- });
6873
-
6874
6799
  // src/daemon/local-token.ts
6875
6800
  import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "node:fs";
6876
6801
  import { dirname as dirname3 } from "node:path";
@@ -6952,7 +6877,61 @@ var init_client4 = __esm(() => {
6952
6877
  });
6953
6878
 
6954
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
+ });
6955
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
+ }
6956
6935
  async function trySendViaDaemon(args) {
6957
6936
  if (!existsSync7(DAEMON_PATHS.SOCK_FILE))
6958
6937
  return null;
@@ -6989,34 +6968,140 @@ var init_daemon_route = __esm(() => {
6989
6968
  init_paths2();
6990
6969
  });
6991
6970
 
6992
- // src/commands/send.ts
6993
- var exports_send = {};
6994
- __export(exports_send, {
6995
- runSend: () => runSend
6971
+ // src/commands/peers.ts
6972
+ var exports_peers = {};
6973
+ __export(exports_peers, {
6974
+ runPeers: () => runPeers
6996
6975
  });
6997
- async function runSend(flags, to, message) {
6998
- if (!to || !message) {
6999
- render.err("Usage: claudemesh send <to> <message>");
7000
- process.exit(1);
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];
7001
6981
  }
7002
- const priority = flags.priority === "now" ? "now" : flags.priority === "low" ? "low" : "next";
6982
+ return out;
6983
+ }
6984
+ async function listPeersForMesh(slug) {
7003
6985
  const config = readConfig();
7004
- const meshSlug = flags.mesh ?? (config.meshes.length === 1 ? config.meshes[0].slug : null);
7005
- if (!flags.self && meshSlug) {
7006
- const joined = config.meshes.find((m) => m.slug === meshSlug);
7007
- if (joined && /^[0-9a-f]{64}$/i.test(to) && to.toLowerCase() === joined.pubkey.toLowerCase()) {
7008
- render.err(`Target "${to.slice(0, 16)}…" is your own member pubkey on mesh "${meshSlug}".`);
7009
- render.hint("Pass --self to message a sibling session of your own member, or pick a different peer's pubkey.");
7010
- process.exit(1);
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));
7011
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));
7012
6999
  }
7013
- {
7014
- const dr = await trySendViaDaemon({ to, message, priority, expectedMesh: meshSlug ?? undefined });
7015
- if (dr !== null) {
7016
- if (dr.ok) {
7017
- if (flags.json)
7018
- console.log(JSON.stringify({ ok: true, messageId: dr.messageId, target: to, via: "daemon", duplicate: !!dr.duplicate }));
7019
- else
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
+
7077
+ // src/commands/send.ts
7078
+ var exports_send = {};
7079
+ __export(exports_send, {
7080
+ runSend: () => runSend
7081
+ });
7082
+ async function runSend(flags, to, message) {
7083
+ if (!to || !message) {
7084
+ render.err("Usage: claudemesh send <to> <message>");
7085
+ process.exit(1);
7086
+ }
7087
+ const priority = flags.priority === "now" ? "now" : flags.priority === "low" ? "low" : "next";
7088
+ const config = readConfig();
7089
+ const meshSlug = flags.mesh ?? (config.meshes.length === 1 ? config.meshes[0].slug : null);
7090
+ if (!flags.self && meshSlug) {
7091
+ const joined = config.meshes.find((m) => m.slug === meshSlug);
7092
+ if (joined && /^[0-9a-f]{64}$/i.test(to) && to.toLowerCase() === joined.pubkey.toLowerCase()) {
7093
+ render.err(`Target "${to.slice(0, 16)}…" is your own member pubkey on mesh "${meshSlug}".`);
7094
+ render.hint("Pass --self to message a sibling session of your own member, or pick a different peer's pubkey.");
7095
+ process.exit(1);
7096
+ }
7097
+ }
7098
+ {
7099
+ const dr = await trySendViaDaemon({ to, message, priority, expectedMesh: meshSlug ?? undefined });
7100
+ if (dr !== null) {
7101
+ if (dr.ok) {
7102
+ if (flags.json)
7103
+ console.log(JSON.stringify({ ok: true, messageId: dr.messageId, target: to, via: "daemon", duplicate: !!dr.duplicate }));
7104
+ else
7020
7105
  render.ok(`sent to ${to} (daemon)`, dr.messageId ? dim(dr.messageId.slice(0, 8)) : undefined);
7021
7106
  return;
7022
7107
  }
@@ -8423,12 +8508,32 @@ function migrateOutbox(db) {
8423
8508
  CREATE INDEX IF NOT EXISTS outbox_aborted
8424
8509
  ON outbox(status, aborted_at) WHERE status = 'aborted';
8425
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);
8426
8530
  }
8427
8531
  function findByClientId(db, clientMessageId) {
8428
8532
  const row = db.prepare(`
8429
8533
  SELECT id, client_message_id, request_fingerprint, payload, enqueued_at,
8430
8534
  attempts, next_attempt_at, status, last_error, delivered_at,
8431
- 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
8432
8537
  FROM outbox WHERE client_message_id = ?
8433
8538
  `).get(clientMessageId);
8434
8539
  return row ?? null;
@@ -8437,9 +8542,10 @@ function insertPending(db, input) {
8437
8542
  db.prepare(`
8438
8543
  INSERT INTO outbox (
8439
8544
  id, client_message_id, request_fingerprint, payload,
8440
- enqueued_at, attempts, next_attempt_at, status
8441
- ) VALUES (?, ?, ?, ?, ?, 0, ?, 'pending')
8442
- `).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);
8443
8549
  }
8444
8550
  function fingerprintsEqual(a, b) {
8445
8551
  if (a.length !== b.length)
@@ -8459,7 +8565,8 @@ function listOutbox(db, p = {}) {
8459
8565
  const sql = `
8460
8566
  SELECT id, client_message_id, request_fingerprint, payload, enqueued_at,
8461
8567
  attempts, next_attempt_at, status, last_error, delivered_at,
8462
- 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
8463
8570
  FROM outbox
8464
8571
  ${where.length ? "WHERE " + where.join(" AND ") : ""}
8465
8572
  ORDER BY enqueued_at DESC
@@ -8472,7 +8579,8 @@ function findById(db, id) {
8472
8579
  return db.prepare(`
8473
8580
  SELECT id, client_message_id, request_fingerprint, payload, enqueued_at,
8474
8581
  attempts, next_attempt_at, status, last_error, delivered_at,
8475
- 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
8476
8584
  FROM outbox WHERE id = ?
8477
8585
  `).get(id) ?? null;
8478
8586
  }
@@ -8615,7 +8723,12 @@ function acceptSend(req, deps) {
8615
8723
  client_message_id: clientId,
8616
8724
  request_fingerprint: fingerprint,
8617
8725
  payload: body,
8618
- 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
8619
8732
  });
8620
8733
  return { kind: "accepted_pending", status: 202, client_message_id: clientId };
8621
8734
  }
@@ -8796,7 +8909,9 @@ function startIpcServer(opts) {
8796
8909
  inboxDb: opts.inboxDb,
8797
8910
  bus: opts.bus,
8798
8911
  broker: opts.broker,
8799
- onPendingInserted: opts.onPendingInserted
8912
+ onPendingInserted: opts.onPendingInserted,
8913
+ meshSecretKey: opts.meshSecretKey,
8914
+ meshSlug: opts.meshSlug
8800
8915
  });
8801
8916
  if (existsSync9(DAEMON_PATHS.SOCK_FILE)) {
8802
8917
  try {
@@ -8888,7 +9003,7 @@ function makeHandler(opts) {
8888
9003
  respond(res, 200, {
8889
9004
  daemon_version: VERSION,
8890
9005
  ipc_api: "v1",
8891
- ipc_features: ["version", "health", "send", "inbox", "events", "peers", "profile"],
9006
+ ipc_features: ["version", "health", "send", "inbox", "events", "peers", "profile", "skills"],
8892
9007
  schema_version: 1
8893
9008
  });
8894
9009
  return;
@@ -8918,6 +9033,42 @@ function makeHandler(opts) {
8918
9033
  }
8919
9034
  return;
8920
9035
  }
9036
+ if (req.method === "GET" && url.pathname === "/v1/skills") {
9037
+ if (!opts.broker) {
9038
+ respond(res, 503, { error: "broker not initialised" });
9039
+ return;
9040
+ }
9041
+ const query = url.searchParams.get("query") ?? undefined;
9042
+ try {
9043
+ const skills = await opts.broker.listSkills(query);
9044
+ respond(res, 200, { skills });
9045
+ } catch (e) {
9046
+ respond(res, 502, { error: "broker_unreachable", detail: String(e) });
9047
+ }
9048
+ return;
9049
+ }
9050
+ if (req.method === "GET" && url.pathname.startsWith("/v1/skills/")) {
9051
+ if (!opts.broker) {
9052
+ respond(res, 503, { error: "broker not initialised" });
9053
+ return;
9054
+ }
9055
+ const name = decodeURIComponent(url.pathname.slice("/v1/skills/".length));
9056
+ if (!name) {
9057
+ respond(res, 400, { error: "missing skill name" });
9058
+ return;
9059
+ }
9060
+ try {
9061
+ const skill = await opts.broker.getSkill(name);
9062
+ if (!skill) {
9063
+ respond(res, 404, { error: "skill_not_found", name });
9064
+ return;
9065
+ }
9066
+ respond(res, 200, { skill });
9067
+ } catch (e) {
9068
+ respond(res, 502, { error: "broker_unreachable", detail: String(e) });
9069
+ }
9070
+ return;
9071
+ }
8921
9072
  if (req.method === "POST" && url.pathname === "/v1/profile") {
8922
9073
  if (!opts.broker) {
8923
9074
  respond(res, 503, { error: "broker not initialised" });
@@ -9066,6 +9217,18 @@ function makeHandler(opts) {
9066
9217
  respond(res, 400, { error: parsed.error });
9067
9218
  return;
9068
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
+ }
9069
9232
  const outcome = acceptSend(parsed.req, { db: opts.outboxDb });
9070
9233
  switch (outcome.kind) {
9071
9234
  case "accepted_pending":
@@ -9171,6 +9334,48 @@ function parseSendRequest(body, idempotencyHeader) {
9171
9334
  }
9172
9335
  };
9173
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
+ }
9174
9379
  function respond(res, status, body) {
9175
9380
  const json = JSON.stringify(body);
9176
9381
  res.statusCode = status;
@@ -9198,6 +9403,8 @@ class DaemonBrokerClient {
9198
9403
  helloTimer = null;
9199
9404
  pendingAcks = new Map;
9200
9405
  peerListResolvers = new Map;
9406
+ skillListResolvers = new Map;
9407
+ skillDataResolvers = new Map;
9201
9408
  sessionPubkey = null;
9202
9409
  sessionSecretKey = null;
9203
9410
  opens = [];
@@ -9318,6 +9525,26 @@ class DaemonBrokerClient {
9318
9525
  }
9319
9526
  return;
9320
9527
  }
9528
+ if (msg.type === "skill_list") {
9529
+ const reqId = String(msg._reqId ?? "");
9530
+ const pending = this.skillListResolvers.get(reqId);
9531
+ if (pending) {
9532
+ this.skillListResolvers.delete(reqId);
9533
+ clearTimeout(pending.timer);
9534
+ pending.resolve(Array.isArray(msg.skills) ? msg.skills : []);
9535
+ }
9536
+ return;
9537
+ }
9538
+ if (msg.type === "skill_data") {
9539
+ const reqId = String(msg._reqId ?? "");
9540
+ const pending = this.skillDataResolvers.get(reqId);
9541
+ if (pending) {
9542
+ this.skillDataResolvers.delete(reqId);
9543
+ clearTimeout(pending.timer);
9544
+ pending.resolve(msg.skill ?? null);
9545
+ }
9546
+ return;
9547
+ }
9321
9548
  if (msg.type === "push" || msg.type === "inbound") {
9322
9549
  this.opts.onPush?.(msg);
9323
9550
  return;
@@ -9400,6 +9627,44 @@ class DaemonBrokerClient {
9400
9627
  }
9401
9628
  });
9402
9629
  }
9630
+ async listSkills(query, timeoutMs = 5000) {
9631
+ if (this._status !== "open" || !this.ws)
9632
+ return [];
9633
+ return new Promise((resolve) => {
9634
+ const reqId = `sl-${++this.reqCounter}`;
9635
+ const timer = setTimeout(() => {
9636
+ if (this.skillListResolvers.delete(reqId))
9637
+ resolve([]);
9638
+ }, timeoutMs);
9639
+ this.skillListResolvers.set(reqId, { resolve, timer });
9640
+ try {
9641
+ this.ws.send(JSON.stringify({ type: "list_skills", query, _reqId: reqId }));
9642
+ } catch {
9643
+ this.skillListResolvers.delete(reqId);
9644
+ clearTimeout(timer);
9645
+ resolve([]);
9646
+ }
9647
+ });
9648
+ }
9649
+ async getSkill(name, timeoutMs = 5000) {
9650
+ if (this._status !== "open" || !this.ws)
9651
+ return null;
9652
+ return new Promise((resolve) => {
9653
+ const reqId = `sg-${++this.reqCounter}`;
9654
+ const timer = setTimeout(() => {
9655
+ if (this.skillDataResolvers.delete(reqId))
9656
+ resolve(null);
9657
+ }, timeoutMs);
9658
+ this.skillDataResolvers.set(reqId, { resolve, timer });
9659
+ try {
9660
+ this.ws.send(JSON.stringify({ type: "get_skill", name, _reqId: reqId }));
9661
+ } catch {
9662
+ this.skillDataResolvers.delete(reqId);
9663
+ clearTimeout(timer);
9664
+ resolve(null);
9665
+ }
9666
+ });
9667
+ }
9403
9668
  setProfile(profile) {
9404
9669
  if (this._status !== "open" || !this.ws)
9405
9670
  return;
@@ -9518,7 +9783,8 @@ function startDrainWorker(opts) {
9518
9783
  async function drainOnce(opts, log2) {
9519
9784
  const now = Date.now();
9520
9785
  const rows = opts.db.prepare(`
9521
- 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
9522
9788
  FROM outbox
9523
9789
  WHERE status = 'pending' AND next_attempt_at <= ?
9524
9790
  ORDER BY enqueued_at
@@ -9530,15 +9796,26 @@ async function drainOnce(opts, log2) {
9530
9796
  if (markInflight(opts.db, row.id, now) === 0)
9531
9797
  continue;
9532
9798
  const fpHex = bufferToHex(row.request_fingerprint);
9533
- const sessionKeys = opts.broker.getSessionKeys();
9534
- const targetSpec = "*";
9535
- const nonce = await randomNonce2();
9536
- 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
+ }
9537
9814
  let res;
9538
9815
  try {
9539
9816
  res = await opts.broker.send({
9540
9817
  targetSpec,
9541
- priority: "next",
9818
+ priority,
9542
9819
  nonce,
9543
9820
  ciphertext,
9544
9821
  client_message_id: row.client_message_id,
@@ -9626,11 +9903,14 @@ async function handleBrokerPush(msg, ctx) {
9626
9903
  const brokerMessageId = stringOrNull(msg.messageId);
9627
9904
  const senderPubkey = stringOrNull(msg.senderPubkey) ?? "";
9628
9905
  const senderName = stringOrNull(msg.senderName) ?? senderPubkey.slice(0, 8);
9906
+ const senderMemberPk = stringOrNull(msg.senderMemberPubkey);
9629
9907
  const topic = stringOrNull(msg.topic);
9630
9908
  const replyToId = stringOrNull(msg.replyToId);
9631
9909
  const ciphertext = stringOrNull(msg.ciphertext) ?? "";
9632
9910
  const nonce = stringOrNull(msg.nonce) ?? "";
9633
9911
  const createdAt = stringOrNull(msg.createdAt);
9912
+ const priority = stringOrNull(msg.priority) ?? "next";
9913
+ const subtype = stringOrNull(msg.subtype);
9634
9914
  const clientMessageId = stringOrNull(msg.client_message_id) ?? brokerMessageId ?? randomUUID5();
9635
9915
  const body = await decryptOrFallback({
9636
9916
  ciphertext,
@@ -9660,9 +9940,12 @@ async function handleBrokerPush(msg, ctx) {
9660
9940
  client_message_id: clientMessageId,
9661
9941
  broker_message_id: brokerMessageId,
9662
9942
  sender_pubkey: senderPubkey,
9943
+ sender_member_pubkey: senderMemberPk,
9663
9944
  sender_name: senderName,
9664
9945
  topic,
9665
9946
  reply_to_id: replyToId,
9947
+ priority,
9948
+ ...subtype ? { subtype } : {},
9666
9949
  body,
9667
9950
  created_at: createdAt
9668
9951
  });
@@ -9946,7 +10229,9 @@ async function runDaemon(opts = {}) {
9946
10229
  inboxDb,
9947
10230
  bus,
9948
10231
  broker,
9949
- onPendingInserted: () => drain?.wake()
10232
+ onPendingInserted: () => drain?.wake(),
10233
+ meshSecretKey: mesh.secretKey,
10234
+ meshSlug: mesh.slug
9950
10235
  });
9951
10236
  try {
9952
10237
  await ipc2.ready;
@@ -10761,6 +11046,7 @@ function installStatusLine() {
10761
11046
  function runInstall(args = []) {
10762
11047
  const skipHooks = args.includes("--no-hooks");
10763
11048
  const skipSkill = args.includes("--no-skill");
11049
+ const skipService = args.includes("--no-service");
10764
11050
  const wantStatusLine = args.includes("--status-line");
10765
11051
  render.section("claudemesh install");
10766
11052
  const entry = resolveEntry();
@@ -10842,10 +11128,25 @@ function runInstall(args = []) {
10842
11128
  }
10843
11129
  }
10844
11130
  let hasMeshes = false;
11131
+ let primaryMesh;
10845
11132
  try {
10846
11133
  const meshConfig = readConfig();
10847
11134
  hasMeshes = meshConfig.meshes.length > 0;
11135
+ primaryMesh = meshConfig.meshes[0]?.slug;
10848
11136
  } catch {}
11137
+ if (!skipService && hasMeshes && primaryMesh) {
11138
+ try {
11139
+ installDaemonService(entry, primaryMesh);
11140
+ } catch (e) {
11141
+ render.warn(`daemon service install failed: ${e instanceof Error ? e.message : String(e)}`, "Run `claudemesh daemon install-service --mesh <slug>` to retry.");
11142
+ }
11143
+ } else if (skipService) {
11144
+ render.info(dim("· Daemon service skipped (--no-service)"));
11145
+ render.info(dim(" MCP integration will fail at boot until you start the daemon manually:"));
11146
+ render.info(dim(" claudemesh daemon up --mesh <slug>"));
11147
+ } else if (!hasMeshes) {
11148
+ render.info(dim("· Daemon service deferred — join a mesh first, then run install again."));
11149
+ }
10849
11150
  render.blank();
10850
11151
  render.warn(`${bold("RESTART CLAUDE CODE")} ${yellow("for MCP tools to appear.")}`);
10851
11152
  if (!hasMeshes) {
@@ -10863,6 +11164,40 @@ function runInstall(args = []) {
10863
11164
  render.info(dim(` claudemesh install --status-line # live peer count in Claude Code`));
10864
11165
  render.info(dim(` claudemesh completions zsh # shell completions`));
10865
11166
  }
11167
+ function installDaemonService(binaryEntry, meshSlug) {
11168
+ const {
11169
+ installService: installService2,
11170
+ detectPlatform: detectPlatform2
11171
+ } = (init_service_install(), __toCommonJS(exports_service_install));
11172
+ const platform6 = detectPlatform2();
11173
+ if (!platform6) {
11174
+ render.info(dim(`· Daemon service skipped — unsupported platform: ${process.platform}`));
11175
+ return;
11176
+ }
11177
+ let binary = process.argv[1] ?? binaryEntry;
11178
+ if (!binary || /\.ts$/.test(binary) || /node_modules|src\/entrypoints/.test(binary)) {
11179
+ try {
11180
+ const { execSync: execSync3 } = __require("node:child_process");
11181
+ binary = execSync3("which claudemesh", { encoding: "utf8" }).trim();
11182
+ } catch {
11183
+ render.warn("couldn't resolve a 'claudemesh' binary on PATH; daemon service skipped", "Install via npm/homebrew, then run `claudemesh daemon install-service --mesh " + meshSlug + "`");
11184
+ return;
11185
+ }
11186
+ }
11187
+ const r = installService2({ binaryPath: binary, meshSlug });
11188
+ render.ok(`daemon service installed (${r.platform})`);
11189
+ render.kv([
11190
+ ["unit", dim(r.unitPath)],
11191
+ ["mesh", dim(meshSlug)]
11192
+ ]);
11193
+ try {
11194
+ const { execSync: execSync3 } = __require("node:child_process");
11195
+ execSync3(r.bootCommand, { stdio: "ignore" });
11196
+ render.ok("daemon started");
11197
+ } catch (e) {
11198
+ render.warn(`daemon service installed but failed to start: ${e instanceof Error ? e.message : String(e)}`, `Run manually: ${r.bootCommand}`);
11199
+ }
11200
+ }
10866
11201
  function runUninstall() {
10867
11202
  render.section("claudemesh uninstall");
10868
11203
  if (removeMcpServer()) {
@@ -13093,6 +13428,32 @@ async function runSqlSchema(opts) {
13093
13428
  });
13094
13429
  }
13095
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 {}
13096
13457
  return await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
13097
13458
  const skills = await client.listSkills(opts.query);
13098
13459
  if (opts.json) {
@@ -13121,6 +13482,29 @@ async function runSkillGet(name, opts) {
13121
13482
  render.err("Usage: claudemesh skill get <name>");
13122
13483
  return EXIT.INVALID_ARGS;
13123
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 {}
13124
13508
  return await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
13125
13509
  const skill = await client.getSkill(name);
13126
13510
  if (!skill) {
@@ -13638,7 +14022,7 @@ claudemesh send "<from_name>" "..." --mesh "<mesh_slug>"
13638
14022
 
13639
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.
13640
14024
 
13641
- ### Daemon path (v0.9.0, opt-in, fastest)
14025
+ ### Daemon path (v1.24.0+, REQUIRED for in-Claude-Code use)
13642
14026
 
13643
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.
13644
14028
 
@@ -13653,11 +14037,15 @@ claudemesh daemon outbox requeue <id> # re-enqueue an aborted/d
13653
14037
  claudemesh daemon down # SIGTERM + wait
13654
14038
  \`\`\`
13655
14039
 
13656
- \`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).
13657
14045
 
13658
14046
  ## Spawning new sessions (no wizard)
13659
14047
 
13660
- \`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.
13661
14049
 
13662
14050
  ### Full flag surface
13663
14051
 
@@ -15958,435 +16346,194 @@ var init_member = __esm(() => {
15958
16346
  init_exit_codes();
15959
16347
  });
15960
16348
 
15961
- // src/mcp/tools/definitions.ts
15962
- var TOOLS;
15963
- var init_definitions = __esm(() => {
15964
- TOOLS = [];
15965
- });
15966
-
15967
- // src/services/bridge/server.ts
15968
- import { createServer as createServer3 } from "node:net";
15969
- import { mkdirSync as mkdirSync12, unlinkSync as unlinkSync5, existsSync as existsSync28, chmodSync as chmodSync6 } from "node:fs";
15970
- async function resolveTarget2(client, to) {
15971
- if (to.startsWith("@") || to === "*" || /^[0-9a-f]{64}$/i.test(to)) {
15972
- return { ok: true, spec: to };
15973
- }
15974
- const peers = await client.listPeers();
15975
- const match = peers.find((p) => p.displayName.toLowerCase() === to.toLowerCase());
15976
- if (!match) {
15977
- return {
15978
- ok: false,
15979
- error: `peer "${to}" not found. online: ${peers.map((p) => p.displayName).join(", ") || "(none)"}`
15980
- };
16349
+ // src/mcp/server.ts
16350
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
16351
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
16352
+ import {
16353
+ ListToolsRequestSchema,
16354
+ CallToolRequestSchema,
16355
+ ListPromptsRequestSchema,
16356
+ GetPromptRequestSchema,
16357
+ ListResourcesRequestSchema,
16358
+ ReadResourceRequestSchema
16359
+ } from "@modelcontextprotocol/sdk/types.js";
16360
+ import { existsSync as existsSync28 } from "node:fs";
16361
+ import { request as httpRequest2 } from "node:http";
16362
+ async function daemonReady() {
16363
+ for (let i = 0;i < DAEMON_BOOT_RETRIES; i++) {
16364
+ if (existsSync28(DAEMON_PATHS.SOCK_FILE))
16365
+ return true;
16366
+ await new Promise((r) => setTimeout(r, DAEMON_BOOT_RETRY_MS));
15981
16367
  }
15982
- return { ok: true, spec: match.pubkey };
16368
+ return false;
15983
16369
  }
15984
- async function dispatch(client, req) {
15985
- const args = req.args ?? {};
15986
- try {
15987
- switch (req.verb) {
15988
- case "ping": {
15989
- const peers = await client.listPeers();
15990
- return {
15991
- id: req.id,
15992
- ok: true,
15993
- result: {
15994
- mesh: client.meshSlug,
15995
- ws_status: client.status,
15996
- peers_online: peers.length,
15997
- push_buffer: client.pushHistory.length
15998
- }
15999
- };
16000
- }
16001
- case "peers": {
16002
- const peers = await client.listPeers();
16003
- return { id: req.id, ok: true, result: peers };
16004
- }
16005
- case "send": {
16006
- const to = String(args.to ?? "");
16007
- const message = String(args.message ?? "");
16008
- const priority = args.priority ?? "next";
16009
- if (!to || !message) {
16010
- return { id: req.id, ok: false, error: "send: `to` and `message` required" };
16011
- }
16012
- const resolved = await resolveTarget2(client, to);
16013
- if (!resolved.ok)
16014
- return { id: req.id, ok: false, error: resolved.error };
16015
- const result = await client.send(resolved.spec, message, priority);
16016
- if (!result.ok) {
16017
- return { id: req.id, ok: false, error: result.error ?? "send failed" };
16018
- }
16019
- return {
16020
- id: req.id,
16021
- ok: true,
16022
- result: { messageId: result.messageId, target: resolved.spec }
16023
- };
16024
- }
16025
- case "summary": {
16026
- const text = String(args.summary ?? "");
16027
- if (!text)
16028
- return { id: req.id, ok: false, error: "summary: `summary` required" };
16029
- await client.setSummary(text);
16030
- return { id: req.id, ok: true, result: { summary: text } };
16031
- }
16032
- case "status_set": {
16033
- const state = String(args.status ?? "");
16034
- if (!["idle", "working", "dnd"].includes(state)) {
16035
- return { id: req.id, ok: false, error: "status_set: must be idle | working | dnd" };
16036
- }
16037
- await client.setStatus(state);
16038
- return { id: req.id, ok: true, result: { status: state } };
16039
- }
16040
- case "visible": {
16041
- const visible = Boolean(args.visible);
16042
- await client.setVisible(visible);
16043
- return { id: req.id, ok: true, result: { visible } };
16044
- }
16045
- default:
16046
- return { id: req.id, ok: false, error: `unknown verb: ${req.verb}` };
16047
- }
16048
- } catch (err) {
16049
- return {
16050
- id: req.id,
16051
- ok: false,
16052
- error: err instanceof Error ? err.message : String(err)
16053
- };
16054
- }
16370
+ function bailNoDaemon() {
16371
+ process.stderr.write(`[claudemesh] daemon is not running.
16372
+ ` + ` Start it: claudemesh daemon up --mesh <slug>
16373
+ ` + ` Or install as service: claudemesh daemon install-service --mesh <slug>
16374
+ ` + ` Diagnose: claudemesh doctor
16375
+ ` + `
16376
+ ` + ` As of 1.24.0 the daemon is required for in-Claude-Code use of
16377
+ ` + ` claudemesh. The CLI itself (claudemesh send/peer/inbox/...) still
16378
+ ` + ` works without a daemon.
16379
+ `);
16380
+ process.exit(1);
16055
16381
  }
16056
- function handleConnection(socket, client) {
16057
- const parser = new LineParser;
16058
- socket.on("data", (chunk) => {
16059
- const lines = parser.feed(chunk);
16060
- for (const line of lines) {
16061
- if (!line.trim())
16062
- continue;
16063
- let req;
16064
- try {
16065
- req = JSON.parse(line);
16066
- } catch {
16067
- continue;
16068
- }
16069
- if (!req || typeof req !== "object" || !req.id || !req.verb)
16070
- continue;
16071
- dispatch(client, req).then((res) => {
16382
+ function daemonGet(path2) {
16383
+ return new Promise((resolve3, reject) => {
16384
+ const req = httpRequest2({ socketPath: DAEMON_PATHS.SOCK_FILE, path: path2, method: "GET", timeout: 5000 }, (res) => {
16385
+ const chunks = [];
16386
+ res.on("data", (c) => chunks.push(c));
16387
+ res.on("end", () => {
16388
+ const text = Buffer.concat(chunks).toString("utf8");
16389
+ let body = null;
16072
16390
  try {
16073
- socket.write(frame(res));
16074
- } catch {}
16391
+ body = JSON.parse(text);
16392
+ } catch {
16393
+ body = text;
16394
+ }
16395
+ resolve3({ status: res.statusCode ?? 0, body });
16075
16396
  });
16076
- }
16397
+ });
16398
+ req.on("error", reject);
16399
+ req.on("timeout", () => req.destroy(new Error("daemon_ipc_timeout")));
16400
+ req.end();
16077
16401
  });
16078
- socket.on("error", () => {});
16079
16402
  }
16080
- function startBridgeServer(client) {
16081
- const path2 = socketPath(client.meshSlug);
16082
- const dir = socketDir();
16083
- if (!existsSync28(dir)) {
16084
- mkdirSync12(dir, { recursive: true, mode: 448 });
16085
- }
16086
- if (existsSync28(path2)) {
16087
- try {
16088
- unlinkSync5(path2);
16089
- } catch {}
16090
- }
16091
- const server = createServer3((socket) => handleConnection(socket, client));
16092
- try {
16093
- server.listen(path2);
16094
- } catch (err) {
16095
- process.stderr.write(`[claudemesh] bridge: failed to bind ${path2}: ${String(err)}
16403
+ function subscribeEvents(onEvent) {
16404
+ let active = true;
16405
+ let req = null;
16406
+ const connect = () => {
16407
+ if (!active)
16408
+ return;
16409
+ req = httpRequest2({
16410
+ socketPath: DAEMON_PATHS.SOCK_FILE,
16411
+ path: "/v1/events",
16412
+ method: "GET",
16413
+ headers: { Accept: "text/event-stream" }
16414
+ });
16415
+ let buffer = "";
16416
+ req.on("response", (res) => {
16417
+ res.setEncoding("utf8");
16418
+ res.on("data", (chunk) => {
16419
+ buffer += chunk;
16420
+ let idx;
16421
+ while ((idx = buffer.indexOf(`
16422
+
16423
+ `)) >= 0) {
16424
+ const block = buffer.slice(0, idx);
16425
+ buffer = buffer.slice(idx + 2);
16426
+ if (!block.trim())
16427
+ continue;
16428
+ let kind = "message";
16429
+ let dataLine = "";
16430
+ for (const line of block.split(`
16431
+ `)) {
16432
+ if (line.startsWith(":"))
16433
+ continue;
16434
+ if (line.startsWith("event:"))
16435
+ kind = line.slice(6).trim();
16436
+ else if (line.startsWith("data:"))
16437
+ dataLine = line.slice(5).trim();
16438
+ }
16439
+ if (!dataLine)
16440
+ continue;
16441
+ try {
16442
+ const parsed = JSON.parse(dataLine);
16443
+ onEvent({ kind, ts: String(parsed.ts ?? ""), data: parsed });
16444
+ } catch {}
16445
+ }
16446
+ });
16447
+ res.on("end", () => {
16448
+ if (active) {
16449
+ process.stderr.write(`[claudemesh-mcp] sse stream ended; reconnecting in 1s
16096
16450
  `);
16097
- return null;
16098
- }
16099
- server.on("error", (err) => {
16100
- process.stderr.write(`[claudemesh] bridge: ${String(err)}
16451
+ setTimeout(connect, 1000);
16452
+ }
16453
+ });
16454
+ res.on("error", (err) => process.stderr.write(`[claudemesh-mcp] sse error: ${err.message}
16455
+ `));
16456
+ });
16457
+ req.on("error", (err) => {
16458
+ process.stderr.write(`[claudemesh-mcp] sse connect error: ${err.message}
16101
16459
  `);
16102
- });
16103
- try {
16104
- chmodSync6(path2, 384);
16105
- } catch {}
16106
- let stopped = false;
16460
+ if (active)
16461
+ setTimeout(connect, 2000);
16462
+ });
16463
+ req.end();
16464
+ };
16465
+ connect();
16107
16466
  return {
16108
- path: path2,
16109
- stop() {
16110
- if (stopped)
16111
- return;
16112
- stopped = true;
16113
- try {
16114
- server.close();
16115
- } catch {}
16467
+ close: () => {
16468
+ active = false;
16116
16469
  try {
16117
- unlinkSync5(path2);
16470
+ req?.destroy();
16118
16471
  } catch {}
16119
16472
  }
16120
16473
  };
16121
16474
  }
16122
- var init_server2 = __esm(() => {
16123
- init_protocol();
16124
- });
16125
-
16126
- // src/mcp/server.ts
16127
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
16128
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
16129
- import {
16130
- ListToolsRequestSchema,
16131
- CallToolRequestSchema,
16132
- ListPromptsRequestSchema,
16133
- GetPromptRequestSchema,
16134
- ListResourcesRequestSchema,
16135
- ReadResourceRequestSchema
16136
- } from "@modelcontextprotocol/sdk/types.js";
16137
- function relativeTime(isoStr) {
16138
- const then = new Date(isoStr).getTime();
16139
- if (isNaN(then))
16140
- return "unknown";
16141
- const diffMs = Date.now() - then;
16142
- if (diffMs < 0)
16143
- return "just now";
16144
- const seconds = Math.floor(diffMs / 1000);
16145
- if (seconds < 60)
16146
- return `${seconds}s ago`;
16147
- const minutes = Math.floor(seconds / 60);
16148
- if (minutes < 60)
16149
- return `${minutes}m ago`;
16150
- const hours = Math.floor(minutes / 60);
16151
- if (hours < 24)
16152
- return `${hours}h ago`;
16153
- const days = Math.floor(hours / 24);
16154
- return `${days} day${days !== 1 ? "s" : ""} ago`;
16155
- }
16156
- async function resolvePeerName(client, pubkey) {
16157
- const now = Date.now();
16158
- if (now - peerNameCacheAge > CACHE_TTL_MS) {
16159
- peerNameCache.clear();
16160
- try {
16161
- const peers = await client.listPeers();
16162
- for (const p of peers)
16163
- peerNameCache.set(p.pubkey, p.displayName);
16164
- } catch {}
16165
- peerNameCacheAge = now;
16166
- }
16167
- return peerNameCache.get(pubkey) ?? `peer-${pubkey.slice(0, 8)}`;
16168
- }
16169
- function decryptFailedWarning(senderPubkey) {
16170
- const who = senderPubkey ? senderPubkey.slice(0, 12) + "…" : "unknown sender";
16171
- return `⚠ message from ${who} failed to decrypt (tampered or wrong keypair)`;
16172
- }
16173
16475
  async function startMcpServer() {
16174
16476
  const serviceIdx = process.argv.indexOf("--service");
16175
16477
  if (serviceIdx !== -1 && process.argv[serviceIdx + 1]) {
16176
16478
  return startServiceProxy(process.argv[serviceIdx + 1]);
16177
16479
  }
16178
- const meshIdx = process.argv.indexOf("--mesh");
16179
- const onlyMesh = meshIdx !== -1 ? process.argv[meshIdx + 1] : null;
16180
- const config = readConfig();
16181
- if (onlyMesh) {
16182
- const available = config.meshes.map((m) => m.slug);
16183
- const filtered = config.meshes.filter((m) => m.slug === onlyMesh);
16184
- if (filtered.length === 0) {
16185
- process.stderr.write(`[claudemesh] --mesh "${onlyMesh}" not found in config. ` + `Joined meshes: ${available.join(", ") || "(none)"}
16186
- `);
16187
- process.exit(1);
16188
- }
16189
- config.meshes = filtered;
16190
- }
16191
- const myName = config.displayName ?? "unnamed";
16192
- const myRole = config.role ?? process.env.CLAUDEMESH_ROLE ?? null;
16193
- const myGroups = (config.groups ?? []).map((g) => `@${g.name}${g.role ? ":" + g.role : ""}`).join(", ") || "none";
16194
- const messageMode = config.messageMode ?? "push";
16195
- const server = new Server({ name: "claudemesh", version: "0.3.0" }, {
16196
- capabilities: {
16197
- experimental: { "claude/channel": {} },
16198
- tools: {},
16199
- prompts: {},
16200
- resources: {}
16201
- },
16202
- instructions: `## Identity
16203
- You are "${myName}"${myRole ? ` (${myRole})` : ""} — a peer in the claudemesh network. Your groups: ${myGroups}. You are one of several Claude Code sessions connected to the same mesh. No orchestrator exists — peers are equals. Your identity comes from your name and group roles, not from a central authority.
16204
-
16205
- ## Responding to messages
16206
- When you receive a <channel source="claudemesh" ...> message, RESPOND IMMEDIATELY. Pause your current task, reply via send_message (or \`claudemesh topic post --reply-to <message_id>\` for topic threads), then resume. Stay in character per your system prompt. Do not ignore low-priority messages — acknowledge them briefly even if you defer action.
16207
-
16208
- The channel attributes carry everything you need to reply — no extra lookups:
16209
- - \`from_name\` — sender display name. Use as the \`to\` arg when replying to a DM.
16210
- - \`from_pubkey\` / \`from_member_id\` — stable ids. Use \`from_member_id\` if the sender's display name might change.
16211
- - \`mesh_slug\` — pass via \`--mesh\` if your default mesh differs.
16212
- - \`priority\` — \`now\` / \`next\` / \`low\`.
16213
- - \`message_id\` — id of THIS message. To thread a reply onto it in a topic, run \`claudemesh topic post <topic> "<text>" --reply-to <message_id>\`.
16214
- - \`topic\` — set when the message arrived through a topic (vs DM). Reply in the same topic.
16215
- - \`reply_to_id\` — set when the incoming message is itself a reply. Render thread context if you re-narrate.
16216
-
16217
- If the channel meta contains \`subtype: reminder\`, this is a scheduled reminder you set for yourself — act on it immediately (no reply needed).
16218
-
16219
- ## Tools
16220
- | Tool | Description |
16221
- |------|-------------|
16222
- | send_message(to, message, priority?) | Send to peer name, @group, or * broadcast. \`to\` accepts display name, pubkey hex, @groupname, or *. |
16223
- | list_peers(mesh_slug?) | List connected peers with status, summary, groups, and roles. |
16224
- | check_messages() | Drain buffered inbound messages (auto-pushed in most cases, use as fallback). |
16225
- | set_summary(summary) | Set 1-2 sentence description of your current work, visible to all peers. |
16226
- | set_status(status) | Override status: idle, working, or dnd. |
16227
- | set_visible(visible) | Toggle visibility. Hidden peers skip list_peers and broadcasts; direct messages still arrive. |
16228
- | set_profile(avatar?, title?, bio?, capabilities?) | Set public profile: emoji avatar, short title, bio, capabilities list. |
16229
- | join_group(name, role?) | Join a @group with optional role (lead, member, observer, or any string). |
16230
- | leave_group(name) | Leave a @group. |
16231
- | set_state(key, value) | Write shared state; pushes change to all peers. |
16232
- | get_state(key) | Read a shared state value. |
16233
- | list_state() | List all state keys with values, authors, and timestamps. |
16234
- | remember(content, tags?) | Store persistent knowledge with optional tags. |
16235
- | recall(query) | Full-text search over mesh memory. |
16236
- | forget(id) | Soft-delete a memory entry. |
16237
- | claudemesh file share <path> [--to peer] [--tags a,b] | Share a file with the mesh, or DM it to a specific peer. Same-host fast path: when --to matches a peer on this machine, sends an absolute filepath instead of uploading (no MinIO round-trip). |
16238
- | claudemesh file get <id> [--out path] | Download a shared file by id. |
16239
- | claudemesh file list [query] | Find files shared in the mesh. |
16240
- | claudemesh file status <id> | Check who has accessed a file. |
16241
- | claudemesh file delete <id> | Remove a shared file from the mesh. |
16242
- | vector_store(collection, text, metadata?) | Store embedding in per-mesh Qdrant collection. |
16243
- | vector_search(collection, query, limit?) | Semantic search over stored embeddings. |
16244
- | vector_delete(collection, id) | Remove an embedding. |
16245
- | list_collections() | List vector collections in this mesh. |
16246
- | graph_query(cypher) | Read-only Cypher query on per-mesh Neo4j. |
16247
- | graph_execute(cypher) | Write Cypher query (CREATE, MERGE, DELETE). |
16248
- | mesh_query(sql) | Run a SELECT query on the per-mesh shared database. |
16249
- | mesh_execute(sql) | Run DDL/DML on the per-mesh database (CREATE TABLE, INSERT, UPDATE, DELETE). |
16250
- | mesh_schema() | List tables and columns in the per-mesh shared database. |
16251
- | create_stream(name) | Create a real-time data stream in the mesh. |
16252
- | publish(stream, data) | Push data to a stream. Subscribers receive it in real-time. |
16253
- | subscribe(stream) | Subscribe to a stream. Data pushes arrive as channel notifications. |
16254
- | list_streams() | List active streams in the mesh. |
16255
- | share_context(summary, files_read?, key_findings?, tags?) | Share session understanding with peers. |
16256
- | get_context(query) | Find context from peers who explored an area. |
16257
- | list_contexts() | See what all peers currently know. |
16258
- | create_task(title, assignee?, priority?, tags?) | Create a work item. |
16259
- | claim_task(id) | Claim an unclaimed task. |
16260
- | complete_task(id, result?) | Mark task done with optional result. |
16261
- | list_tasks(status?, assignee?) | List tasks filtered by status/assignee. |
16262
- | schedule_reminder(message, in_seconds?, deliver_at?, to?) | Schedule a reminder to yourself (no \`to\`) or a delayed message to a peer/group. Delivered as a push with \`subtype: reminder\` in the channel meta. |
16263
- | list_scheduled() | List pending scheduled reminders and messages. |
16264
- | cancel_scheduled(id) | Cancel a pending scheduled item. |
16265
- | read_peer_file(peer, path) | Read a file from another peer's project (max 1MB). |
16266
- | list_peer_files(peer, path?, pattern?) | List files in a peer's shared directory. |
16267
- | mesh_mcp_register(server_name, description, tools) | Register an MCP server with the mesh. Other peers can call its tools. |
16268
- | mesh_mcp_list() | List MCP servers available in the mesh with their tools. |
16269
- | mesh_tool_call(server_name, tool_name, args?) | Call a tool on a mesh-registered MCP server (30s timeout). |
16270
- | mesh_mcp_remove(server_name) | Unregister an MCP server you registered. |
16271
-
16272
- If multiple meshes are joined, prefix \`to\` with \`<mesh-slug>:\` to disambiguate (e.g. \`dev-team:Alice\`).
16273
-
16274
- Multi-target: send_message accepts an array of targets for the 'to' field.
16275
- send_message(to: ["Alice", "@backend"], message: "sprint starts")
16276
- Targets are deduplicated — each peer receives the message once.
16277
-
16278
- Targeted views: when different audiences need different details about the same event,
16279
- send tailored messages instead of one generic broadcast:
16280
- send_message(to: "@frontend", message: "Auth v2: useAuth hook changed, see src/auth/")
16281
- send_message(to: "@backend", message: "Auth v2: new /api/auth/v2 endpoints, v1 deprecated")
16282
- send_message(to: "@pm", message: "Auth v2 done. 3 points, no blockers.")
16283
-
16284
- ## Groups
16285
- Groups are routing labels. Send to @groupname to multicast to all members. Roles are metadata that peers interpret: a "lead" gathers input before synthesizing a response, a "member" contributes when asked, an "observer" watches silently. Join and leave groups dynamically with join_group/leave_group. Check list_peers to see who belongs to which groups and their roles.
16286
-
16287
- ## State
16288
- Shared key-value store scoped to the mesh. Use get_state/set_state for live coordination facts (deploy frozen? current sprint? PR queue). set_state pushes the change to all connected peers. Read state before asking peers questions — the answer may already be there. State is operational, not archival.
16289
-
16290
- ## Memory
16291
- Persistent knowledge that survives across sessions. Use remember(content, tags?) to store lessons, decisions, and incidents. Use recall(query) to search before asking peers. New peers should recall at session start to load institutional knowledge.
16292
-
16293
- ## File access — decision guide
16294
- Three ways to access files. Pick the right one:
16295
-
16296
- 1. **Local peer (same machine, [local] tag):** Read files directly via filesystem using their \`cwd\` path from list_peers. No limit, instant. This is the default for local peers.
16297
- 2. **Remote peer (different machine, [remote] tag):** Use \`read_peer_file(peer, path)\` — relays through the mesh. **1 MB limit**, base64 encoded. Use \`list_peer_files\` to browse first.
16298
- 3. **Persistent sharing (any peer):** Use \`share_file(path)\` — uploads to mesh storage (MinIO). **No size limit**. All peers can download anytime via \`get_file\`. Use for files that need to persist or be shared with multiple peers.
16299
-
16300
- **Rule of thumb:** local peer → filesystem. Remote peer, small file → read_peer_file. Large file or needs to persist → share_file.
16301
-
16302
- ## Vectors
16303
- Store and search semantic embeddings. Use vector_store to index content, vector_search to find similar content.
16304
-
16305
- ## Graph
16306
- Build and query entity relationship graphs. Use graph_execute for writes (CREATE, MERGE), graph_query for reads (MATCH).
16307
-
16308
- ## Mesh Database
16309
- Per-mesh PostgreSQL database. Use mesh_execute for DDL/DML (CREATE TABLE, INSERT), mesh_query for SELECT, mesh_schema to inspect tables. Schema auto-created on first use.
16310
-
16311
- ## Streams
16312
- Real-time data channels. create_stream to start one, publish to push data, subscribe to receive pushes. Use for build logs, deploy status, live metrics.
16313
-
16314
- ## Context
16315
- Share your session understanding with peers. Use share_context after exploring a codebase area. Check get_context before re-reading files another peer already analyzed.
16316
-
16317
- ## Tasks
16318
- Create and claim work items. create_task to propose work, claim_task to take ownership, complete_task when done. Prevents duplicate effort.
16319
-
16320
- ## Priority
16321
- - "now": interrupt immediately, even if recipient is in DND (use for urgent: broken deploy, blocking issue)
16322
- - "next" (default): deliver when recipient goes idle (normal coordination)
16323
- - "low": pull-only via check_messages (FYI, non-blocking context)
16324
-
16325
- ## Coordination
16326
- Call list_peers at session start to understand who is online, their roles, and what they are working on. If you are a group lead, gather input from members before responding to external requests — do not answer alone. If you are a member, contribute to your lead when asked. Use @group messages for team-wide questions, direct messages for 1:1 coordination. Set a meaningful summary so peers know your current focus.
16327
-
16328
- ## Message Mode
16329
- Your message mode is "${messageMode}".
16330
- - push: messages arrive in real-time as channel notifications. Respond immediately.
16331
- - inbox: messages are held. You'll see "[inbox] New message from X" notifications. Call check_messages to read them.
16332
- - off: no message notifications. Use check_messages manually to poll.`
16333
- });
16334
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
16335
- tools: TOOLS
16336
- }));
16480
+ const ok = await daemonReady();
16481
+ if (!ok)
16482
+ bailNoDaemon();
16483
+ const server = new Server({ name: "claudemesh", version: VERSION }, { capabilities: { tools: {}, prompts: {}, resources: {} } });
16484
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [] }));
16337
16485
  server.setRequestHandler(ListPromptsRequestSchema, async () => {
16338
- const client = allClients()[0];
16339
- if (!client)
16486
+ try {
16487
+ const { status, body } = await daemonGet("/v1/skills");
16488
+ if (status !== 200)
16489
+ return { prompts: [] };
16490
+ const skills = body?.skills ?? [];
16491
+ return { prompts: skills.map((s) => ({ name: s.name, description: s.description, arguments: [] })) };
16492
+ } catch {
16340
16493
  return { prompts: [] };
16341
- const skills = await client.listSkills();
16342
- return {
16343
- prompts: skills.map((s) => ({
16344
- name: s.name,
16345
- description: s.description,
16346
- arguments: []
16347
- }))
16348
- };
16494
+ }
16349
16495
  });
16350
16496
  server.setRequestHandler(GetPromptRequestSchema, async (req) => {
16351
- const { name, arguments: promptArgs } = req.params;
16352
- const client = allClients()[0];
16353
- if (!client)
16354
- throw new Error("Not connected to any mesh");
16355
- const skill = await client.getSkill(name);
16356
- if (!skill)
16497
+ const name = req.params.name;
16498
+ const { status, body } = await daemonGet(`/v1/skills/${encodeURIComponent(name)}`);
16499
+ if (status === 404)
16357
16500
  throw new Error(`Skill "${name}" not found in the mesh`);
16501
+ if (status !== 200)
16502
+ throw new Error(`daemon returned ${status} fetching skill`);
16503
+ const skill = body.skill;
16358
16504
  let content = skill.instructions;
16359
- const manifest = skill.manifest;
16360
- if (manifest && typeof manifest === "object") {
16505
+ const m = skill.manifest;
16506
+ if (m && typeof m === "object") {
16361
16507
  const fm = ["---"];
16362
- if (manifest.description)
16363
- fm.push(`description: "${manifest.description}"`);
16364
- if (manifest.when_to_use)
16365
- fm.push(`when_to_use: "${manifest.when_to_use}"`);
16366
- if (manifest.allowed_tools?.length)
16508
+ if (m.description)
16509
+ fm.push(`description: "${m.description}"`);
16510
+ if (m.when_to_use)
16511
+ fm.push(`when_to_use: "${m.when_to_use}"`);
16512
+ if (Array.isArray(m.allowed_tools) && m.allowed_tools.length) {
16367
16513
  fm.push(`allowed-tools:
16368
- ${manifest.allowed_tools.map((t) => ` - ${t}`).join(`
16514
+ ${m.allowed_tools.map((t) => ` - ${t}`).join(`
16369
16515
  `)}`);
16370
- if (manifest.model)
16371
- fm.push(`model: ${manifest.model}`);
16372
- if (manifest.context)
16373
- fm.push(`context: ${manifest.context}`);
16374
- if (manifest.agent)
16375
- fm.push(`agent: ${manifest.agent}`);
16376
- if (manifest.user_invocable === false)
16516
+ }
16517
+ if (m.model)
16518
+ fm.push(`model: ${m.model}`);
16519
+ if (m.context)
16520
+ fm.push(`context: ${m.context}`);
16521
+ if (m.agent)
16522
+ fm.push(`agent: ${m.agent}`);
16523
+ if (m.user_invocable === false)
16377
16524
  fm.push(`user-invocable: false`);
16378
- if (manifest.argument_hint)
16379
- fm.push(`argument-hint: "${manifest.argument_hint}"`);
16525
+ if (m.argument_hint)
16526
+ fm.push(`argument-hint: "${m.argument_hint}"`);
16380
16527
  fm.push(`---
16381
16528
  `);
16382
16529
  if (fm.length > 3)
16383
16530
  content = fm.join(`
16384
16531
  `) + content;
16385
- if (manifest.context === "fork") {
16386
- const agentType = manifest.agent || "general-purpose";
16387
- const modelHint = manifest.model ? `, model: "${manifest.model}"` : "";
16388
- const toolsHint = manifest.allowed_tools?.length ? `
16389
- Only use these tools: ${manifest.allowed_tools.join(", ")}.` : "";
16532
+ if (m.context === "fork") {
16533
+ const agentType = m.agent || "general-purpose";
16534
+ const modelHint = m.model ? `, model: "${m.model}"` : "";
16535
+ const toolsHint = m.allowed_tools?.length ? `
16536
+ Only use these tools: ${m.allowed_tools.join(", ")}.` : "";
16390
16537
  content = `IMPORTANT: Execute this skill in an isolated sub-agent. Use the Agent tool with subagent_type="${agentType}"${modelHint}. Pass the full instructions below as the agent prompt.${toolsHint}
16391
16538
 
16392
16539
  ` + content;
@@ -16394,273 +16541,127 @@ Only use these tools: ${manifest.allowed_tools.join(", ")}.` : "";
16394
16541
  }
16395
16542
  return {
16396
16543
  description: skill.description,
16397
- messages: [
16398
- {
16399
- role: "user",
16400
- content: { type: "text", text: content }
16401
- }
16402
- ]
16544
+ messages: [{ role: "user", content: { type: "text", text: content } }]
16403
16545
  };
16404
16546
  });
16405
16547
  server.setRequestHandler(ListResourcesRequestSchema, async () => {
16406
- const client = allClients()[0];
16407
- if (!client)
16548
+ try {
16549
+ const { body } = await daemonGet("/v1/skills");
16550
+ const skills = body?.skills ?? [];
16551
+ return {
16552
+ resources: skills.map((s) => ({
16553
+ uri: `skill://claudemesh/${encodeURIComponent(s.name)}`,
16554
+ name: s.name,
16555
+ description: s.description,
16556
+ mimeType: "text/markdown"
16557
+ }))
16558
+ };
16559
+ } catch {
16408
16560
  return { resources: [] };
16409
- const skills = await client.listSkills();
16410
- return {
16411
- resources: skills.map((s) => ({
16412
- uri: `skill://claudemesh/${encodeURIComponent(s.name)}`,
16413
- name: s.name,
16414
- description: s.description,
16415
- mimeType: "text/markdown"
16416
- }))
16417
- };
16561
+ }
16418
16562
  });
16419
16563
  server.setRequestHandler(ReadResourceRequestSchema, async (req) => {
16420
- const { uri } = req.params;
16421
- const match = uri.match(/^skill:\/\/claudemesh\/(.+)$/);
16422
- if (!match)
16564
+ const uri = req.params.uri;
16565
+ const m = uri.match(/^skill:\/\/claudemesh\/(.+)$/);
16566
+ if (!m)
16423
16567
  throw new Error(`Unknown resource URI: ${uri}`);
16424
- const name = decodeURIComponent(match[1]);
16425
- const client = allClients()[0];
16426
- if (!client)
16427
- throw new Error("Not connected to any mesh");
16428
- const skill = await client.getSkill(name);
16429
- if (!skill)
16568
+ const name = decodeURIComponent(m[1]);
16569
+ const { status, body } = await daemonGet(`/v1/skills/${encodeURIComponent(name)}`);
16570
+ if (status === 404)
16430
16571
  throw new Error(`Skill "${name}" not found`);
16431
- const manifest = skill.manifest;
16432
- const fmLines = ["---"];
16433
- fmLines.push(`name: ${skill.name}`);
16434
- fmLines.push(`description: "${skill.description}"`);
16435
- if (skill.tags.length)
16436
- fmLines.push(`tags: [${skill.tags.join(", ")}]`);
16437
- if (manifest && typeof manifest === "object") {
16438
- if (manifest.when_to_use)
16439
- fmLines.push(`when_to_use: "${manifest.when_to_use}"`);
16440
- if (manifest.allowed_tools?.length)
16441
- fmLines.push(`allowed-tools:
16442
- ${manifest.allowed_tools.map((t) => ` - ${t}`).join(`
16572
+ if (status !== 200)
16573
+ throw new Error(`daemon returned ${status} fetching skill`);
16574
+ const skill = body.skill;
16575
+ const fm = ["---"];
16576
+ fm.push(`name: ${skill.name}`);
16577
+ fm.push(`description: "${skill.description}"`);
16578
+ if (skill.tags?.length)
16579
+ fm.push(`tags: [${skill.tags.join(", ")}]`);
16580
+ const mf = skill.manifest;
16581
+ if (mf && typeof mf === "object") {
16582
+ if (mf.when_to_use)
16583
+ fm.push(`when_to_use: "${mf.when_to_use}"`);
16584
+ if (Array.isArray(mf.allowed_tools) && mf.allowed_tools.length) {
16585
+ fm.push(`allowed-tools:
16586
+ ${mf.allowed_tools.map((t) => ` - ${t}`).join(`
16443
16587
  `)}`);
16444
- if (manifest.model)
16445
- fmLines.push(`model: ${manifest.model}`);
16446
- if (manifest.context)
16447
- fmLines.push(`context: ${manifest.context}`);
16448
- if (manifest.agent)
16449
- fmLines.push(`agent: ${manifest.agent}`);
16450
- if (manifest.user_invocable === false)
16451
- fmLines.push(`user-invocable: false`);
16452
- if (manifest.argument_hint)
16453
- fmLines.push(`argument-hint: "${manifest.argument_hint}"`);
16454
- }
16455
- fmLines.push(`---
16588
+ }
16589
+ if (mf.model)
16590
+ fm.push(`model: ${mf.model}`);
16591
+ if (mf.context)
16592
+ fm.push(`context: ${mf.context}`);
16593
+ }
16594
+ fm.push(`---
16456
16595
  `);
16457
- const fullContent = fmLines.join(`
16458
- `) + skill.instructions;
16459
- return {
16460
- contents: [
16461
- {
16462
- uri,
16463
- mimeType: "text/markdown",
16464
- text: fullContent
16465
- }
16466
- ]
16467
- };
16596
+ return { contents: [{ uri, mimeType: "text/markdown", text: fm.join(`
16597
+ `) + skill.instructions }] };
16468
16598
  });
16469
- const transport = new StdioServerTransport;
16470
- await server.connect(transport);
16471
- const bridges = [];
16472
- startClients(config).then(() => {
16473
- wirePushHandlers().catch(() => {});
16474
- for (const client of allClients()) {
16475
- const bridge = startBridgeServer(client);
16476
- if (bridge)
16477
- bridges.push(bridge);
16478
- }
16479
- }).catch(() => {
16480
- wirePushHandlers().catch(() => {});
16481
- });
16482
- async function wirePushHandlers() {
16483
- for (const client of allClients()) {
16484
- client.onPush(async (msg) => {
16485
- if (messageMode === "off")
16486
- return;
16487
- if (msg.subtype === "system" && msg.event) {
16488
- const eventName = msg.event;
16489
- const data = msg.eventData ?? {};
16490
- let content2;
16491
- if (eventName === "tick") {
16492
- const tick = data.tick ?? 0;
16493
- const simTime = String(data.simTime ?? "").replace("T", " ").replace(/\..*/, "");
16494
- const speed = data.speed ?? 1;
16495
- content2 = `[heartbeat] tick ${tick} | sim time: ${simTime} | speed: x${speed}`;
16496
- } else if (eventName === "peer_joined") {
16497
- content2 = `[system] Peer "${data.name ?? "unknown"}" joined the mesh`;
16498
- } else if (eventName === "peer_returned") {
16499
- const peerName = String(data.name ?? "unknown");
16500
- const lastSeenAt = data.lastSeenAt ? relativeTime(String(data.lastSeenAt)) : "unknown";
16501
- const groups = Array.isArray(data.groups) ? data.groups.map((g) => g.role ? `@${g.name}:${g.role}` : `@${g.name}`).join(", ") : "";
16502
- const summary = data.summary ? ` Summary: "${data.summary}"` : "";
16503
- content2 = `[system] Welcome back, "${peerName}"! Last seen ${lastSeenAt}.${groups ? ` Restored: ${groups}` : ""}${summary}`;
16504
- } else if (eventName === "peer_left") {
16505
- content2 = `[system] Peer "${data.name ?? "unknown"}" left the mesh`;
16506
- } else if (eventName === "mcp_registered") {
16507
- const tools = Array.isArray(data.tools) ? data.tools.join(", ") : "";
16508
- content2 = `[system] New MCP server available: "${data.serverName}" (hosted by ${data.hostedBy}). Tools: ${tools}. Use mesh_tool_call to invoke.`;
16509
- } else if (eventName === "mcp_unregistered") {
16510
- content2 = `[system] MCP server "${data.serverName}" removed (was hosted by ${data.hostedBy})`;
16511
- } else if (eventName === "mcp_restored") {
16512
- content2 = `[system] MCP server "${data.serverName}" is back online (hosted by ${data.hostedBy})`;
16513
- } else if (eventName === "watch_triggered") {
16514
- content2 = `[WATCH] ${data.label ?? data.url}: ${data.oldValue} → ${data.newValue}`;
16515
- } else if (eventName === "mcp_deployed") {
16516
- content2 = `[SERVICE] "${data.name}" deployed (${data.tool_count} tools) by ${data.deployed_by}`;
16517
- } else if (eventName === "mcp_undeployed") {
16518
- content2 = `[SERVICE] "${data.name}" undeployed by ${data.by}`;
16519
- } else if (eventName === "mcp_scope_changed") {
16520
- content2 = `[SERVICE] "${data.name}" scope changed to ${JSON.stringify(data.scope)} by ${data.by}`;
16521
- } else {
16522
- content2 = `[system] ${eventName}: ${JSON.stringify(data)}`;
16523
- }
16524
- try {
16525
- await server.notification({
16526
- method: "notifications/claude/channel",
16527
- params: {
16528
- content: content2,
16529
- meta: {
16530
- kind: "system",
16531
- event: eventName,
16532
- mesh_slug: client.meshSlug,
16533
- mesh_id: client.meshId,
16534
- ...Object.keys(data).length > 0 ? { eventData: JSON.stringify(data) } : {}
16535
- }
16536
- }
16537
- });
16538
- process.stderr.write(`[claudemesh] system: ${content2}
16539
- `);
16540
- } catch (pushErr) {
16541
- process.stderr.write(`[claudemesh] system push FAILED: ${pushErr}
16542
- `);
16543
- }
16544
- return;
16545
- }
16546
- const fromPubkey = msg.senderPubkey || "";
16547
- const fromName = fromPubkey ? await resolvePeerName(client, fromPubkey) : "unknown";
16548
- if (fromPubkey) {
16549
- try {
16550
- const { isAllowed: isAllowed2 } = await Promise.resolve().then(() => (init_grants(), exports_grants));
16551
- const kindCap = msg.kind === "broadcast" ? "broadcast" : "dm";
16552
- if (!isAllowed2(client.meshSlug, fromPubkey, kindCap)) {
16553
- process.stderr.write(`[claudemesh] dropped ${kindCap} from ${fromName} (not granted)
16554
- `);
16555
- return;
16556
- }
16557
- } catch {}
16558
- }
16559
- if (messageMode === "inbox") {
16560
- try {
16561
- await server.notification({
16562
- method: "notifications/claude/channel",
16563
- params: {
16564
- content: `[inbox] New message from ${fromName}. Use check_messages to read.`,
16565
- meta: { kind: "inbox_notification", from_name: fromName }
16566
- }
16567
- });
16568
- } catch {}
16569
- return;
16570
- }
16571
- const body = msg.plaintext ?? decryptFailedWarning(fromPubkey);
16572
- const prioBadge = msg.priority === "now" ? "[URGENT] " : msg.priority === "low" ? "[low] " : "";
16573
- const kindBadge = msg.kind === "broadcast" ? " (broadcast)" : "";
16574
- const content = `${prioBadge}${fromName}${kindBadge}: ${body}`;
16575
- const fromMemberPubkey = msg.senderMemberPubkey ?? fromPubkey;
16576
- try {
16577
- await server.notification({
16578
- method: "notifications/claude/channel",
16579
- params: {
16580
- content,
16581
- meta: {
16582
- from_id: fromMemberPubkey,
16583
- from_pubkey: fromMemberPubkey,
16584
- from_session_pubkey: fromPubkey,
16585
- from_name: fromName,
16586
- ...msg.senderMemberId ? { from_member_id: msg.senderMemberId } : {},
16587
- mesh_slug: client.meshSlug,
16588
- mesh_id: client.meshId,
16589
- priority: msg.priority,
16590
- sent_at: msg.createdAt,
16591
- delivered_at: msg.receivedAt,
16592
- kind: msg.kind,
16593
- message_id: msg.messageId,
16594
- ...msg.topic ? { topic: msg.topic } : {},
16595
- ...msg.replyToId ? { reply_to_id: msg.replyToId } : {},
16596
- ...msg.subtype ? { subtype: msg.subtype } : {}
16597
- }
16599
+ const sub = subscribeEvents(async (ev) => {
16600
+ if (ev.kind === "message") {
16601
+ const d = ev.data;
16602
+ const fromName = String(d.sender_name ?? "unknown");
16603
+ const fromMember = String(d.sender_member_pubkey ?? d.sender_pubkey ?? "");
16604
+ const body = String(d.body ?? "(decrypt failed)");
16605
+ const priority = String(d.priority ?? "next");
16606
+ const prioBadge = priority === "now" ? "[URGENT] " : priority === "low" ? "[low] " : "";
16607
+ const topicTag = d.topic ? ` (#${d.topic})` : "";
16608
+ const content = `${prioBadge}${fromName}${topicTag}: ${body}`;
16609
+ try {
16610
+ await server.notification({
16611
+ method: "notifications/claude/channel",
16612
+ params: {
16613
+ content,
16614
+ meta: {
16615
+ from_id: fromMember,
16616
+ from_pubkey: fromMember,
16617
+ from_session_pubkey: String(d.sender_pubkey ?? ""),
16618
+ from_name: fromName,
16619
+ mesh_slug: String(d.mesh ?? ""),
16620
+ priority,
16621
+ message_id: String(d.broker_message_id ?? d.id ?? ""),
16622
+ client_message_id: String(d.client_message_id ?? ""),
16623
+ ...d.topic ? { topic: String(d.topic) } : {},
16624
+ ...d.reply_to_id ? { reply_to_id: String(d.reply_to_id) } : {},
16625
+ ...d.subtype ? { subtype: String(d.subtype) } : {}
16598
16626
  }
16599
- });
16600
- process.stderr.write(`[claudemesh] pushed: from=${fromName} content=${body.slice(0, 60)}
16601
- `);
16602
- } catch (pushErr) {
16603
- process.stderr.write(`[claudemesh] push FAILED: ${pushErr}
16627
+ }
16628
+ });
16629
+ } catch (err) {
16630
+ process.stderr.write(`[claudemesh-mcp] channel emit failed: ${err}
16604
16631
  `);
16605
- }
16606
- });
16607
- client.onStreamData(async (evt) => {
16608
- try {
16609
- await server.notification({
16610
- method: "notifications/claude/channel",
16611
- params: {
16612
- content: `[stream:${evt.stream}] from ${evt.publishedBy}: ${JSON.stringify(evt.data)}`,
16613
- meta: {
16614
- kind: "stream_data",
16615
- stream: evt.stream,
16616
- published_by: evt.publishedBy
16617
- }
16618
- }
16619
- });
16620
- } catch {}
16621
- });
16622
- client.onStateChange(async (change) => {
16623
- try {
16624
- await server.notification({
16625
- method: "notifications/claude/channel",
16626
- params: {
16627
- content: `[state] ${change.key} = ${JSON.stringify(change.value)} (set by ${change.updatedBy})`,
16628
- meta: {
16629
- kind: "state_change",
16630
- key: change.key,
16631
- updated_by: change.updatedBy
16632
- }
16633
- }
16634
- });
16635
- } catch {}
16636
- });
16637
- }
16638
- setTimeout(async () => {
16639
- const welcomeClient = allClients()[0];
16640
- if (!welcomeClient || welcomeClient.status !== "open")
16641
- return;
16632
+ }
16633
+ } else if (ev.kind === "peer_join" || ev.kind === "peer_leave" || ev.kind === "system") {
16634
+ const d = ev.data;
16635
+ const eventName = String(d.event ?? ev.kind);
16636
+ let content;
16637
+ if (ev.kind === "peer_join") {
16638
+ content = `[system] Peer "${String(d.name ?? "unknown")}" joined the mesh`;
16639
+ } else if (ev.kind === "peer_leave") {
16640
+ content = `[system] Peer "${String(d.name ?? "unknown")}" left the mesh`;
16641
+ } else {
16642
+ content = `[system] ${eventName}: ${JSON.stringify(d).slice(0, 240)}`;
16643
+ }
16642
16644
  try {
16643
- const peers = await welcomeClient.listPeers();
16644
- const peerNames = peers.filter((p) => p.displayName !== myName).map((p) => p.displayName).join(", ") || "none";
16645
16645
  await server.notification({
16646
16646
  method: "notifications/claude/channel",
16647
16647
  params: {
16648
- content: `[system] Connected as ${myName} to mesh ${welcomeClient.meshSlug}. ${peers.length} peer(s) online: ${peerNames}. Call mesh_info for full details or set_summary to announce yourself.`,
16649
- meta: { kind: "welcome", mesh_slug: welcomeClient.meshSlug }
16648
+ content,
16649
+ meta: {
16650
+ kind: "system",
16651
+ event: eventName,
16652
+ mesh_slug: String(d.mesh ?? "")
16653
+ }
16650
16654
  }
16651
16655
  });
16652
16656
  } catch {}
16653
- }, 2000);
16654
- }
16657
+ }
16658
+ });
16659
+ const transport = new StdioServerTransport;
16660
+ await server.connect(transport);
16655
16661
  const keepalive = setInterval(() => {}, 1000);
16656
16662
  const shutdown = () => {
16657
16663
  clearInterval(keepalive);
16658
- for (const b of bridges) {
16659
- try {
16660
- b.stop();
16661
- } catch {}
16662
- }
16663
- stopAll();
16664
+ sub.close();
16664
16665
  process.exit(0);
16665
16666
  };
16666
16667
  process.on("SIGTERM", shutdown);
@@ -16691,9 +16692,8 @@ async function startServiceProxy(serviceName) {
16691
16692
  tools = fetched;
16692
16693
  } catch {
16693
16694
  const cached2 = client.serviceCatalog.find((s) => s.name === serviceName);
16694
- if (cached2) {
16695
+ if (cached2)
16695
16696
  tools = cached2.tools;
16696
- }
16697
16697
  }
16698
16698
  if (tools.length === 0) {
16699
16699
  process.stderr.write(`[mesh:${serviceName}] no tools found — service may not be running
@@ -16718,12 +16718,7 @@ async function startServiceProxy(serviceName) {
16718
16718
  }
16719
16719
  if (client.status !== "open") {
16720
16720
  return {
16721
- content: [
16722
- {
16723
- type: "text",
16724
- text: `Service temporarily unavailable — broker reconnecting. Retry in a few seconds.`
16725
- }
16726
- ],
16721
+ content: [{ type: "text", text: "Service temporarily unavailable — broker reconnecting. Retry in a few seconds." }],
16727
16722
  isError: true
16728
16723
  };
16729
16724
  }
@@ -16731,23 +16726,13 @@ async function startServiceProxy(serviceName) {
16731
16726
  try {
16732
16727
  const result = await client.mcpCall(serviceName, toolName, args);
16733
16728
  if (result.error) {
16734
- return {
16735
- content: [{ type: "text", text: `Error: ${result.error}` }],
16736
- isError: true
16737
- };
16729
+ return { content: [{ type: "text", text: `Error: ${result.error}` }], isError: true };
16738
16730
  }
16739
16731
  const resultText = typeof result.result === "string" ? result.result : JSON.stringify(result.result, null, 2);
16740
- return {
16741
- content: [{ type: "text", text: resultText }]
16742
- };
16732
+ return { content: [{ type: "text", text: resultText }] };
16743
16733
  } catch (e) {
16744
16734
  return {
16745
- content: [
16746
- {
16747
- type: "text",
16748
- text: `Call failed: ${e instanceof Error ? e.message : String(e)}`
16749
- }
16750
- ],
16735
+ content: [{ type: "text", text: `Call failed: ${e instanceof Error ? e.message : String(e)}` }],
16751
16736
  isError: true
16752
16737
  };
16753
16738
  }
@@ -16763,9 +16748,7 @@ async function startServiceProxy(serviceName) {
16763
16748
  const newTools = push.eventData?.tools;
16764
16749
  if (Array.isArray(newTools)) {
16765
16750
  tools = newTools;
16766
- server.notification({
16767
- method: "notifications/tools/list_changed"
16768
- }).catch(() => {});
16751
+ server.notification({ method: "notifications/tools/list_changed" }).catch(() => {});
16769
16752
  }
16770
16753
  }
16771
16754
  });
@@ -16780,13 +16763,12 @@ async function startServiceProxy(serviceName) {
16780
16763
  process.on("SIGTERM", shutdown);
16781
16764
  process.on("SIGINT", shutdown);
16782
16765
  }
16783
- var peerNameCache, peerNameCacheAge = 0, CACHE_TTL_MS = 30000;
16784
- var init_server3 = __esm(() => {
16785
- init_definitions();
16766
+ var DAEMON_BOOT_RETRIES = 4, DAEMON_BOOT_RETRY_MS = 500;
16767
+ var init_server2 = __esm(() => {
16768
+ init_paths2();
16769
+ init_urls();
16786
16770
  init_facade();
16787
16771
  init_facade8();
16788
- init_server2();
16789
- peerNameCache = new Map;
16790
16772
  });
16791
16773
 
16792
16774
  // src/commands/mcp.ts
@@ -16801,7 +16783,7 @@ async function runMcp() {
16801
16783
  process.exit(0);
16802
16784
  }
16803
16785
  var init_mcp = __esm(() => {
16804
- init_server3();
16786
+ init_server2();
16805
16787
  });
16806
16788
 
16807
16789
  // src/commands/hook.ts
@@ -18612,4 +18594,4 @@ main().catch((err) => {
18612
18594
  process.exit(EXIT.INTERNAL_ERROR);
18613
18595
  });
18614
18596
 
18615
- //# debugId=DF34DB4C8E61E91564756E2164756E21
18597
+ //# debugId=EC115E4068A7B5F664756E2164756E21