claudemesh-cli 1.22.1 → 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.22.1", 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",
@@ -962,6 +977,13 @@ var ready = false;
962
977
  var init_keypair = () => {};
963
978
 
964
979
  // src/services/crypto/file-crypto.ts
980
+ var exports_file_crypto = {};
981
+ __export(exports_file_crypto, {
982
+ sealKeyForPeer: () => sealKeyForPeer,
983
+ openSealedKey: () => openSealedKey,
984
+ encryptFile: () => encryptFile,
985
+ decryptFile: () => decryptFile
986
+ });
965
987
  async function encryptFile(plaintext) {
966
988
  const s = await ensureSodium();
967
989
  const key = s.randombytes_buf(s.crypto_secretbox_KEYBYTES);
@@ -3301,44 +3323,10 @@ var init_ws_client = __esm(() => {
3301
3323
  });
3302
3324
 
3303
3325
  // src/services/broker/manager.ts
3304
- async function ensureClient(mesh) {
3305
- const existing = clients.get(mesh.meshId);
3306
- if (existing)
3307
- return existing;
3308
- const isDebug2 = process.env.CLAUDEMESH_DEBUG === "1" || process.env.CLAUDEMESH_DEBUG === "true";
3309
- const client = new BrokerClient(mesh, { debug: isDebug2, displayName: configDisplayName });
3310
- clients.set(mesh.meshId, client);
3311
- try {
3312
- await client.connect();
3313
- for (const g of configGroups ?? []) {
3314
- try {
3315
- await client.joinGroup(g.name, g.role);
3316
- } catch {}
3317
- }
3318
- } catch (err) {
3319
- process.stderr.write(`[claudemesh] broker connect failed for ${mesh.slug}: ${err instanceof Error ? err.message : err} (will retry)
3320
- `);
3321
- }
3322
- return client;
3323
- }
3324
- async function startClients(config) {
3325
- configDisplayName = config.displayName;
3326
- configGroups = config.groups ?? [];
3327
- await Promise.allSettled(config.meshes.map(ensureClient));
3328
- }
3329
- function allClients() {
3330
- return [...clients.values()];
3331
- }
3332
- function stopAll() {
3333
- for (const c of clients.values())
3334
- c.close();
3335
- clients.clear();
3336
- }
3337
- var clients, configDisplayName, configGroups;
3326
+ var clients;
3338
3327
  var init_manager = __esm(() => {
3339
3328
  init_ws_client();
3340
3329
  clients = new Map;
3341
- configGroups = [];
3342
3330
  });
3343
3331
 
3344
3332
  // src/services/broker/envelope.ts
@@ -3593,6 +3581,42 @@ var init_spinner = __esm(() => {
3593
3581
  FRAME_HEIGHT = H;
3594
3582
  });
3595
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
+
3596
3620
  // src/commands/launch.ts
3597
3621
  var exports_launch = {};
3598
3622
  __export(exports_launch, {
@@ -3602,8 +3626,41 @@ import { spawnSync as spawnSync2 } from "node:child_process";
3602
3626
  import { randomUUID } from "node:crypto";
3603
3627
  import { mkdtempSync, writeFileSync as writeFileSync4, rmSync, readdirSync, statSync, existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
3604
3628
  import { tmpdir, hostname as hostname2, homedir as homedir3 } from "node:os";
3605
- import { join as join3 } from "node:path";
3629
+ import { join as join4 } from "node:path";
3606
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
+ }
3607
3664
  function parseGroupsString(raw) {
3608
3665
  return raw.split(",").map((s) => s.trim()).filter(Boolean).map((token) => {
3609
3666
  const idx = token.indexOf(":");
@@ -3923,14 +3980,15 @@ async function runLaunch(flags, rawArgs) {
3923
3980
  for (const entry of readdirSync(tmpBase)) {
3924
3981
  if (!entry.startsWith("claudemesh-"))
3925
3982
  continue;
3926
- const full = join3(tmpBase, entry);
3983
+ const full = join4(tmpBase, entry);
3927
3984
  const age = Date.now() - statSync(full).mtimeMs;
3928
3985
  if (age > 3600000)
3929
3986
  rmSync(full, { recursive: true, force: true });
3930
3987
  }
3931
3988
  } catch {}
3989
+ await ensureDaemonRunning(mesh.slug, args.quiet);
3932
3990
  try {
3933
- const claudeConfigPath = join3(homedir3(), ".claude.json");
3991
+ const claudeConfigPath = join4(homedir3(), ".claude.json");
3934
3992
  if (existsSync5(claudeConfigPath)) {
3935
3993
  const claudeConfig = JSON.parse(readFileSync4(claudeConfigPath, "utf-8"));
3936
3994
  const mcpServers = claudeConfig.mcpServers ?? {};
@@ -3967,7 +4025,7 @@ async function runLaunch(flags, rawArgs) {
3967
4025
  console.log(" (Could not fetch service catalog — mesh services won't be natively available)");
3968
4026
  }
3969
4027
  }
3970
- const tmpDir = mkdtempSync(join3(tmpdir(), "claudemesh-"));
4028
+ const tmpDir = mkdtempSync(join4(tmpdir(), "claudemesh-"));
3971
4029
  const sessionConfig = {
3972
4030
  version: 1,
3973
4031
  meshes: [mesh],
@@ -3976,14 +4034,14 @@ async function runLaunch(flags, rawArgs) {
3976
4034
  ...parsedGroups.length > 0 ? { groups: parsedGroups } : {},
3977
4035
  messageMode
3978
4036
  };
3979
- writeFileSync4(join3(tmpDir, "config.json"), JSON.stringify(sessionConfig, null, 2) + `
4037
+ writeFileSync4(join4(tmpDir, "config.json"), JSON.stringify(sessionConfig, null, 2) + `
3980
4038
  `, "utf-8");
3981
4039
  if (!args.quiet) {
3982
4040
  printBanner(displayName, mesh.slug, role, parsedGroups, messageMode);
3983
4041
  }
3984
4042
  const meshMcpEntries = [];
3985
4043
  if (serviceCatalog.length > 0) {
3986
- const claudeConfigPath = join3(homedir3(), ".claude.json");
4044
+ const claudeConfigPath = join4(homedir3(), ".claude.json");
3987
4045
  let claudeConfig = {};
3988
4046
  try {
3989
4047
  claudeConfig = JSON.parse(readFileSync4(claudeConfigPath, "utf-8"));
@@ -4050,9 +4108,9 @@ async function runLaunch(flags, rawArgs) {
4050
4108
  let claudeBin = "claude";
4051
4109
  if (!isWindows2) {
4052
4110
  const candidates = [
4053
- join3(homedir3(), ".local", "bin", "claude"),
4111
+ join4(homedir3(), ".local", "bin", "claude"),
4054
4112
  "/usr/local/bin/claude",
4055
- join3(homedir3(), ".claude", "bin", "claude")
4113
+ join4(homedir3(), ".claude", "bin", "claude")
4056
4114
  ];
4057
4115
  for (const c of candidates) {
4058
4116
  if (existsSync5(c)) {
@@ -4064,7 +4122,7 @@ async function runLaunch(flags, rawArgs) {
4064
4122
  const cleanup = () => {
4065
4123
  if (meshMcpEntries.length > 0) {
4066
4124
  try {
4067
- const claudeConfigPath = join3(homedir3(), ".claude.json");
4125
+ const claudeConfigPath = join4(homedir3(), ".claude.json");
4068
4126
  const claudeConfig = JSON.parse(readFileSync4(claudeConfigPath, "utf-8"));
4069
4127
  const mcpServers = claudeConfig.mcpServers ?? {};
4070
4128
  for (const { key } of meshMcpEntries) {
@@ -4769,7 +4827,7 @@ __export(exports_join, {
4769
4827
  });
4770
4828
  import sodium3 from "libsodium-wrappers";
4771
4829
  import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync3 } from "node:fs";
4772
- import { join as join4, dirname as dirname2 } from "node:path";
4830
+ import { join as join5, dirname as dirname2 } from "node:path";
4773
4831
  import { homedir as homedir4, hostname as hostname3 } from "node:os";
4774
4832
  function deriveAppBaseUrl() {
4775
4833
  const override = process.env.CLAUDEMESH_APP_URL;
@@ -4889,8 +4947,8 @@ async function runJoin(args) {
4889
4947
  joinedAt: new Date().toISOString()
4890
4948
  });
4891
4949
  writeConfig(config);
4892
- const configDir = env.CLAUDEMESH_CONFIG_DIR ?? join4(homedir4(), ".claudemesh");
4893
- 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`);
4894
4952
  try {
4895
4953
  mkdirSync3(dirname2(inviteFile), { recursive: true });
4896
4954
  writeFileSync5(inviteFile, link, "utf-8");
@@ -6633,12 +6691,9 @@ var init_ban = __esm(() => {
6633
6691
 
6634
6692
  // src/services/bridge/protocol.ts
6635
6693
  import { homedir as homedir5 } from "node:os";
6636
- import { join as join5 } from "node:path";
6694
+ import { join as join6 } from "node:path";
6637
6695
  function socketPath(meshSlug) {
6638
- return join5(homedir5(), ".claudemesh", "sockets", `${meshSlug}.sock`);
6639
- }
6640
- function socketDir() {
6641
- return join5(homedir5(), ".claudemesh", "sockets");
6696
+ return join6(homedir5(), ".claudemesh", "sockets", `${meshSlug}.sock`);
6642
6697
  }
6643
6698
  function frame(obj) {
6644
6699
  return JSON.stringify(obj) + `
@@ -6834,36 +6889,6 @@ var init_peers = __esm(() => {
6834
6889
  };
6835
6890
  });
6836
6891
 
6837
- // src/daemon/paths.ts
6838
- import { join as join6 } from "node:path";
6839
- var DAEMON_PATHS, DAEMON_TCP_HOST = "127.0.0.1", DAEMON_TCP_DEFAULT_PORT = 47823;
6840
- var init_paths2 = __esm(() => {
6841
- init_paths();
6842
- DAEMON_PATHS = {
6843
- get DAEMON_DIR() {
6844
- return join6(PATHS.CONFIG_DIR, "daemon");
6845
- },
6846
- get PID_FILE() {
6847
- return join6(this.DAEMON_DIR, "daemon.pid");
6848
- },
6849
- get SOCK_FILE() {
6850
- return join6(this.DAEMON_DIR, "daemon.sock");
6851
- },
6852
- get TOKEN_FILE() {
6853
- return join6(this.DAEMON_DIR, "local-token");
6854
- },
6855
- get OUTBOX_DB() {
6856
- return join6(this.DAEMON_DIR, "outbox.db");
6857
- },
6858
- get INBOX_DB() {
6859
- return join6(this.DAEMON_DIR, "inbox.db");
6860
- },
6861
- get LOG_FILE() {
6862
- return join6(this.DAEMON_DIR, "daemon.log");
6863
- }
6864
- };
6865
- });
6866
-
6867
6892
  // src/daemon/local-token.ts
6868
6893
  import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "node:fs";
6869
6894
  import { dirname as dirname3 } from "node:path";
@@ -8881,7 +8906,7 @@ function makeHandler(opts) {
8881
8906
  respond(res, 200, {
8882
8907
  daemon_version: VERSION,
8883
8908
  ipc_api: "v1",
8884
- ipc_features: ["version", "health", "send", "inbox", "events", "peers", "profile"],
8909
+ ipc_features: ["version", "health", "send", "inbox", "events", "peers", "profile", "skills"],
8885
8910
  schema_version: 1
8886
8911
  });
8887
8912
  return;
@@ -8911,6 +8936,42 @@ function makeHandler(opts) {
8911
8936
  }
8912
8937
  return;
8913
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
+ }
8914
8975
  if (req.method === "POST" && url.pathname === "/v1/profile") {
8915
8976
  if (!opts.broker) {
8916
8977
  respond(res, 503, { error: "broker not initialised" });
@@ -9191,6 +9252,8 @@ class DaemonBrokerClient {
9191
9252
  helloTimer = null;
9192
9253
  pendingAcks = new Map;
9193
9254
  peerListResolvers = new Map;
9255
+ skillListResolvers = new Map;
9256
+ skillDataResolvers = new Map;
9194
9257
  sessionPubkey = null;
9195
9258
  sessionSecretKey = null;
9196
9259
  opens = [];
@@ -9311,6 +9374,26 @@ class DaemonBrokerClient {
9311
9374
  }
9312
9375
  return;
9313
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
+ }
9314
9397
  if (msg.type === "push" || msg.type === "inbound") {
9315
9398
  this.opts.onPush?.(msg);
9316
9399
  return;
@@ -9393,6 +9476,44 @@ class DaemonBrokerClient {
9393
9476
  }
9394
9477
  });
9395
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
+ }
9396
9517
  setProfile(profile) {
9397
9518
  if (this._status !== "open" || !this.ws)
9398
9519
  return;
@@ -9619,11 +9740,14 @@ async function handleBrokerPush(msg, ctx) {
9619
9740
  const brokerMessageId = stringOrNull(msg.messageId);
9620
9741
  const senderPubkey = stringOrNull(msg.senderPubkey) ?? "";
9621
9742
  const senderName = stringOrNull(msg.senderName) ?? senderPubkey.slice(0, 8);
9743
+ const senderMemberPk = stringOrNull(msg.senderMemberPubkey);
9622
9744
  const topic = stringOrNull(msg.topic);
9623
9745
  const replyToId = stringOrNull(msg.replyToId);
9624
9746
  const ciphertext = stringOrNull(msg.ciphertext) ?? "";
9625
9747
  const nonce = stringOrNull(msg.nonce) ?? "";
9626
9748
  const createdAt = stringOrNull(msg.createdAt);
9749
+ const priority = stringOrNull(msg.priority) ?? "next";
9750
+ const subtype = stringOrNull(msg.subtype);
9627
9751
  const clientMessageId = stringOrNull(msg.client_message_id) ?? brokerMessageId ?? randomUUID5();
9628
9752
  const body = await decryptOrFallback({
9629
9753
  ciphertext,
@@ -9653,9 +9777,12 @@ async function handleBrokerPush(msg, ctx) {
9653
9777
  client_message_id: clientMessageId,
9654
9778
  broker_message_id: brokerMessageId,
9655
9779
  sender_pubkey: senderPubkey,
9780
+ sender_member_pubkey: senderMemberPk,
9656
9781
  sender_name: senderName,
9657
9782
  topic,
9658
9783
  reply_to_id: replyToId,
9784
+ priority,
9785
+ ...subtype ? { subtype } : {},
9659
9786
  body,
9660
9787
  created_at: createdAt
9661
9788
  });
@@ -10754,6 +10881,7 @@ function installStatusLine() {
10754
10881
  function runInstall(args = []) {
10755
10882
  const skipHooks = args.includes("--no-hooks");
10756
10883
  const skipSkill = args.includes("--no-skill");
10884
+ const skipService = args.includes("--no-service");
10757
10885
  const wantStatusLine = args.includes("--status-line");
10758
10886
  render.section("claudemesh install");
10759
10887
  const entry = resolveEntry();
@@ -10835,10 +10963,25 @@ function runInstall(args = []) {
10835
10963
  }
10836
10964
  }
10837
10965
  let hasMeshes = false;
10966
+ let primaryMesh;
10838
10967
  try {
10839
10968
  const meshConfig = readConfig();
10840
10969
  hasMeshes = meshConfig.meshes.length > 0;
10970
+ primaryMesh = meshConfig.meshes[0]?.slug;
10841
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
+ }
10842
10985
  render.blank();
10843
10986
  render.warn(`${bold("RESTART CLAUDE CODE")} ${yellow("for MCP tools to appear.")}`);
10844
10987
  if (!hasMeshes) {
@@ -10856,6 +10999,40 @@ function runInstall(args = []) {
10856
10999
  render.info(dim(` claudemesh install --status-line # live peer count in Claude Code`));
10857
11000
  render.info(dim(` claudemesh completions zsh # shell completions`));
10858
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
+ }
10859
11036
  function runUninstall() {
10860
11037
  render.section("claudemesh uninstall");
10861
11038
  if (removeMcpServer()) {
@@ -12737,11 +12914,14 @@ var exports_platform_actions = {};
12737
12914
  __export(exports_platform_actions, {
12738
12915
  runWebhookList: () => runWebhookList,
12739
12916
  runWebhookDelete: () => runWebhookDelete,
12917
+ runWebhookCreate: () => runWebhookCreate,
12740
12918
  runWatchList: () => runWatchList,
12919
+ runWatchAdd: () => runWatchAdd,
12741
12920
  runVectorStore: () => runVectorStore,
12742
12921
  runVectorSearch: () => runVectorSearch,
12743
12922
  runVectorDelete: () => runVectorDelete,
12744
12923
  runVectorCollections: () => runVectorCollections,
12924
+ runVaultSet: () => runVaultSet,
12745
12925
  runVaultList: () => runVaultList,
12746
12926
  runVaultDelete: () => runVaultDelete,
12747
12927
  runUnwatch: () => runUnwatch,
@@ -13188,6 +13368,40 @@ async function runVaultDelete(key, opts) {
13188
13368
  return ok ? EXIT.SUCCESS : EXIT.NOT_FOUND;
13189
13369
  });
13190
13370
  }
13371
+ async function runVaultSet(key, value, opts) {
13372
+ if (!key || value == null) {
13373
+ render.err("Usage: claudemesh vault set <key> <value> [--type env|file] [--mount /path] [--description ...]");
13374
+ return EXIT.INVALID_ARGS;
13375
+ }
13376
+ const { encryptFile: encryptFile2, sealKeyForPeer: sealKeyForPeer2 } = await Promise.resolve().then(() => (init_file_crypto(), exports_file_crypto));
13377
+ const { getMeshConfig: getMeshConfig2 } = await Promise.resolve().then(() => (init_facade(), exports_facade));
13378
+ const { readConfig: readConfig2 } = await Promise.resolve().then(() => (init_facade(), exports_facade));
13379
+ const config = readConfig2();
13380
+ const slug = opts.mesh ?? (config.meshes.length === 1 ? config.meshes[0].slug : null);
13381
+ if (!slug) {
13382
+ render.err("multiple meshes joined; pass --mesh <slug>");
13383
+ return EXIT.INVALID_ARGS;
13384
+ }
13385
+ const mesh = getMeshConfig2(slug);
13386
+ if (!mesh) {
13387
+ render.err(`not joined to mesh "${slug}"`);
13388
+ return EXIT.NOT_FOUND;
13389
+ }
13390
+ const plaintext = new TextEncoder().encode(value);
13391
+ const enc = await encryptFile2(plaintext);
13392
+ const ciphertextB64 = Buffer.from(enc.ciphertext).toString("base64");
13393
+ const sealed = await sealKeyForPeer2(enc.key, mesh.pubkey);
13394
+ return await withMesh({ meshSlug: slug }, async (client) => {
13395
+ const ok = await client.vaultSet(key, ciphertextB64, enc.nonce, sealed, opts.entryType ?? "env", opts.mountPath, opts.description);
13396
+ if (opts.json)
13397
+ emitJson({ key, stored: ok });
13398
+ else if (ok)
13399
+ render.ok(`vault[${bold(key)}] stored`, dim(`(${ciphertextB64.length}b)`));
13400
+ else
13401
+ render.err(`vault set failed for "${key}"`);
13402
+ return ok ? EXIT.SUCCESS : EXIT.IO_ERROR;
13403
+ });
13404
+ }
13191
13405
  async function runWatchList(opts) {
13192
13406
  return await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
13193
13407
  const watches = await client.watchList();
@@ -13210,6 +13424,34 @@ async function runWatchList(opts) {
13210
13424
  return EXIT.SUCCESS;
13211
13425
  });
13212
13426
  }
13427
+ async function runWatchAdd(url, opts) {
13428
+ if (!url) {
13429
+ render.err("Usage: claudemesh watch add <url> [--label ...] [--interval <sec>] [--extract <css>] [--notify-on changed|always]");
13430
+ return EXIT.INVALID_ARGS;
13431
+ }
13432
+ return await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
13433
+ const result = await client.watch(url, {
13434
+ label: opts.label,
13435
+ interval: opts.interval,
13436
+ mode: opts.mode,
13437
+ extract: opts.extract,
13438
+ notify_on: opts.notifyOn
13439
+ });
13440
+ if (result?.error) {
13441
+ if (opts.json)
13442
+ emitJson({ ok: false, error: result.error });
13443
+ else
13444
+ render.err(`watch add failed: ${result.error}`);
13445
+ return EXIT.IO_ERROR;
13446
+ }
13447
+ const id = String(result?.id ?? result?.watch_id ?? "?");
13448
+ if (opts.json)
13449
+ emitJson({ ok: true, id, url, ...opts.label ? { label: opts.label } : {} });
13450
+ else
13451
+ render.ok(`watching ${clay(url)}`, dim(id.slice(0, 8)));
13452
+ return EXIT.SUCCESS;
13453
+ });
13454
+ }
13213
13455
  async function runUnwatch(id, opts) {
13214
13456
  if (!id) {
13215
13457
  render.err("Usage: claudemesh watch remove <id>");
@@ -13246,6 +13488,32 @@ async function runWebhookList(opts) {
13246
13488
  return EXIT.SUCCESS;
13247
13489
  });
13248
13490
  }
13491
+ async function runWebhookCreate(name, opts) {
13492
+ if (!name) {
13493
+ render.err("Usage: claudemesh webhook create <name>");
13494
+ return EXIT.INVALID_ARGS;
13495
+ }
13496
+ return await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
13497
+ const created = await client.createWebhook(name);
13498
+ if (!created) {
13499
+ if (opts.json)
13500
+ emitJson({ ok: false, error: "create failed (timeout or duplicate)" });
13501
+ else
13502
+ render.err(`webhook create "${name}" failed`);
13503
+ return EXIT.IO_ERROR;
13504
+ }
13505
+ if (opts.json)
13506
+ emitJson({ ok: true, ...created });
13507
+ else {
13508
+ render.ok(`created webhook ${bold(created.name)}`);
13509
+ process.stdout.write(` url: ${clay(created.url)}
13510
+ `);
13511
+ process.stdout.write(` secret: ${dim(created.secret)} ${dim("(shown once)")}
13512
+ `);
13513
+ }
13514
+ return EXIT.SUCCESS;
13515
+ });
13516
+ }
13249
13517
  async function runWebhookDelete(name, opts) {
13250
13518
  if (!name) {
13251
13519
  render.err("Usage: claudemesh webhook delete <name>");
@@ -15860,435 +16128,194 @@ var init_member = __esm(() => {
15860
16128
  init_exit_codes();
15861
16129
  });
15862
16130
 
15863
- // src/mcp/tools/definitions.ts
15864
- var TOOLS;
15865
- var init_definitions = __esm(() => {
15866
- TOOLS = [];
15867
- });
15868
-
15869
- // src/services/bridge/server.ts
15870
- import { createServer as createServer3 } from "node:net";
15871
- import { mkdirSync as mkdirSync12, unlinkSync as unlinkSync5, existsSync as existsSync28, chmodSync as chmodSync6 } from "node:fs";
15872
- async function resolveTarget2(client, to) {
15873
- if (to.startsWith("@") || to === "*" || /^[0-9a-f]{64}$/i.test(to)) {
15874
- return { ok: true, spec: to };
15875
- }
15876
- const peers = await client.listPeers();
15877
- const match = peers.find((p) => p.displayName.toLowerCase() === to.toLowerCase());
15878
- if (!match) {
15879
- return {
15880
- ok: false,
15881
- error: `peer "${to}" not found. online: ${peers.map((p) => p.displayName).join(", ") || "(none)"}`
15882
- };
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));
15883
16149
  }
15884
- return { ok: true, spec: match.pubkey };
16150
+ return false;
15885
16151
  }
15886
- async function dispatch(client, req) {
15887
- const args = req.args ?? {};
15888
- try {
15889
- switch (req.verb) {
15890
- case "ping": {
15891
- const peers = await client.listPeers();
15892
- return {
15893
- id: req.id,
15894
- ok: true,
15895
- result: {
15896
- mesh: client.meshSlug,
15897
- ws_status: client.status,
15898
- peers_online: peers.length,
15899
- push_buffer: client.pushHistory.length
15900
- }
15901
- };
15902
- }
15903
- case "peers": {
15904
- const peers = await client.listPeers();
15905
- return { id: req.id, ok: true, result: peers };
15906
- }
15907
- case "send": {
15908
- const to = String(args.to ?? "");
15909
- const message = String(args.message ?? "");
15910
- const priority = args.priority ?? "next";
15911
- if (!to || !message) {
15912
- return { id: req.id, ok: false, error: "send: `to` and `message` required" };
15913
- }
15914
- const resolved = await resolveTarget2(client, to);
15915
- if (!resolved.ok)
15916
- return { id: req.id, ok: false, error: resolved.error };
15917
- const result = await client.send(resolved.spec, message, priority);
15918
- if (!result.ok) {
15919
- return { id: req.id, ok: false, error: result.error ?? "send failed" };
15920
- }
15921
- return {
15922
- id: req.id,
15923
- ok: true,
15924
- result: { messageId: result.messageId, target: resolved.spec }
15925
- };
15926
- }
15927
- case "summary": {
15928
- const text = String(args.summary ?? "");
15929
- if (!text)
15930
- return { id: req.id, ok: false, error: "summary: `summary` required" };
15931
- await client.setSummary(text);
15932
- return { id: req.id, ok: true, result: { summary: text } };
15933
- }
15934
- case "status_set": {
15935
- const state = String(args.status ?? "");
15936
- if (!["idle", "working", "dnd"].includes(state)) {
15937
- return { id: req.id, ok: false, error: "status_set: must be idle | working | dnd" };
15938
- }
15939
- await client.setStatus(state);
15940
- return { id: req.id, ok: true, result: { status: state } };
15941
- }
15942
- case "visible": {
15943
- const visible = Boolean(args.visible);
15944
- await client.setVisible(visible);
15945
- return { id: req.id, ok: true, result: { visible } };
15946
- }
15947
- default:
15948
- return { id: req.id, ok: false, error: `unknown verb: ${req.verb}` };
15949
- }
15950
- } catch (err) {
15951
- return {
15952
- id: req.id,
15953
- ok: false,
15954
- error: err instanceof Error ? err.message : String(err)
15955
- };
15956
- }
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);
15957
16163
  }
15958
- function handleConnection(socket, client) {
15959
- const parser = new LineParser;
15960
- socket.on("data", (chunk) => {
15961
- const lines = parser.feed(chunk);
15962
- for (const line of lines) {
15963
- if (!line.trim())
15964
- continue;
15965
- let req;
15966
- try {
15967
- req = JSON.parse(line);
15968
- } catch {
15969
- continue;
15970
- }
15971
- if (!req || typeof req !== "object" || !req.id || !req.verb)
15972
- continue;
15973
- 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;
15974
16172
  try {
15975
- socket.write(frame(res));
15976
- } catch {}
16173
+ body = JSON.parse(text);
16174
+ } catch {
16175
+ body = text;
16176
+ }
16177
+ resolve3({ status: res.statusCode ?? 0, body });
15977
16178
  });
15978
- }
16179
+ });
16180
+ req.on("error", reject);
16181
+ req.on("timeout", () => req.destroy(new Error("daemon_ipc_timeout")));
16182
+ req.end();
15979
16183
  });
15980
- socket.on("error", () => {});
15981
16184
  }
15982
- function startBridgeServer(client) {
15983
- const path2 = socketPath(client.meshSlug);
15984
- const dir = socketDir();
15985
- if (!existsSync28(dir)) {
15986
- mkdirSync12(dir, { recursive: true, mode: 448 });
15987
- }
15988
- if (existsSync28(path2)) {
15989
- try {
15990
- unlinkSync5(path2);
15991
- } catch {}
15992
- }
15993
- const server = createServer3((socket) => handleConnection(socket, client));
15994
- try {
15995
- server.listen(path2);
15996
- } catch (err) {
15997
- 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
15998
16232
  `);
15999
- return null;
16000
- }
16001
- server.on("error", (err) => {
16002
- 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}
16003
16241
  `);
16004
- });
16005
- try {
16006
- chmodSync6(path2, 384);
16007
- } catch {}
16008
- let stopped = false;
16242
+ if (active)
16243
+ setTimeout(connect, 2000);
16244
+ });
16245
+ req.end();
16246
+ };
16247
+ connect();
16009
16248
  return {
16010
- path: path2,
16011
- stop() {
16012
- if (stopped)
16013
- return;
16014
- stopped = true;
16015
- try {
16016
- server.close();
16017
- } catch {}
16249
+ close: () => {
16250
+ active = false;
16018
16251
  try {
16019
- unlinkSync5(path2);
16252
+ req?.destroy();
16020
16253
  } catch {}
16021
16254
  }
16022
16255
  };
16023
16256
  }
