claudemesh-cli 1.23.0 → 1.24.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.24.0", env;
92
107
  var init_urls = __esm(() => {
93
108
  URLS = {
94
109
  BROKER: process.env.CLAUDEMESH_BROKER_URL ?? "wss://ic.claudemesh.com/ws",
@@ -3308,44 +3323,10 @@ var init_ws_client = __esm(() => {
3308
3323
  });
3309
3324
 
3310
3325
  // 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;
3326
+ var clients;
3345
3327
  var init_manager = __esm(() => {
3346
3328
  init_ws_client();
3347
3329
  clients = new Map;
3348
- configGroups = [];
3349
3330
  });
3350
3331
 
3351
3332
  // src/services/broker/envelope.ts
@@ -3600,6 +3581,42 @@ var init_spinner = __esm(() => {
3600
3581
  FRAME_HEIGHT = H;
3601
3582
  });
3602
3583
 
3584
+ // src/daemon/paths.ts
3585
+ var exports_paths = {};
3586
+ __export(exports_paths, {
3587
+ DAEMON_TCP_HOST: () => DAEMON_TCP_HOST,
3588
+ DAEMON_TCP_DEFAULT_PORT: () => DAEMON_TCP_DEFAULT_PORT,
3589
+ DAEMON_PATHS: () => DAEMON_PATHS
3590
+ });
3591
+ import { join as join3 } from "node:path";
3592
+ var DAEMON_PATHS, DAEMON_TCP_HOST = "127.0.0.1", DAEMON_TCP_DEFAULT_PORT = 47823;
3593
+ var init_paths2 = __esm(() => {
3594
+ init_paths();
3595
+ DAEMON_PATHS = {
3596
+ get DAEMON_DIR() {
3597
+ return join3(PATHS.CONFIG_DIR, "daemon");
3598
+ },
3599
+ get PID_FILE() {
3600
+ return join3(this.DAEMON_DIR, "daemon.pid");
3601
+ },
3602
+ get SOCK_FILE() {
3603
+ return join3(this.DAEMON_DIR, "daemon.sock");
3604
+ },
3605
+ get TOKEN_FILE() {
3606
+ return join3(this.DAEMON_DIR, "local-token");
3607
+ },
3608
+ get OUTBOX_DB() {
3609
+ return join3(this.DAEMON_DIR, "outbox.db");
3610
+ },
3611
+ get INBOX_DB() {
3612
+ return join3(this.DAEMON_DIR, "inbox.db");
3613
+ },
3614
+ get LOG_FILE() {
3615
+ return join3(this.DAEMON_DIR, "daemon.log");
3616
+ }
3617
+ };
3618
+ });
3619
+
3603
3620
  // src/commands/launch.ts
3604
3621
  var exports_launch = {};
3605
3622
  __export(exports_launch, {
@@ -3609,8 +3626,41 @@ import { spawnSync as spawnSync2 } from "node:child_process";
3609
3626
  import { randomUUID } from "node:crypto";
3610
3627
  import { mkdtempSync, writeFileSync as writeFileSync4, rmSync, readdirSync, statSync, existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
3611
3628
  import { tmpdir, hostname as hostname2, homedir as homedir3 } from "node:os";
3612
- import { join as join3 } from "node:path";
3629
+ import { join as join4 } from "node:path";
3613
3630
  import { createInterface as createInterface4 } from "node:readline";
3631
+ async function ensureDaemonRunning(meshSlug, quiet) {
3632
+ const { DAEMON_PATHS: DAEMON_PATHS2 } = await Promise.resolve().then(() => (init_paths2(), exports_paths));
3633
+ if (existsSync5(DAEMON_PATHS2.SOCK_FILE))
3634
+ return;
3635
+ if (!quiet)
3636
+ render.info("starting claudemesh daemon…");
3637
+ const { spawn } = await import("node:child_process");
3638
+ const argv0 = process.argv[1] ?? "claudemesh";
3639
+ let binary = argv0;
3640
+ if (/\.ts$/.test(binary) || /node_modules|src\/entrypoints/.test(binary)) {
3641
+ try {
3642
+ const { execSync } = await import("node:child_process");
3643
+ binary = execSync("which claudemesh", { encoding: "utf8" }).trim();
3644
+ } catch {
3645
+ binary = "claudemesh";
3646
+ }
3647
+ }
3648
+ const child = spawn(binary, ["daemon", "up", "--mesh", meshSlug], {
3649
+ detached: true,
3650
+ stdio: "ignore"
3651
+ });
3652
+ child.unref();
3653
+ const start = Date.now();
3654
+ while (Date.now() - start < 1e4) {
3655
+ if (existsSync5(DAEMON_PATHS2.SOCK_FILE)) {
3656
+ if (!quiet)
3657
+ render.ok("daemon ready");
3658
+ return;
3659
+ }
3660
+ await new Promise((r) => setTimeout(r, 200));
3661
+ }
3662
+ render.warn("daemon failed to start within 10s", "Run `claudemesh daemon up --mesh " + meshSlug + "` manually, then re-launch.");
3663
+ }
3614
3664
  function parseGroupsString(raw) {
3615
3665
  return raw.split(",").map((s) => s.trim()).filter(Boolean).map((token) => {
3616
3666
  const idx = token.indexOf(":");
@@ -3930,14 +3980,15 @@ async function runLaunch(flags, rawArgs) {
3930
3980
  for (const entry of readdirSync(tmpBase)) {
3931
3981
  if (!entry.startsWith("claudemesh-"))
3932
3982
  continue;
3933
- const full = join3(tmpBase, entry);
3983
+ const full = join4(tmpBase, entry);
3934
3984
  const age = Date.now() - statSync(full).mtimeMs;
3935
3985
  if (age > 3600000)
3936
3986
  rmSync(full, { recursive: true, force: true });
3937
3987
  }
3938
3988
  } catch {}
3989
+ await ensureDaemonRunning(mesh.slug, args.quiet);
3939
3990
  try {
3940
- const claudeConfigPath = join3(homedir3(), ".claude.json");
3991
+ const claudeConfigPath = join4(homedir3(), ".claude.json");
3941
3992
  if (existsSync5(claudeConfigPath)) {
3942
3993
  const claudeConfig = JSON.parse(readFileSync4(claudeConfigPath, "utf-8"));
3943
3994
  const mcpServers = claudeConfig.mcpServers ?? {};
@@ -3974,7 +4025,7 @@ async function runLaunch(flags, rawArgs) {
3974
4025
  console.log(" (Could not fetch service catalog — mesh services won't be natively available)");
3975
4026
  }
3976
4027
  }
3977
- const tmpDir = mkdtempSync(join3(tmpdir(), "claudemesh-"));
4028
+ const tmpDir = mkdtempSync(join4(tmpdir(), "claudemesh-"));
3978
4029
  const sessionConfig = {
3979
4030
  version: 1,
3980
4031
  meshes: [mesh],
@@ -3983,14 +4034,14 @@ async function runLaunch(flags, rawArgs) {
3983
4034
  ...parsedGroups.length > 0 ? { groups: parsedGroups } : {},
3984
4035
  messageMode
3985
4036
  };
3986
- writeFileSync4(join3(tmpDir, "config.json"), JSON.stringify(sessionConfig, null, 2) + `
4037
+ writeFileSync4(join4(tmpDir, "config.json"), JSON.stringify(sessionConfig, null, 2) + `
3987
4038
  `, "utf-8");
3988
4039
  if (!args.quiet) {
3989
4040
  printBanner(displayName, mesh.slug, role, parsedGroups, messageMode);
3990
4041
  }
3991
4042
  const meshMcpEntries = [];
3992
4043
  if (serviceCatalog.length > 0) {
3993
- const claudeConfigPath = join3(homedir3(), ".claude.json");
4044
+ const claudeConfigPath = join4(homedir3(), ".claude.json");
3994
4045
  let claudeConfig = {};
3995
4046
  try {
3996
4047
  claudeConfig = JSON.parse(readFileSync4(claudeConfigPath, "utf-8"));
@@ -4057,9 +4108,9 @@ async function runLaunch(flags, rawArgs) {
4057
4108
  let claudeBin = "claude";
4058
4109
  if (!isWindows2) {
4059
4110
  const candidates = [
4060
- join3(homedir3(), ".local", "bin", "claude"),
4111
+ join4(homedir3(), ".local", "bin", "claude"),
4061
4112
  "/usr/local/bin/claude",
4062
- join3(homedir3(), ".claude", "bin", "claude")
4113
+ join4(homedir3(), ".claude", "bin", "claude")
4063
4114
  ];
4064
4115
  for (const c of candidates) {
4065
4116
  if (existsSync5(c)) {
@@ -4071,7 +4122,7 @@ async function runLaunch(flags, rawArgs) {
4071
4122
  const cleanup = () => {
4072
4123
  if (meshMcpEntries.length > 0) {
4073
4124
  try {
4074
- const claudeConfigPath = join3(homedir3(), ".claude.json");
4125
+ const claudeConfigPath = join4(homedir3(), ".claude.json");
4075
4126
  const claudeConfig = JSON.parse(readFileSync4(claudeConfigPath, "utf-8"));
4076
4127
  const mcpServers = claudeConfig.mcpServers ?? {};
4077
4128
  for (const { key } of meshMcpEntries) {
@@ -4776,7 +4827,7 @@ __export(exports_join, {
4776
4827
  });
4777
4828
  import sodium3 from "libsodium-wrappers";
4778
4829
  import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync3 } from "node:fs";
4779
- import { join as join4, dirname as dirname2 } from "node:path";
4830
+ import { join as join5, dirname as dirname2 } from "node:path";
4780
4831
  import { homedir as homedir4, hostname as hostname3 } from "node:os";
4781
4832
  function deriveAppBaseUrl() {
4782
4833
  const override = process.env.CLAUDEMESH_APP_URL;
@@ -4896,8 +4947,8 @@ async function runJoin(args) {
4896
4947
  joinedAt: new Date().toISOString()
4897
4948
  });
4898
4949
  writeConfig(config);
4899
- const configDir = env.CLAUDEMESH_CONFIG_DIR ?? join4(homedir4(), ".claudemesh");
4900
- const inviteFile = join4(configDir, `invite-${payload.mesh_slug}.txt`);
4950
+ const configDir = env.CLAUDEMESH_CONFIG_DIR ?? join5(homedir4(), ".claudemesh");
4951
+ const inviteFile = join5(configDir, `invite-${payload.mesh_slug}.txt`);
4901
4952
  try {
4902
4953
  mkdirSync3(dirname2(inviteFile), { recursive: true });
4903
4954
  writeFileSync5(inviteFile, link, "utf-8");
@@ -6640,12 +6691,9 @@ var init_ban = __esm(() => {
6640
6691
 
6641
6692
  // src/services/bridge/protocol.ts
6642
6693
  import { homedir as homedir5 } from "node:os";
6643
- import { join as join5 } from "node:path";
6694
+ import { join as join6 } from "node:path";
6644
6695
  function socketPath(meshSlug) {
6645
- return join5(homedir5(), ".claudemesh", "sockets", `${meshSlug}.sock`);
6646
- }
6647
- function socketDir() {
6648
- return join5(homedir5(), ".claudemesh", "sockets");
6696
+ return join6(homedir5(), ".claudemesh", "sockets", `${meshSlug}.sock`);
6649
6697
  }
6650
6698
  function frame(obj) {
6651
6699
  return JSON.stringify(obj) + `
@@ -6841,36 +6889,6 @@ var init_peers = __esm(() => {
6841
6889
  };
6842
6890
  });
6843
6891
 
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
6892
  // src/daemon/local-token.ts
6875
6893
  import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "node:fs";
6876
6894
  import { dirname as dirname3 } from "node:path";
@@ -8888,7 +8906,7 @@ function makeHandler(opts) {
8888
8906
  respond(res, 200, {
8889
8907
  daemon_version: VERSION,
8890
8908
  ipc_api: "v1",
8891
- ipc_features: ["version", "health", "send", "inbox", "events", "peers", "profile"],
8909
+ ipc_features: ["version", "health", "send", "inbox", "events", "peers", "profile", "skills"],
8892
8910
  schema_version: 1
8893
8911
  });
8894
8912
  return;
@@ -8918,6 +8936,42 @@ function makeHandler(opts) {
8918
8936
  }
8919
8937
  return;
8920
8938
  }
8939
+ if (req.method === "GET" && url.pathname === "/v1/skills") {
8940
+ if (!opts.broker) {
8941
+ respond(res, 503, { error: "broker not initialised" });
8942
+ return;
8943
+ }
8944
+ const query = url.searchParams.get("query") ?? undefined;
8945
+ try {
8946
+ const skills = await opts.broker.listSkills(query);
8947
+ respond(res, 200, { skills });
8948
+ } catch (e) {
8949
+ respond(res, 502, { error: "broker_unreachable", detail: String(e) });
8950
+ }
8951
+ return;
8952
+ }
8953
+ if (req.method === "GET" && url.pathname.startsWith("/v1/skills/")) {
8954
+ if (!opts.broker) {
8955
+ respond(res, 503, { error: "broker not initialised" });
8956
+ return;
8957
+ }
8958
+ const name = decodeURIComponent(url.pathname.slice("/v1/skills/".length));
8959
+ if (!name) {
8960
+ respond(res, 400, { error: "missing skill name" });
8961
+ return;
8962
+ }
8963
+ try {
8964
+ const skill = await opts.broker.getSkill(name);
8965
+ if (!skill) {
8966
+ respond(res, 404, { error: "skill_not_found", name });
8967
+ return;
8968
+ }
8969
+ respond(res, 200, { skill });
8970
+ } catch (e) {
8971
+ respond(res, 502, { error: "broker_unreachable", detail: String(e) });
8972
+ }
8973
+ return;
8974
+ }
8921
8975
  if (req.method === "POST" && url.pathname === "/v1/profile") {
8922
8976
  if (!opts.broker) {
8923
8977
  respond(res, 503, { error: "broker not initialised" });
@@ -9198,6 +9252,8 @@ class DaemonBrokerClient {
9198
9252
  helloTimer = null;
9199
9253
  pendingAcks = new Map;
9200
9254
  peerListResolvers = new Map;
9255
+ skillListResolvers = new Map;
9256
+ skillDataResolvers = new Map;
9201
9257
  sessionPubkey = null;
9202
9258
  sessionSecretKey = null;
9203
9259
  opens = [];
@@ -9318,6 +9374,26 @@ class DaemonBrokerClient {
9318
9374
  }
9319
9375
  return;
9320
9376
  }
9377
+ if (msg.type === "skill_list") {
9378
+ const reqId = String(msg._reqId ?? "");
9379
+ const pending = this.skillListResolvers.get(reqId);
9380
+ if (pending) {
9381
+ this.skillListResolvers.delete(reqId);
9382
+ clearTimeout(pending.timer);
9383
+ pending.resolve(Array.isArray(msg.skills) ? msg.skills : []);
9384
+ }
9385
+ return;
9386
+ }
9387
+ if (msg.type === "skill_data") {
9388
+ const reqId = String(msg._reqId ?? "");
9389
+ const pending = this.skillDataResolvers.get(reqId);
9390
+ if (pending) {
9391
+ this.skillDataResolvers.delete(reqId);
9392
+ clearTimeout(pending.timer);
9393
+ pending.resolve(msg.skill ?? null);
9394
+ }
9395
+ return;
9396
+ }
9321
9397
  if (msg.type === "push" || msg.type === "inbound") {
9322
9398
  this.opts.onPush?.(msg);
9323
9399
  return;
@@ -9400,6 +9476,44 @@ class DaemonBrokerClient {
9400
9476
  }
9401
9477
  });
9402
9478
  }
9479
+ async listSkills(query, timeoutMs = 5000) {
9480
+ if (this._status !== "open" || !this.ws)
9481
+ return [];
9482
+ return new Promise((resolve) => {
9483
+ const reqId = `sl-${++this.reqCounter}`;
9484
+ const timer = setTimeout(() => {
9485
+ if (this.skillListResolvers.delete(reqId))
9486
+ resolve([]);
9487
+ }, timeoutMs);
9488
+ this.skillListResolvers.set(reqId, { resolve, timer });
9489
+ try {
9490
+ this.ws.send(JSON.stringify({ type: "list_skills", query, _reqId: reqId }));
9491
+ } catch {
9492
+ this.skillListResolvers.delete(reqId);
9493
+ clearTimeout(timer);
9494
+ resolve([]);
9495
+ }
9496
+ });
9497
+ }
9498
+ async getSkill(name, timeoutMs = 5000) {
9499
+ if (this._status !== "open" || !this.ws)
9500
+ return null;
9501
+ return new Promise((resolve) => {
9502
+ const reqId = `sg-${++this.reqCounter}`;
9503
+ const timer = setTimeout(() => {
9504
+ if (this.skillDataResolvers.delete(reqId))
9505
+ resolve(null);
9506
+ }, timeoutMs);
9507
+ this.skillDataResolvers.set(reqId, { resolve, timer });
9508
+ try {
9509
+ this.ws.send(JSON.stringify({ type: "get_skill", name, _reqId: reqId }));
9510
+ } catch {
9511
+ this.skillDataResolvers.delete(reqId);
9512
+ clearTimeout(timer);
9513
+ resolve(null);
9514
+ }
9515
+ });
9516
+ }
9403
9517
  setProfile(profile) {
9404
9518
  if (this._status !== "open" || !this.ws)
9405
9519
  return;
@@ -9626,11 +9740,14 @@ async function handleBrokerPush(msg, ctx) {
9626
9740
  const brokerMessageId = stringOrNull(msg.messageId);
9627
9741
  const senderPubkey = stringOrNull(msg.senderPubkey) ?? "";
9628
9742
  const senderName = stringOrNull(msg.senderName) ?? senderPubkey.slice(0, 8);
9743
+ const senderMemberPk = stringOrNull(msg.senderMemberPubkey);
9629
9744
  const topic = stringOrNull(msg.topic);
9630
9745
  const replyToId = stringOrNull(msg.replyToId);
9631
9746
  const ciphertext = stringOrNull(msg.ciphertext) ?? "";
9632
9747
  const nonce = stringOrNull(msg.nonce) ?? "";
9633
9748
  const createdAt = stringOrNull(msg.createdAt);
9749
+ const priority = stringOrNull(msg.priority) ?? "next";
9750
+ const subtype = stringOrNull(msg.subtype);
9634
9751
  const clientMessageId = stringOrNull(msg.client_message_id) ?? brokerMessageId ?? randomUUID5();
9635
9752
  const body = await decryptOrFallback({
9636
9753
  ciphertext,
@@ -9660,9 +9777,12 @@ async function handleBrokerPush(msg, ctx) {
9660
9777
  client_message_id: clientMessageId,
9661
9778
  broker_message_id: brokerMessageId,
9662
9779
  sender_pubkey: senderPubkey,
9780
+ sender_member_pubkey: senderMemberPk,
9663
9781
  sender_name: senderName,
9664
9782
  topic,
9665
9783
  reply_to_id: replyToId,
9784
+ priority,
9785
+ ...subtype ? { subtype } : {},
9666
9786
  body,
9667
9787
  created_at: createdAt
9668
9788
  });
@@ -10761,6 +10881,7 @@ function installStatusLine() {
10761
10881
  function runInstall(args = []) {
10762
10882
  const skipHooks = args.includes("--no-hooks");
10763
10883
  const skipSkill = args.includes("--no-skill");
10884
+ const skipService = args.includes("--no-service");
10764
10885
  const wantStatusLine = args.includes("--status-line");
10765
10886
  render.section("claudemesh install");
10766
10887
  const entry = resolveEntry();
@@ -10842,10 +10963,25 @@ function runInstall(args = []) {
10842
10963
  }
10843
10964
  }
10844
10965
  let hasMeshes = false;
10966
+ let primaryMesh;
10845
10967
  try {
10846
10968
  const meshConfig = readConfig();
10847
10969
  hasMeshes = meshConfig.meshes.length > 0;
10970
+ primaryMesh = meshConfig.meshes[0]?.slug;
10848
10971
  } catch {}
10972
+ if (!skipService && hasMeshes && primaryMesh) {
10973
+ try {
10974
+ installDaemonService(entry, primaryMesh);
10975
+ } catch (e) {
10976
+ render.warn(`daemon service install failed: ${e instanceof Error ? e.message : String(e)}`, "Run `claudemesh daemon install-service --mesh <slug>` to retry.");
10977
+ }
10978
+ } else if (skipService) {
10979
+ render.info(dim("· Daemon service skipped (--no-service)"));
10980
+ render.info(dim(" MCP integration will fail at boot until you start the daemon manually:"));
10981
+ render.info(dim(" claudemesh daemon up --mesh <slug>"));
10982
+ } else if (!hasMeshes) {
10983
+ render.info(dim("· Daemon service deferred — join a mesh first, then run install again."));
10984
+ }
10849
10985
  render.blank();
10850
10986
  render.warn(`${bold("RESTART CLAUDE CODE")} ${yellow("for MCP tools to appear.")}`);
10851
10987
  if (!hasMeshes) {
@@ -10863,6 +10999,40 @@ function runInstall(args = []) {
10863
10999
  render.info(dim(` claudemesh install --status-line # live peer count in Claude Code`));
10864
11000
  render.info(dim(` claudemesh completions zsh # shell completions`));
10865
11001
  }
11002
+ function installDaemonService(binaryEntry, meshSlug) {
11003
+ const {
11004
+ installService: installService2,
11005
+ detectPlatform: detectPlatform2
11006
+ } = (init_service_install(), __toCommonJS(exports_service_install));
11007
+ const platform6 = detectPlatform2();
11008
+ if (!platform6) {
11009
+ render.info(dim(`· Daemon service skipped — unsupported platform: ${process.platform}`));
11010
+ return;
11011
+ }
11012
+ let binary = process.argv[1] ?? binaryEntry;
11013
+ if (!binary || /\.ts$/.test(binary) || /node_modules|src\/entrypoints/.test(binary)) {
11014
+ try {
11015
+ const { execSync: execSync3 } = __require("node:child_process");
11016
+ binary = execSync3("which claudemesh", { encoding: "utf8" }).trim();
11017
+ } catch {
11018
+ 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 + "`");
11019
+ return;
11020
+ }
11021
+ }
11022
+ const r = installService2({ binaryPath: binary, meshSlug });
11023
+ render.ok(`daemon service installed (${r.platform})`);
11024
+ render.kv([
11025
+ ["unit", dim(r.unitPath)],
11026
+ ["mesh", dim(meshSlug)]
11027
+ ]);
11028
+ try {
11029
+ const { execSync: execSync3 } = __require("node:child_process");
11030
+ execSync3(r.bootCommand, { stdio: "ignore" });
11031
+ render.ok("daemon started");
11032
+ } catch (e) {
11033
+ render.warn(`daemon service installed but failed to start: ${e instanceof Error ? e.message : String(e)}`, `Run manually: ${r.bootCommand}`);
11034
+ }
11035
+ }
10866
11036
  function runUninstall() {
10867
11037
  render.section("claudemesh uninstall");
10868
11038
  if (removeMcpServer()) {
@@ -15958,435 +16128,194 @@ var init_member = __esm(() => {
15958
16128
  init_exit_codes();
15959
16129
  });
15960
16130
 
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
- };
16131
+ // src/mcp/server.ts
16132
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
16133
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
16134
+ import {
16135
+ ListToolsRequestSchema,
16136
+ CallToolRequestSchema,
16137
+ ListPromptsRequestSchema,
16138
+ GetPromptRequestSchema,
16139
+ ListResourcesRequestSchema,
16140
+ ReadResourceRequestSchema
16141
+ } from "@modelcontextprotocol/sdk/types.js";
16142
+ import { existsSync as existsSync28 } from "node:fs";
16143
+ import { request as httpRequest2 } from "node:http";
16144
+ async function daemonReady() {
16145
+ for (let i = 0;i < DAEMON_BOOT_RETRIES; i++) {
16146
+ if (existsSync28(DAEMON_PATHS.SOCK_FILE))
16147
+ return true;
16148
+ await new Promise((r) => setTimeout(r, DAEMON_BOOT_RETRY_MS));
15981
16149
  }
15982
- return { ok: true, spec: match.pubkey };
16150
+ return false;
15983
16151
  }
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
- }
16152
+ function bailNoDaemon() {
16153
+ process.stderr.write(`[claudemesh] daemon is not running.
16154
+ ` + ` Start it: claudemesh daemon up --mesh <slug>
16155
+ ` + ` Or install as service: claudemesh daemon install-service --mesh <slug>
16156
+ ` + ` Diagnose: claudemesh doctor
16157
+ ` + `
16158
+ ` + ` As of 1.24.0 the daemon is required for in-Claude-Code use of
16159
+ ` + ` claudemesh. The CLI itself (claudemesh send/peer/inbox/...) still
16160
+ ` + ` works without a daemon.
16161
+ `);
16162
+ process.exit(1);
16055
16163
  }
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) => {
16164
+ function daemonGet(path2) {
16165
+ return new Promise((resolve3, reject) => {
16166
+ const req = httpRequest2({ socketPath: DAEMON_PATHS.SOCK_FILE, path: path2, method: "GET", timeout: 5000 }, (res) => {
16167
+ const chunks = [];
16168
+ res.on("data", (c) => chunks.push(c));
16169
+ res.on("end", () => {
16170
+ const text = Buffer.concat(chunks).toString("utf8");
16171
+ let body = null;
16072
16172
  try {
16073
- socket.write(frame(res));
16074
- } catch {}
16173
+ body = JSON.parse(text);
16174
+ } catch {
16175
+ body = text;
16176
+ }
16177
+ resolve3({ status: res.statusCode ?? 0, body });
16075
16178
  });
16076
- }
16179
+ });
16180
+ req.on("error", reject);
16181
+ req.on("timeout", () => req.destroy(new Error("daemon_ipc_timeout")));
16182
+ req.end();
16077
16183
  });
16078
- socket.on("error", () => {});
16079
16184
  }
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)}
16185
+ function subscribeEvents(onEvent) {
16186
+ let active = true;
16187
+ let req = null;
16188
+ const connect = () => {
16189
+ if (!active)
16190
+ return;
16191
+ req = httpRequest2({
16192
+ socketPath: DAEMON_PATHS.SOCK_FILE,
16193
+ path: "/v1/events",
16194
+ method: "GET",
16195
+ headers: { Accept: "text/event-stream" }
16196
+ });
16197
+ let buffer = "";
16198
+ req.on("response", (res) => {
16199
+ res.setEncoding("utf8");
16200
+ res.on("data", (chunk) => {
16201
+ buffer += chunk;
16202
+ let idx;
16203
+ while ((idx = buffer.indexOf(`
16204
+
16205
+ `)) >= 0) {
16206
+ const block = buffer.slice(0, idx);
16207
+ buffer = buffer.slice(idx + 2);
16208
+ if (!block.trim())
16209
+ continue;
16210
+ let kind = "message";
16211
+ let dataLine = "";
16212
+ for (const line of block.split(`
16213
+ `)) {
16214
+ if (line.startsWith(":"))
16215
+ continue;
16216
+ if (line.startsWith("event:"))
16217
+ kind = line.slice(6).trim();
16218
+ else if (line.startsWith("data:"))
16219
+ dataLine = line.slice(5).trim();
16220
+ }
16221
+ if (!dataLine)
16222
+ continue;
16223
+ try {
16224
+ const parsed = JSON.parse(dataLine);
16225
+ onEvent({ kind, ts: String(parsed.ts ?? ""), data: parsed });
16226
+ } catch {}
16227
+ }
16228
+ });
16229
+ res.on("end", () => {
16230
+ if (active) {
16231
+ process.stderr.write(`[claudemesh-mcp] sse stream ended; reconnecting in 1s
16096
16232
  `);
16097
- return null;
16098
- }
16099
- server.on("error", (err) => {
16100
- process.stderr.write(`[claudemesh] bridge: ${String(err)}
16233
+ setTimeout(connect, 1000);
16234
+ }
16235
+ });
16236
+ res.on("error", (err) => process.stderr.write(`[claudemesh-mcp] sse error: ${err.message}
16237
+ `));
16238
+ });
16239
+ req.on("error", (err) => {
16240
+ process.stderr.write(`[claudemesh-mcp] sse connect error: ${err.message}
16101
16241
  `);
16102
- });
16103
- try {
16104
- chmodSync6(path2, 384);
16105
- } catch {}
16106
- let stopped = false;
16242
+ if (active)
16243
+ setTimeout(connect, 2000);
16244
+ });
16245
+ req.end();
16246
+ };
16247
+ connect();
16107
16248
  return {
16108
- path: path2,
16109
- stop() {
16110
- if (stopped)
16111
- return;
16112
- stopped = true;
16113
- try {
16114
- server.close();
16115
- } catch {}
16249
+ close: () => {
16250
+ active = false;
16116
16251
  try {
16117
- unlinkSync5(path2);
16252
+ req?.destroy();
16118
16253
  } catch {}
16119
16254
  }
16120
16255
  };
16121
16256
  }
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
16257
  async function startMcpServer() {
16174
16258
  const serviceIdx = process.argv.indexOf("--service");
16175
16259
  if (serviceIdx !== -1 && process.argv[serviceIdx + 1]) {
16176
16260
  return startServiceProxy(process.argv[serviceIdx + 1]);
16177
16261
  }
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
- }));
16262
+ const ok = await daemonReady();
16263
+ if (!ok)
16264
+ bailNoDaemon();
16265
+ const server = new Server({ name: "claudemesh", version: VERSION }, { capabilities: { tools: {}, prompts: {}, resources: {} } });
16266
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [] }));
16337
16267
  server.setRequestHandler(ListPromptsRequestSchema, async () => {
16338
- const client = allClients()[0];
16339
- if (!client)
16268
+ try {
16269
+ const { status, body } = await daemonGet("/v1/skills");
16270
+ if (status !== 200)
16271
+ return { prompts: [] };
16272
+ const skills = body?.skills ?? [];
16273
+ return { prompts: skills.map((s) => ({ name: s.name, description: s.description, arguments: [] })) };
16274
+ } catch {
16340
16275
  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
- };
16276
+ }
16349
16277
  });
16350
16278
  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)
16279
+ const name = req.params.name;
16280
+ const { status, body } = await daemonGet(`/v1/skills/${encodeURIComponent(name)}`);
16281
+ if (status === 404)
16357
16282
  throw new Error(`Skill "${name}" not found in the mesh`);
16283
+ if (status !== 200)
16284
+ throw new Error(`daemon returned ${status} fetching skill`);
16285
+ const skill = body.skill;
16358
16286
  let content = skill.instructions;
16359
- const manifest = skill.manifest;
16360
- if (manifest && typeof manifest === "object") {
16287
+ const m = skill.manifest;
16288
+ if (m && typeof m === "object") {
16361
16289
  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)
16290
+ if (m.description)
16291
+ fm.push(`description: "${m.description}"`);
16292
+ if (m.when_to_use)
16293
+ fm.push(`when_to_use: "${m.when_to_use}"`);
16294
+ if (Array.isArray(m.allowed_tools) && m.allowed_tools.length) {
16367
16295
  fm.push(`allowed-tools:
16368
- ${manifest.allowed_tools.map((t) => ` - ${t}`).join(`
16296
+ ${m.allowed_tools.map((t) => ` - ${t}`).join(`
16369
16297
  `)}`);
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)
16298
+ }
16299
+ if (m.model)
16300
+ fm.push(`model: ${m.model}`);
16301
+ if (m.context)
16302
+ fm.push(`context: ${m.context}`);
16303
+ if (m.agent)
16304
+ fm.push(`agent: ${m.agent}`);
16305
+ if (m.user_invocable === false)
16377
16306
  fm.push(`user-invocable: false`);
16378
- if (manifest.argument_hint)
16379
- fm.push(`argument-hint: "${manifest.argument_hint}"`);
16307
+ if (m.argument_hint)
16308
+ fm.push(`argument-hint: "${m.argument_hint}"`);
16380
16309
  fm.push(`---
16381
16310
  `);
16382
16311
  if (fm.length > 3)
16383
16312
  content = fm.join(`
16384
16313
  `) + 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(", ")}.` : "";
16314
+ if (m.context === "fork") {
16315
+ const agentType = m.agent || "general-purpose";
16316
+ const modelHint = m.model ? `, model: "${m.model}"` : "";
16317
+ const toolsHint = m.allowed_tools?.length ? `
16318
+ Only use these tools: ${m.allowed_tools.join(", ")}.` : "";
16390
16319
  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
16320
 
16392
16321
  ` + content;
@@ -16394,273 +16323,127 @@ Only use these tools: ${manifest.allowed_tools.join(", ")}.` : "";
16394
16323
  }
16395
16324
  return {
16396
16325
  description: skill.description,
16397
- messages: [
16398
- {
16399
- role: "user",
16400
- content: { type: "text", text: content }
16401
- }
16402
- ]
16326
+ messages: [{ role: "user", content: { type: "text", text: content } }]
16403
16327
  };
16404
16328
  });
16405
16329
  server.setRequestHandler(ListResourcesRequestSchema, async () => {
16406
- const client = allClients()[0];
16407
- if (!client)
16330
+ try {
16331
+ const { body } = await daemonGet("/v1/skills");
16332
+ const skills = body?.skills ?? [];
16333
+ return {
16334
+ resources: skills.map((s) => ({
16335
+ uri: `skill://claudemesh/${encodeURIComponent(s.name)}`,
16336
+ name: s.name,
16337
+ description: s.description,
16338
+ mimeType: "text/markdown"
16339
+ }))
16340
+ };
16341
+ } catch {
16408
16342
  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
- };
16343
+ }
16418
16344
  });
16419
16345
  server.setRequestHandler(ReadResourceRequestSchema, async (req) => {
16420
- const { uri } = req.params;
16421
- const match = uri.match(/^skill:\/\/claudemesh\/(.+)$/);
16422
- if (!match)
16346
+ const uri = req.params.uri;
16347
+ const m = uri.match(/^skill:\/\/claudemesh\/(.+)$/);
16348
+ if (!m)
16423
16349
  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)
16350
+ const name = decodeURIComponent(m[1]);
16351
+ const { status, body } = await daemonGet(`/v1/skills/${encodeURIComponent(name)}`);
16352
+ if (status === 404)
16430
16353
  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(`
16354
+ if (status !== 200)
16355
+ throw new Error(`daemon returned ${status} fetching skill`);
16356
+ const skill = body.skill;
16357
+ const fm = ["---"];
16358
+ fm.push(`name: ${skill.name}`);
16359
+ fm.push(`description: "${skill.description}"`);
16360
+ if (skill.tags?.length)
16361
+ fm.push(`tags: [${skill.tags.join(", ")}]`);
16362
+ const mf = skill.manifest;
16363
+ if (mf && typeof mf === "object") {
16364
+ if (mf.when_to_use)
16365
+ fm.push(`when_to_use: "${mf.when_to_use}"`);
16366
+ if (Array.isArray(mf.allowed_tools) && mf.allowed_tools.length) {
16367
+ fm.push(`allowed-tools:
16368
+ ${mf.allowed_tools.map((t) => ` - ${t}`).join(`
16443
16369
  `)}`);
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(`---
16370
+ }
16371
+ if (mf.model)
16372
+ fm.push(`model: ${mf.model}`);
16373
+ if (mf.context)
16374
+ fm.push(`context: ${mf.context}`);
16375
+ }
16376
+ fm.push(`---
16456
16377
  `);
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
- };
16468
- });
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(() => {});
16378
+ return { contents: [{ uri, mimeType: "text/markdown", text: fm.join(`
16379
+ `) + skill.instructions }] };
16481
16380
  });
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
- }
16381
+ const sub = subscribeEvents(async (ev) => {
16382
+ if (ev.kind === "message") {
16383
+ const d = ev.data;
16384
+ const fromName = String(d.sender_name ?? "unknown");
16385
+ const fromMember = String(d.sender_member_pubkey ?? d.sender_pubkey ?? "");
16386
+ const body = String(d.body ?? "(decrypt failed)");
16387
+ const priority = String(d.priority ?? "next");
16388
+ const prioBadge = priority === "now" ? "[URGENT] " : priority === "low" ? "[low] " : "";
16389
+ const topicTag = d.topic ? ` (#${d.topic})` : "";
16390
+ const content = `${prioBadge}${fromName}${topicTag}: ${body}`;
16391
+ try {
16392
+ await server.notification({
16393
+ method: "notifications/claude/channel",
16394
+ params: {
16395
+ content,
16396
+ meta: {
16397
+ from_id: fromMember,
16398
+ from_pubkey: fromMember,
16399
+ from_session_pubkey: String(d.sender_pubkey ?? ""),
16400
+ from_name: fromName,
16401
+ mesh_slug: String(d.mesh ?? ""),
16402
+ priority,
16403
+ message_id: String(d.broker_message_id ?? d.id ?? ""),
16404
+ client_message_id: String(d.client_message_id ?? ""),
16405
+ ...d.topic ? { topic: String(d.topic) } : {},
16406
+ ...d.reply_to_id ? { reply_to_id: String(d.reply_to_id) } : {},
16407
+ ...d.subtype ? { subtype: String(d.subtype) } : {}
16598
16408
  }
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}
16409
+ }
16410
+ });
16411
+ } catch (err) {
16412
+ process.stderr.write(`[claudemesh-mcp] channel emit failed: ${err}
16604
16413
  `);
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;
16414
+ }
16415
+ } else if (ev.kind === "peer_join" || ev.kind === "peer_leave" || ev.kind === "system") {
16416
+ const d = ev.data;
16417
+ const eventName = String(d.event ?? ev.kind);
16418
+ let content;
16419
+ if (ev.kind === "peer_join") {
16420
+ content = `[system] Peer "${String(d.name ?? "unknown")}" joined the mesh`;
16421
+ } else if (ev.kind === "peer_leave") {
16422
+ content = `[system] Peer "${String(d.name ?? "unknown")}" left the mesh`;
16423
+ } else {
16424
+ content = `[system] ${eventName}: ${JSON.stringify(d).slice(0, 240)}`;
16425
+ }
16642
16426
  try {
16643
- const peers = await welcomeClient.listPeers();
16644
- const peerNames = peers.filter((p) => p.displayName !== myName).map((p) => p.displayName).join(", ") || "none";
16645
16427
  await server.notification({
16646
16428
  method: "notifications/claude/channel",
16647
16429
  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 }
16430
+ content,
16431
+ meta: {
16432
+ kind: "system",
16433
+ event: eventName,
16434
+ mesh_slug: String(d.mesh ?? "")
16435
+ }
16650
16436
  }
16651
16437
  });
16652
16438
  } catch {}
16653
- }, 2000);
16654
- }
16439
+ }
16440
+ });
16441
+ const transport = new StdioServerTransport;
16442
+ await server.connect(transport);
16655
16443
  const keepalive = setInterval(() => {}, 1000);