16024
- var init_server2 = __esm(() => {
16025
- init_protocol();
16026
- });
16027
-
16028
- // src/mcp/server.ts
16029
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
16030
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
16031
- import {
16032
- ListToolsRequestSchema,
16033
- CallToolRequestSchema,
16034
- ListPromptsRequestSchema,
16035
- GetPromptRequestSchema,
16036
- ListResourcesRequestSchema,
16037
- ReadResourceRequestSchema
16038
- } from "@modelcontextprotocol/sdk/types.js";
16039
- function relativeTime(isoStr) {
16040
- const then = new Date(isoStr).getTime();
16041
- if (isNaN(then))
16042
- return "unknown";
16043
- const diffMs = Date.now() - then;
16044
- if (diffMs < 0)
16045
- return "just now";
16046
- const seconds = Math.floor(diffMs / 1000);
16047
- if (seconds < 60)
16048
- return `${seconds}s ago`;
16049
- const minutes = Math.floor(seconds / 60);
16050
- if (minutes < 60)
16051
- return `${minutes}m ago`;
16052
- const hours = Math.floor(minutes / 60);
16053
- if (hours < 24)
16054
- return `${hours}h ago`;
16055
- const days = Math.floor(hours / 24);
16056
- return `${days} day${days !== 1 ? "s" : ""} ago`;
16057
- }
16058
- async function resolvePeerName(client, pubkey) {
16059
- const now = Date.now();
16060
- if (now - peerNameCacheAge > CACHE_TTL_MS) {
16061
- peerNameCache.clear();
16062
- try {
16063
- const peers = await client.listPeers();
16064
- for (const p of peers)
16065
- peerNameCache.set(p.pubkey, p.displayName);
16066
- } catch {}
16067
- peerNameCacheAge = now;
16068
- }
16069
- return peerNameCache.get(pubkey) ?? `peer-${pubkey.slice(0, 8)}`;
16070
- }
16071
- function decryptFailedWarning(senderPubkey) {
16072
- const who = senderPubkey ? senderPubkey.slice(0, 12) + "…" : "unknown sender";
16073
- return `⚠ message from ${who} failed to decrypt (tampered or wrong keypair)`;
16074
- }
16075
16257
  async function startMcpServer() {
16076
16258
  const serviceIdx = process.argv.indexOf("--service");
16077
16259
  if (serviceIdx !== -1 && process.argv[serviceIdx + 1]) {
16078
16260
  return startServiceProxy(process.argv[serviceIdx + 1]);
16079
16261
  }
16080
- const meshIdx = process.argv.indexOf("--mesh");
16081
- const onlyMesh = meshIdx !== -1 ? process.argv[meshIdx + 1] : null;
16082
- const config = readConfig();
16083
- if (onlyMesh) {
16084
- const available = config.meshes.map((m) => m.slug);
16085
- const filtered = config.meshes.filter((m) => m.slug === onlyMesh);
16086
- if (filtered.length === 0) {
16087
- process.stderr.write(`[claudemesh] --mesh "${onlyMesh}" not found in config. ` + `Joined meshes: ${available.join(", ") || "(none)"}
16088
- `);
16089
- process.exit(1);
16090
- }
16091
- config.meshes = filtered;
16092
- }
16093
- const myName = config.displayName ?? "unnamed";
16094
- const myRole = config.role ?? process.env.CLAUDEMESH_ROLE ?? null;
16095
- const myGroups = (config.groups ?? []).map((g) => `@${g.name}${g.role ? ":" + g.role : ""}`).join(", ") || "none";
16096
- const messageMode = config.messageMode ?? "push";
16097
- const server = new Server({ name: "claudemesh", version: "0.3.0" }, {
16098
- capabilities: {
16099
- experimental: { "claude/channel": {} },
16100
- tools: {},
16101
- prompts: {},
16102
- resources: {}
16103
- },
16104
- instructions: `## Identity
16105
- 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.
16106
-
16107
- ## Responding to messages
16108
- 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.
16109
-
16110
- The channel attributes carry everything you need to reply — no extra lookups:
16111
- - \`from_name\` — sender display name. Use as the \`to\` arg when replying to a DM.
16112
- - \`from_pubkey\` / \`from_member_id\` — stable ids. Use \`from_member_id\` if the sender's display name might change.
16113
- - \`mesh_slug\` — pass via \`--mesh\` if your default mesh differs.
16114
- - \`priority\` — \`now\` / \`next\` / \`low\`.
16115
- - \`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>\`.
16116
- - \`topic\` — set when the message arrived through a topic (vs DM). Reply in the same topic.
16117
- - \`reply_to_id\` — set when the incoming message is itself a reply. Render thread context if you re-narrate.
16118
-
16119
- If the channel meta contains \`subtype: reminder\`, this is a scheduled reminder you set for yourself — act on it immediately (no reply needed).
16120
-
16121
- ## Tools
16122
- | Tool | Description |
16123
- |------|-------------|
16124
- | send_message(to, message, priority?) | Send to peer name, @group, or * broadcast. \`to\` accepts display name, pubkey hex, @groupname, or *. |
16125
- | list_peers(mesh_slug?) | List connected peers with status, summary, groups, and roles. |
16126
- | check_messages() | Drain buffered inbound messages (auto-pushed in most cases, use as fallback). |
16127
- | set_summary(summary) | Set 1-2 sentence description of your current work, visible to all peers. |
16128
- | set_status(status) | Override status: idle, working, or dnd. |
16129
- | set_visible(visible) | Toggle visibility. Hidden peers skip list_peers and broadcasts; direct messages still arrive. |
16130
- | set_profile(avatar?, title?, bio?, capabilities?) | Set public profile: emoji avatar, short title, bio, capabilities list. |
16131
- | join_group(name, role?) | Join a @group with optional role (lead, member, observer, or any string). |
16132
- | leave_group(name) | Leave a @group. |
16133
- | set_state(key, value) | Write shared state; pushes change to all peers. |
16134
- | get_state(key) | Read a shared state value. |
16135
- | list_state() | List all state keys with values, authors, and timestamps. |
16136
- | remember(content, tags?) | Store persistent knowledge with optional tags. |
16137
- | recall(query) | Full-text search over mesh memory. |
16138
- | forget(id) | Soft-delete a memory entry. |
16139
- | 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). |
16140
- | claudemesh file get <id> [--out path] | Download a shared file by id. |
16141
- | claudemesh file list [query] | Find files shared in the mesh. |
16142
- | claudemesh file status <id> | Check who has accessed a file. |
16143
- | claudemesh file delete <id> | Remove a shared file from the mesh. |
16144
- | vector_store(collection, text, metadata?) | Store embedding in per-mesh Qdrant collection. |
16145
- | vector_search(collection, query, limit?) | Semantic search over stored embeddings. |
16146
- | vector_delete(collection, id) | Remove an embedding. |
16147
- | list_collections() | List vector collections in this mesh. |
16148
- | graph_query(cypher) | Read-only Cypher query on per-mesh Neo4j. |
16149
- | graph_execute(cypher) | Write Cypher query (CREATE, MERGE, DELETE). |
16150
- | mesh_query(sql) | Run a SELECT query on the per-mesh shared database. |
16151
- | mesh_execute(sql) | Run DDL/DML on the per-mesh database (CREATE TABLE, INSERT, UPDATE, DELETE). |
16152
- | mesh_schema() | List tables and columns in the per-mesh shared database. |
16153
- | create_stream(name) | Create a real-time data stream in the mesh. |
16154
- | publish(stream, data) | Push data to a stream. Subscribers receive it in real-time. |
16155
- | subscribe(stream) | Subscribe to a stream. Data pushes arrive as channel notifications. |
16156
- | list_streams() | List active streams in the mesh. |
16157
- | share_context(summary, files_read?, key_findings?, tags?) | Share session understanding with peers. |
16158
- | get_context(query) | Find context from peers who explored an area. |
16159
- | list_contexts() | See what all peers currently know. |
16160
- | create_task(title, assignee?, priority?, tags?) | Create a work item. |
16161
- | claim_task(id) | Claim an unclaimed task. |
16162
- | complete_task(id, result?) | Mark task done with optional result. |
16163
- | list_tasks(status?, assignee?) | List tasks filtered by status/assignee. |
16164
- | 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. |
16165
- | list_scheduled() | List pending scheduled reminders and messages. |
16166
- | cancel_scheduled(id) | Cancel a pending scheduled item. |
16167
- | read_peer_file(peer, path) | Read a file from another peer's project (max 1MB). |
16168
- | list_peer_files(peer, path?, pattern?) | List files in a peer's shared directory. |
16169
- | mesh_mcp_register(server_name, description, tools) | Register an MCP server with the mesh. Other peers can call its tools. |
16170
- | mesh_mcp_list() | List MCP servers available in the mesh with their tools. |
16171
- | mesh_tool_call(server_name, tool_name, args?) | Call a tool on a mesh-registered MCP server (30s timeout). |
16172
- | mesh_mcp_remove(server_name) | Unregister an MCP server you registered. |
16173
-
16174
- If multiple meshes are joined, prefix \`to\` with \`<mesh-slug>:\` to disambiguate (e.g. \`dev-team:Alice\`).
16175
-
16176
- Multi-target: send_message accepts an array of targets for the 'to' field.
16177
- send_message(to: ["Alice", "@backend"], message: "sprint starts")
16178
- Targets are deduplicated — each peer receives the message once.
16179
-
16180
- Targeted views: when different audiences need different details about the same event,
16181
- send tailored messages instead of one generic broadcast:
16182
- send_message(to: "@frontend", message: "Auth v2: useAuth hook changed, see src/auth/")
16183
- send_message(to: "@backend", message: "Auth v2: new /api/auth/v2 endpoints, v1 deprecated")
16184
- send_message(to: "@pm", message: "Auth v2 done. 3 points, no blockers.")
16185
-
16186
- ## Groups
16187
- 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.
16188
-
16189
- ## State
16190
- 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.
16191
-
16192
- ## Memory
16193
- 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.
16194
-
16195
- ## File access — decision guide
16196
- Three ways to access files. Pick the right one:
16197
-
16198
- 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.
16199
- 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.
16200
- 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.
16201
-
16202
- **Rule of thumb:** local peer → filesystem. Remote peer, small file → read_peer_file. Large file or needs to persist → share_file.
16203
-
16204
- ## Vectors
16205
- Store and search semantic embeddings. Use vector_store to index content, vector_search to find similar content.
16206
-
16207
- ## Graph
16208
- Build and query entity relationship graphs. Use graph_execute for writes (CREATE, MERGE), graph_query for reads (MATCH).
16209
-
16210
- ## Mesh Database
16211
- 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.
16212
-
16213
- ## Streams
16214
- 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.
16215
-
16216
- ## Context
16217
- 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.
16218
-
16219
- ## Tasks
16220
- Create and claim work items. create_task to propose work, claim_task to take ownership, complete_task when done. Prevents duplicate effort.
16221
-
16222
- ## Priority
16223
- - "now": interrupt immediately, even if recipient is in DND (use for urgent: broken deploy, blocking issue)
16224
- - "next" (default): deliver when recipient goes idle (normal coordination)
16225
- - "low": pull-only via check_messages (FYI, non-blocking context)
16226
-
16227
- ## Coordination
16228
- 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.
16229
-
16230
- ## Message Mode
16231
- Your message mode is "${messageMode}".
16232
- - push: messages arrive in real-time as channel notifications. Respond immediately.
16233
- - inbox: messages are held. You'll see "[inbox] New message from X" notifications. Call check_messages to read them.
16234
- - off: no message notifications. Use check_messages manually to poll.`
16235
- });
16236
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
16237
- tools: TOOLS
16238
- }));
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: [] }));
16239
16267
  server.setRequestHandler(ListPromptsRequestSchema, async () => {
16240
- const client = allClients()[0];
16241
- 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 {
16242
16275
  return { prompts: [] };
16243
- const skills = await client.listSkills();
16244
- return {
16245
- prompts: skills.map((s) => ({
16246
- name: s.name,
16247
- description: s.description,
16248
- arguments: []
16249
- }))
16250
- };
16276
+ }
16251
16277
  });
16252
16278
  server.setRequestHandler(GetPromptRequestSchema, async (req) => {
16253
- const { name, arguments: promptArgs } = req.params;
16254
- const client = allClients()[0];
16255
- if (!client)
16256
- throw new Error("Not connected to any mesh");
16257
- const skill = await client.getSkill(name);
16258
- if (!skill)
16279
+ const name = req.params.name;
16280
+ const { status, body } = await daemonGet(`/v1/skills/${encodeURIComponent(name)}`);
16281
+ if (status === 404)
16259
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;
16260
16286
  let content = skill.instructions;
16261
- const manifest = skill.manifest;
16262
- if (manifest && typeof manifest === "object") {
16287
+ const m = skill.manifest;
16288
+ if (m && typeof m === "object") {
16263
16289
  const fm = ["---"];
16264
- if (manifest.description)
16265
- fm.push(`description: "${manifest.description}"`);
16266
- if (manifest.when_to_use)
16267
- fm.push(`when_to_use: "${manifest.when_to_use}"`);
16268
- 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) {
16269
16295
  fm.push(`allowed-tools:
16270
- ${manifest.allowed_tools.map((t) => ` - ${t}`).join(`
16296
+ ${m.allowed_tools.map((t) => ` - ${t}`).join(`
16271
16297
  `)}`);
16272
- if (manifest.model)
16273
- fm.push(`model: ${manifest.model}`);
16274
- if (manifest.context)
16275
- fm.push(`context: ${manifest.context}`);
16276
- if (manifest.agent)
16277
- fm.push(`agent: ${manifest.agent}`);
16278
- 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)
16279
16306
  fm.push(`user-invocable: false`);
16280
- if (manifest.argument_hint)
16281
- fm.push(`argument-hint: "${manifest.argument_hint}"`);
16307
+ if (m.argument_hint)
16308
+ fm.push(`argument-hint: "${m.argument_hint}"`);
16282
16309
  fm.push(`---
16283
16310
  `);
16284
16311
  if (fm.length > 3)
16285
16312
  content = fm.join(`
16286
16313
  `) + content;
16287
- if (manifest.context === "fork") {
16288
- const agentType = manifest.agent || "general-purpose";
16289
- const modelHint = manifest.model ? `, model: "${manifest.model}"` : "";
16290
- const toolsHint = manifest.allowed_tools?.length ? `
16291
- 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(", ")}.` : "";
16292
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}
16293
16320
 
16294
16321
  ` + content;
@@ -16296,273 +16323,127 @@ Only use these tools: ${manifest.allowed_tools.join(", ")}.` : "";
16296
16323
  }
16297
16324
  return {
16298
16325
  description: skill.description,
16299
- messages: [
16300
- {
16301
- role: "user",
16302
- content: { type: "text", text: content }
16303
- }
16304
- ]
16326
+ messages: [{ role: "user", content: { type: "text", text: content } }]
16305
16327
  };
16306
16328
  });
16307
16329
  server.setRequestHandler(ListResourcesRequestSchema, async () => {
16308
- const client = allClients()[0];
16309
- 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 {
16310
16342
  return { resources: [] };
16311
- const skills = await client.listSkills();
16312
- return {
16313
- resources: skills.map((s) => ({
16314
- uri: `skill://claudemesh/${encodeURIComponent(s.name)}`,
16315
- name: s.name,
16316
- description: s.description,
16317
- mimeType: "text/markdown"
16318
- }))
16319
- };
16343
+ }
16320
16344
  });