16656
16444
  const shutdown = () => {
16657
16445
  clearInterval(keepalive);
16658
- for (const b of bridges) {
16659
- try {
16660
- b.stop();
16661
- } catch {}
16662
- }
16663
- stopAll();
16446
+ sub.close();
16664
16447
  process.exit(0);
16665
16448
  };
16666
16449
  process.on("SIGTERM", shutdown);
@@ -16691,9 +16474,8 @@ async function startServiceProxy(serviceName) {
16691
16474
  tools = fetched;
16692
16475
  } catch {
16693
16476
  const cached2 = client.serviceCatalog.find((s) => s.name === serviceName);
16694
- if (cached2) {
16477
+ if (cached2)
16695
16478
  tools = cached2.tools;
16696
- }
16697
16479
  }
16698
16480
  if (tools.length === 0) {
16699
16481
  process.stderr.write(`[mesh:${serviceName}] no tools found — service may not be running
@@ -16718,12 +16500,7 @@ async function startServiceProxy(serviceName) {
16718
16500
  }
16719
16501
  if (client.status !== "open") {
16720
16502
  return {
16721
- content: [
16722
- {
16723
- type: "text",
16724
- text: `Service temporarily unavailable — broker reconnecting. Retry in a few seconds.`
16725
- }
16726
- ],
16503
+ content: [{ type: "text", text: "Service temporarily unavailable — broker reconnecting. Retry in a few seconds." }],
16727
16504
  isError: true
16728
16505
  };
16729
16506
  }
@@ -16731,23 +16508,13 @@ async function startServiceProxy(serviceName) {
16731
16508
  try {
16732
16509
  const result = await client.mcpCall(serviceName, toolName, args);
16733
16510
  if (result.error) {
16734
- return {
16735
- content: [{ type: "text", text: `Error: ${result.error}` }],
16736
- isError: true
16737
- };
16511
+ return { content: [{ type: "text", text: `Error: ${result.error}` }], isError: true };
16738
16512
  }
16739
16513
  const resultText = typeof result.result === "string" ? result.result : JSON.stringify(result.result, null, 2);
16740
- return {
16741
- content: [{ type: "text", text: resultText }]
16742
- };
16514
+ return { content: [{ type: "text", text: resultText }] };
16743
16515
  } catch (e) {
16744
16516
  return {
16745
- content: [
16746
- {
16747
- type: "text",
16748
- text: `Call failed: ${e instanceof Error ? e.message : String(e)}`
16749
- }
16750
- ],
16517
+ content: [{ type: "text", text: `Call failed: ${e instanceof Error ? e.message : String(e)}` }],
16751
16518
  isError: true
16752
16519
  };
16753
16520
  }
@@ -16763,9 +16530,7 @@ async function startServiceProxy(serviceName) {
16763
16530
  const newTools = push.eventData?.tools;
16764
16531
  if (Array.isArray(newTools)) {
16765
16532
  tools = newTools;
16766
- server.notification({
16767
- method: "notifications/tools/list_changed"
16768
- }).catch(() => {});
16533
+ server.notification({ method: "notifications/tools/list_changed" }).catch(() => {});
16769
16534
  }
16770
16535
  }
16771
16536
  });
@@ -16780,13 +16545,12 @@ async function startServiceProxy(serviceName) {
16780
16545
  process.on("SIGTERM", shutdown);
16781
16546
  process.on("SIGINT", shutdown);
16782
16547
  }
16783
- var peerNameCache, peerNameCacheAge = 0, CACHE_TTL_MS = 30000;
16784
- var init_server3 = __esm(() => {
16785
- init_definitions();
16548
+ var DAEMON_BOOT_RETRIES = 4, DAEMON_BOOT_RETRY_MS = 500;
16549
+ var init_server2 = __esm(() => {
16550
+ init_paths2();
16551
+ init_urls();
16786
16552
  init_facade();
16787
16553
  init_facade8();
16788
- init_server2();
16789
- peerNameCache = new Map;
16790
16554
  });
16791
16555
 
16792
16556
  // src/commands/mcp.ts
@@ -16801,7 +16565,7 @@ async function runMcp() {
16801
16565
  process.exit(0);
16802
16566
  }
16803
16567
  var init_mcp = __esm(() => {
16804
- init_server3();
16568
+ init_server2();
16805
16569
  });
16806
16570
 
16807
16571
  // src/commands/hook.ts
@@ -18612,4 +18376,4 @@ main().catch((err) => {
18612
18376
  process.exit(EXIT.INTERNAL_ERROR);
18613
18377
  });
18614
18378
 
18615
- //# debugId=DF34DB4C8E61E91564756E2164756E21
18379
+ //# debugId=BAEC2C4FE7977E7264756E2164756E21