16321
16345
  server.setRequestHandler(ReadResourceRequestSchema, async (req) => {
16322
- const { uri } = req.params;
16323
- const match = uri.match(/^skill:\/\/claudemesh\/(.+)$/);
16324
- if (!match)
16346
+ const uri = req.params.uri;
16347
+ const m = uri.match(/^skill:\/\/claudemesh\/(.+)$/);
16348
+ if (!m)
16325
16349
  throw new Error(`Unknown resource URI: ${uri}`);
16326
- const name = decodeURIComponent(match[1]);
16327
- const client = allClients()[0];
16328
- if (!client)
16329
- throw new Error("Not connected to any mesh");
16330
- const skill = await client.getSkill(name);
16331
- if (!skill)
16350
+ const name = decodeURIComponent(m[1]);
16351
+ const { status, body } = await daemonGet(`/v1/skills/${encodeURIComponent(name)}`);
16352
+ if (status === 404)
16332
16353
  throw new Error(`Skill "${name}" not found`);
16333
- const manifest = skill.manifest;
16334
- const fmLines = ["---"];
16335
- fmLines.push(`name: ${skill.name}`);
16336
- fmLines.push(`description: "${skill.description}"`);
16337
- if (skill.tags.length)
16338
- fmLines.push(`tags: [${skill.tags.join(", ")}]`);
16339
- if (manifest && typeof manifest === "object") {
16340
- if (manifest.when_to_use)
16341
- fmLines.push(`when_to_use: "${manifest.when_to_use}"`);
16342
- if (manifest.allowed_tools?.length)
16343
- fmLines.push(`allowed-tools:
16344
- ${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(`
16345
16369
  `)}`);
16346
- if (manifest.model)
16347
- fmLines.push(`model: ${manifest.model}`);
16348
- if (manifest.context)
16349
- fmLines.push(`context: ${manifest.context}`);
16350
- if (manifest.agent)
16351
- fmLines.push(`agent: ${manifest.agent}`);
16352
- if (manifest.user_invocable === false)
16353
- fmLines.push(`user-invocable: false`);
16354
- if (manifest.argument_hint)
16355
- fmLines.push(`argument-hint: "${manifest.argument_hint}"`);
16356
- }
16357
- 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(`---
16358
16377
  `);
16359
- const fullContent = fmLines.join(`
16360
- `) + skill.instructions;
16361
- return {
16362
- contents: [
16363
- {
16364
- uri,
16365
- mimeType: "text/markdown",
16366
- text: fullContent
16367
- }
16368
- ]
16369
- };
16378
+ return { contents: [{ uri, mimeType: "text/markdown", text: fm.join(`
16379
+ `) + skill.instructions }] };
16370
16380
  });
16371
- const transport = new StdioServerTransport;
16372
- await server.connect(transport);
16373
- const bridges = [];
16374
- startClients(config).then(() => {
16375
- wirePushHandlers().catch(() => {});
16376
- for (const client of allClients()) {
16377
- const bridge = startBridgeServer(client);
16378
- if (bridge)
16379
- bridges.push(bridge);
16380
- }
16381
- }).catch(() => {
16382
- wirePushHandlers().catch(() => {});
16383
- });
16384
- async function wirePushHandlers() {
16385
- for (const client of allClients()) {
16386
- client.onPush(async (msg) => {
16387
- if (messageMode === "off")
16388
- return;
16389
- if (msg.subtype === "system" && msg.event) {
16390
- const eventName = msg.event;
16391
- const data = msg.eventData ?? {};
16392
- let content2;
16393
- if (eventName === "tick") {
16394
- const tick = data.tick ?? 0;
16395
- const simTime = String(data.simTime ?? "").replace("T", " ").replace(/\..*/, "");
16396
- const speed = data.speed ?? 1;
16397
- content2 = `[heartbeat] tick ${tick} | sim time: ${simTime} | speed: x${speed}`;
16398
- } else if (eventName === "peer_joined") {
16399
- content2 = `[system] Peer "${data.name ?? "unknown"}" joined the mesh`;
16400
- } else if (eventName === "peer_returned") {
16401
- const peerName = String(data.name ?? "unknown");
16402
- const lastSeenAt = data.lastSeenAt ? relativeTime(String(data.lastSeenAt)) : "unknown";
16403
- const groups = Array.isArray(data.groups) ? data.groups.map((g) => g.role ? `@${g.name}:${g.role}` : `@${g.name}`).join(", ") : "";
16404
- const summary = data.summary ? ` Summary: "${data.summary}"` : "";
16405
- content2 = `[system] Welcome back, "${peerName}"! Last seen ${lastSeenAt}.${groups ? ` Restored: ${groups}` : ""}${summary}`;
16406
- } else if (eventName === "peer_left") {
16407
- content2 = `[system] Peer "${data.name ?? "unknown"}" left the mesh`;
16408
- } else if (eventName === "mcp_registered") {
16409
- const tools = Array.isArray(data.tools) ? data.tools.join(", ") : "";
16410
- content2 = `[system] New MCP server available: "${data.serverName}" (hosted by ${data.hostedBy}). Tools: ${tools}. Use mesh_tool_call to invoke.`;
16411
- } else if (eventName === "mcp_unregistered") {
16412
- content2 = `[system] MCP server "${data.serverName}" removed (was hosted by ${data.hostedBy})`;
16413
- } else if (eventName === "mcp_restored") {
16414
- content2 = `[system] MCP server "${data.serverName}" is back online (hosted by ${data.hostedBy})`;
16415
- } else if (eventName === "watch_triggered") {
16416
- content2 = `[WATCH] ${data.label ?? data.url}: ${data.oldValue} → ${data.newValue}`;
16417
- } else if (eventName === "mcp_deployed") {
16418
- content2 = `[SERVICE] "${data.name}" deployed (${data.tool_count} tools) by ${data.deployed_by}`;
16419
- } else if (eventName === "mcp_undeployed") {
16420
- content2 = `[SERVICE] "${data.name}" undeployed by ${data.by}`;
16421
- } else if (eventName === "mcp_scope_changed") {
16422
- content2 = `[SERVICE] "${data.name}" scope changed to ${JSON.stringify(data.scope)} by ${data.by}`;
16423
- } else {
16424
- content2 = `[system] ${eventName}: ${JSON.stringify(data)}`;
16425
- }
16426
- try {
16427
- await server.notification({
16428
- method: "notifications/claude/channel",
16429
- params: {
16430
- content: content2,
16431
- meta: {
16432
- kind: "system",
16433
- event: eventName,
16434
- mesh_slug: client.meshSlug,
16435
- mesh_id: client.meshId,
16436
- ...Object.keys(data).length > 0 ? { eventData: JSON.stringify(data) } : {}
16437
- }
16438
- }
16439
- });
16440
- process.stderr.write(`[claudemesh] system: ${content2}
16441
- `);
16442
- } catch (pushErr) {
16443
- process.stderr.write(`[claudemesh] system push FAILED: ${pushErr}
16444
- `);
16445
- }
16446
- return;
16447
- }
16448
- const fromPubkey = msg.senderPubkey || "";
16449
- const fromName = fromPubkey ? await resolvePeerName(client, fromPubkey) : "unknown";
16450
- if (fromPubkey) {
16451
- try {
16452
- const { isAllowed: isAllowed2 } = await Promise.resolve().then(() => (init_grants(), exports_grants));
16453
- const kindCap = msg.kind === "broadcast" ? "broadcast" : "dm";
16454
- if (!isAllowed2(client.meshSlug, fromPubkey, kindCap)) {
16455
- process.stderr.write(`[claudemesh] dropped ${kindCap} from ${fromName} (not granted)
16456
- `);
16457
- return;
16458
- }
16459
- } catch {}
16460
- }
16461
- if (messageMode === "inbox") {
16462
- try {
16463
- await server.notification({
16464
- method: "notifications/claude/channel",
16465
- params: {
16466
- content: `[inbox] New message from ${fromName}. Use check_messages to read.`,
16467
- meta: { kind: "inbox_notification", from_name: fromName }
16468
- }
16469
- });
16470
- } catch {}
16471
- return;
16472
- }
16473
- const body = msg.plaintext ?? decryptFailedWarning(fromPubkey);
16474
- const prioBadge = msg.priority === "now" ? "[URGENT] " : msg.priority === "low" ? "[low] " : "";
16475
- const kindBadge = msg.kind === "broadcast" ? " (broadcast)" : "";
16476
- const content = `${prioBadge}${fromName}${kindBadge}: ${body}`;
16477
- const fromMemberPubkey = msg.senderMemberPubkey ?? fromPubkey;
16478
- try {
16479
- await server.notification({
16480
- method: "notifications/claude/channel",
16481
- params: {
16482
- content,
16483
- meta: {
16484
- from_id: fromMemberPubkey,
16485
- from_pubkey: fromMemberPubkey,
16486
- from_session_pubkey: fromPubkey,
16487
- from_name: fromName,
16488
- ...msg.senderMemberId ? { from_member_id: msg.senderMemberId } : {},
16489
- mesh_slug: client.meshSlug,
16490
- mesh_id: client.meshId,
16491
- priority: msg.priority,
16492
- sent_at: msg.createdAt,
16493
- delivered_at: msg.receivedAt,
16494
- kind: msg.kind,
16495
- message_id: msg.messageId,
16496
- ...msg.topic ? { topic: msg.topic } : {},
16497
- ...msg.replyToId ? { reply_to_id: msg.replyToId } : {},
16498
- ...msg.subtype ? { subtype: msg.subtype } : {}
16499
- }
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) } : {}
16500
16408
  }
16501
- });
16502
- process.stderr.write(`[claudemesh] pushed: from=${fromName} content=${body.slice(0, 60)}
16503
- `);
16504
- } catch (pushErr) {
16505
- process.stderr.write(`[claudemesh] push FAILED: ${pushErr}
16409
+ }
16410
+ });
16411
+ } catch (err) {
16412
+ process.stderr.write(`[claudemesh-mcp] channel emit failed: ${err}
16506
16413
  `);
16507
- }
16508
- });
16509
- client.onStreamData(async (evt) => {
16510
- try {
16511
- await server.notification({
16512
- method: "notifications/claude/channel",
16513
- params: {
16514
- content: `[stream:${evt.stream}] from ${evt.publishedBy}: ${JSON.stringify(evt.data)}`,
16515
- meta: {
16516
- kind: "stream_data",
16517
- stream: evt.stream,
16518
- published_by: evt.publishedBy
16519
- }
16520
- }
16521
- });
16522
- } catch {}
16523
- });
16524
- client.onStateChange(async (change) => {
16525
- try {
16526
- await server.notification({
16527
- method: "notifications/claude/channel",
16528
- params: {
16529
- content: `[state] ${change.key} = ${JSON.stringify(change.value)} (set by ${change.updatedBy})`,
16530
- meta: {
16531
- kind: "state_change",
16532
- key: change.key,
16533
- updated_by: change.updatedBy
16534
- }
16535
- }
16536
- });
16537
- } catch {}
16538
- });
16539
- }
16540
- setTimeout(async () => {
16541
- const welcomeClient = allClients()[0];
16542
- if (!welcomeClient || welcomeClient.status !== "open")
16543
- 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
+ }
16544
16426
  try {
16545
- const peers = await welcomeClient.listPeers();
16546
- const peerNames = peers.filter((p) => p.displayName !== myName).map((p) => p.displayName).join(", ") || "none";
16547
16427
  await server.notification({
16548
16428
  method: "notifications/claude/channel",
16549
16429
  params: {
16550
- 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.`,
16551
- meta: { kind: "welcome", mesh_slug: welcomeClient.meshSlug }
16430
+ content,
16431
+ meta: {
16432
+ kind: "system",
16433
+ event: eventName,
16434
+ mesh_slug: String(d.mesh ?? "")
16435
+ }
16552
16436
  }
16553
16437
  });
16554
16438
  } catch {}
16555
- }, 2000);
16556
- }
16439
+ }
16440
+ });
16441
+ const transport = new StdioServerTransport;
16442
+ await server.connect(transport);
16557
16443
  const keepalive = setInterval(() => {}, 1000);
16558
16444
  const shutdown = () => {
16559
16445
  clearInterval(keepalive);
16560
- for (const b of bridges) {
16561
- try {
16562
- b.stop();
16563
- } catch {}
16564
- }
16565
- stopAll();
16446
+ sub.close();
16566
16447
  process.exit(0);
16567
16448
  };
16568
16449
  process.on("SIGTERM", shutdown);
@@ -16593,9 +16474,8 @@ async function startServiceProxy(serviceName) {
16593
16474
  tools = fetched;
16594
16475
  } catch {
16595
16476
  const cached2 = client.serviceCatalog.find((s) => s.name === serviceName);
16596
- if (cached2) {
16477
+ if (cached2)
16597
16478
  tools = cached2.tools;
16598
- }
16599
16479
  }
16600
16480
  if (tools.length === 0) {
16601
16481
  process.stderr.write(`[mesh:${serviceName}] no tools found — service may not be running
@@ -16620,12 +16500,7 @@ async function startServiceProxy(serviceName) {
16620
16500
  }
16621
16501
  if (client.status !== "open") {
16622
16502
  return {
16623
- content: [
16624
- {
16625
- type: "text",
16626
- text: `Service temporarily unavailable — broker reconnecting. Retry in a few seconds.`
16627
- }
16628
- ],
16503
+ content: [{ type: "text", text: "Service temporarily unavailable — broker reconnecting. Retry in a few seconds." }],
16629
16504
  isError: true
16630
16505
  };
16631
16506
  }
@@ -16633,23 +16508,13 @@ async function startServiceProxy(serviceName) {
16633
16508
  try {
16634
16509
  const result = await client.mcpCall(serviceName, toolName, args);
16635
16510
  if (result.error) {
16636
- return {
16637
- content: [{ type: "text", text: `Error: ${result.error}` }],
16638
- isError: true
16639
- };
16511
+ return { content: [{ type: "text", text: `Error: ${result.error}` }], isError: true };
16640
16512
  }
16641
16513
  const resultText = typeof result.result === "string" ? result.result : JSON.stringify(result.result, null, 2);
16642
- return {
16643
- content: [{ type: "text", text: resultText }]
16644
- };
16514
+ return { content: [{ type: "text", text: resultText }] };
16645
16515
  } catch (e) {
16646
16516
  return {
16647
- content: [
16648
- {
16649
- type: "text",
16650
- text: `Call failed: ${e instanceof Error ? e.message : String(e)}`
16651
- }
16652
- ],
16517
+ content: [{ type: "text", text: `Call failed: ${e instanceof Error ? e.message : String(e)}` }],
16653
16518
  isError: true
16654
16519
  };
16655
16520
  }
@@ -16665,9 +16530,7 @@ async function startServiceProxy(serviceName) {
16665
16530
  const newTools = push.eventData?.tools;
16666
16531
  if (Array.isArray(newTools)) {
16667
16532
  tools = newTools;
16668
- server.notification({
16669
- method: "notifications/tools/list_changed"
16670
- }).catch(() => {});
16533
+ server.notification({ method: "notifications/tools/list_changed" }).catch(() => {});
16671
16534
  }
16672
16535
  }
16673
16536
  });
@@ -16682,13 +16545,12 @@ async function startServiceProxy(serviceName) {
16682
16545
  process.on("SIGTERM", shutdown);
16683
16546
  process.on("SIGINT", shutdown);
16684
16547
  }
16685
- var peerNameCache, peerNameCacheAge = 0, CACHE_TTL_MS = 30000;
16686
- var init_server3 = __esm(() => {
16687
- init_definitions();
16548
+ var DAEMON_BOOT_RETRIES = 4, DAEMON_BOOT_RETRY_MS = 500;
16549
+ var init_server2 = __esm(() => {
16550
+ init_paths2();
16551
+ init_urls();
16688
16552
  init_facade();
16689
16553
  init_facade8();
16690
- init_server2();
16691
- peerNameCache = new Map;
16692
16554
  });
16693
16555
 
16694
16556
  // src/commands/mcp.ts
@@ -16703,7 +16565,7 @@ async function runMcp() {
16703
16565
  process.exit(0);
16704
16566
  }
16705
16567
  var init_mcp = __esm(() => {
16706
- init_server3();
16568
+ init_server2();
16707
16569
  });
16708
16570
 
16709
16571
  // src/commands/hook.ts
@@ -17484,9 +17346,9 @@ Platform
17484
17346
  claudemesh stream create|publish|list pub/sub event bus
17485
17347
  claudemesh sql query|execute|schema per-mesh SQL
17486
17348
  claudemesh skill list|get|remove mesh-published skills
17487
- claudemesh vault list|delete encrypted secrets
17488
- claudemesh watch list|remove URL change watchers
17489
- claudemesh webhook list|delete outbound HTTP triggers
17349
+ claudemesh vault set|list|delete encrypted secrets (set: --type env|file --mount /p)
17350
+ claudemesh watch add|list|remove URL change watchers (add: --label --interval --extract)
17351
+ claudemesh webhook create|list|delete outbound HTTP triggers
17490
17352
  claudemesh file share <path> [--to peer] upload (or local-host fast path if --to matches)
17491
17353
  claudemesh file get <id> [--out path] download by id
17492
17354
  claudemesh file list|status|delete shared mesh files
@@ -18150,8 +18012,16 @@ async function main() {
18150
18012
  } else if (sub === "delete") {
18151
18013
  const { runVaultDelete: runVaultDelete2 } = await Promise.resolve().then(() => (init_platform_actions(), exports_platform_actions));
18152
18014
  process.exit(await runVaultDelete2(positionals[1] ?? "", f));
18015
+ } else if (sub === "set") {
18016
+ const { runVaultSet: runVaultSet2 } = await Promise.resolve().then(() => (init_platform_actions(), exports_platform_actions));
18017
+ process.exit(await runVaultSet2(positionals[1] ?? "", positionals[2] ?? "", {
18018
+ ...f,
18019
+ entryType: flags.type,
18020
+ mountPath: flags.mount,
18021
+ description: flags.description
18022
+ }));
18153
18023
  } else {
18154
- console.error("Usage: claudemesh vault <list|delete> (set/get currently via MCP — needs crypto)");
18024
+ console.error("Usage: claudemesh vault <list|set|delete>");
18155
18025
  process.exit(EXIT.INVALID_ARGS);
18156
18026
  }
18157
18027
  break;
@@ -18165,8 +18035,18 @@ async function main() {
18165
18035
  } else if (sub === "remove") {
18166
18036
  const { runUnwatch: runUnwatch2 } = await Promise.resolve().then(() => (init_platform_actions(), exports_platform_actions));
18167
18037
  process.exit(await runUnwatch2(positionals[1] ?? "", f));
18038
+ } else if (sub === "add") {
18039
+ const { runWatchAdd: runWatchAdd2 } = await Promise.resolve().then(() => (init_platform_actions(), exports_platform_actions));
18040
+ process.exit(await runWatchAdd2(positionals[1] ?? "", {
18041
+ ...f,
18042
+ label: flags.label,
18043
+ interval: flags.interval ? Number(flags.interval) : undefined,
18044
+ mode: flags.mode,
18045
+ extract: flags.extract,
18046
+ notifyOn: flags["notify-on"]
18047
+ }));
18168
18048
  } else {
18169
- console.error("Usage: claudemesh watch <list|remove>");
18049
+ console.error("Usage: claudemesh watch <list|add|remove>");
18170
18050
  process.exit(EXIT.INVALID_ARGS);
18171
18051
  }
18172
18052
  break;
@@ -18180,8 +18060,11 @@ async function main() {
18180
18060
  } else if (sub === "delete") {
18181
18061
  const { runWebhookDelete: runWebhookDelete2 } = await Promise.resolve().then(() => (init_platform_actions(), exports_platform_actions));
18182
18062
  process.exit(await runWebhookDelete2(positionals[1] ?? "", f));
18063
+ } else if (sub === "create") {
18064
+ const { runWebhookCreate: runWebhookCreate2 } = await Promise.resolve().then(() => (init_platform_actions(), exports_platform_actions));
18065
+ process.exit(await runWebhookCreate2(positionals[1] ?? "", f));
18183
18066
  } else {
18184
- console.error("Usage: claudemesh webhook <list|delete>");
18067
+ console.error("Usage: claudemesh webhook <list|create|delete>");
18185
18068
  process.exit(EXIT.INVALID_ARGS);
18186
18069
  }
18187
18070
  break;
@@ -18493,4 +18376,4 @@ main().catch((err) => {
18493
18376
  process.exit(EXIT.INTERNAL_ERROR);
18494
18377
  });
18495
18378
 
18496
- //# debugId=7D9AD4B3EA7DF68364756E2164756E21
18379
+ //# debugId=BAEC2C4FE7977E7264756E2164756E21