claudemesh-cli 0.6.8 → 0.7.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.
Files changed (2) hide show
  1. package/dist/index.js +1450 -110
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -46941,6 +46941,46 @@ var TOOLS = [
46941
46941
  required: ["status"]
46942
46942
  }
46943
46943
  },
46944
+ {
46945
+ name: "set_visible",
46946
+ description: "Control your visibility in the mesh. When hidden, you won't appear in list_peers and won't receive broadcasts — but direct messages still reach you.",
46947
+ inputSchema: {
46948
+ type: "object",
46949
+ properties: {
46950
+ visible: {
46951
+ type: "boolean",
46952
+ description: "true to be visible (default), false to hide"
46953
+ }
46954
+ },
46955
+ required: ["visible"]
46956
+ }
46957
+ },
46958
+ {
46959
+ name: "set_profile",
46960
+ description: "Set your public profile — what other peers see about you. Avatar (emoji), title, bio, and capabilities list.",
46961
+ inputSchema: {
46962
+ type: "object",
46963
+ properties: {
46964
+ avatar: {
46965
+ type: "string",
46966
+ description: "Emoji or URL for your avatar"
46967
+ },
46968
+ title: {
46969
+ type: "string",
46970
+ description: "Short role label (e.g. 'Frontend Lead', 'DevOps')"
46971
+ },
46972
+ bio: {
46973
+ type: "string",
46974
+ description: "One-liner about yourself"
46975
+ },
46976
+ capabilities: {
46977
+ type: "array",
46978
+ items: { type: "string" },
46979
+ description: "What you can help with"
46980
+ }
46981
+ }
46982
+ }
46983
+ },
46944
46984
  {
46945
46985
  name: "join_group",
46946
46986
  description: "Join a group with an optional role. Other peers see your group membership in list_peers.",
@@ -47376,13 +47416,14 @@ var TOOLS = [
47376
47416
  },
47377
47417
  {
47378
47418
  name: "schedule_reminder",
47379
- description: "Schedule a message for future delivery. Without `to`, it fires back to yourself (a self-reminder). With `to`, it delivers to a peer, @group, or * broadcast. The broker holds it and delivers when the time arrives. Receivers see `subtype: reminder` in the push envelope.",
47419
+ description: "Schedule a one-shot or recurring message. Without `to`, it fires back to yourself (a self-reminder). With `to`, it delivers to a peer, @group, or * broadcast. For one-shot, provide `deliver_at` or `in_seconds`. For recurring, provide `cron` (standard 5-field expression). The broker persists schedules to the database they survive restarts. Receivers see `subtype: reminder` in the push envelope.",
47380
47420
  inputSchema: {
47381
47421
  type: "object",
47382
47422
  properties: {
47383
47423
  message: { type: "string", description: "Message or reminder text" },
47384
- deliver_at: { type: "number", description: "Unix timestamp (ms) when to deliver" },
47385
- in_seconds: { type: "number", description: "Alternative to deliver_at: fire after N seconds" },
47424
+ deliver_at: { type: "number", description: "Unix timestamp (ms) when to deliver (one-shot)" },
47425
+ in_seconds: { type: "number", description: "Alternative to deliver_at: fire after N seconds (one-shot)" },
47426
+ cron: { type: "string", description: "Cron expression for recurring reminders (e.g. '0 */2 * * *' for every 2 hours, '30 9 * * 1-5' for 9:30 weekdays)" },
47386
47427
  to: {
47387
47428
  type: "string",
47388
47429
  description: "Recipient: display name, pubkey hex, @group, or * (omit for self-reminder)"
@@ -47412,6 +47453,144 @@ var TOOLS = [
47412
47453
  description: "Get a complete overview of the mesh: peers, groups, state, memory, files, tasks, streams, tables. Call on session start for full situational awareness.",
47413
47454
  inputSchema: { type: "object", properties: {} }
47414
47455
  },
47456
+ {
47457
+ name: "mesh_stats",
47458
+ description: "View resource usage stats for all peers: messages sent/received, tool calls, uptime, errors.",
47459
+ inputSchema: { type: "object", properties: {} }
47460
+ },
47461
+ {
47462
+ name: "mesh_mcp_register",
47463
+ description: "Register an MCP server with the mesh. Other peers can invoke its tools through the mesh without restarting their sessions. Provide the server name, description, and full tool definitions.",
47464
+ inputSchema: {
47465
+ type: "object",
47466
+ properties: {
47467
+ server_name: { type: "string", description: "Unique name for the MCP server (e.g. 'github', 'jira')" },
47468
+ description: { type: "string", description: "What this MCP server does" },
47469
+ tools: {
47470
+ type: "array",
47471
+ items: {
47472
+ type: "object",
47473
+ properties: {
47474
+ name: { type: "string" },
47475
+ description: { type: "string" },
47476
+ inputSchema: { type: "object", description: "JSON Schema for tool arguments" }
47477
+ },
47478
+ required: ["name", "description", "inputSchema"]
47479
+ },
47480
+ description: "Tool definitions to expose"
47481
+ }
47482
+ },
47483
+ required: ["server_name", "description", "tools"]
47484
+ }
47485
+ },
47486
+ {
47487
+ name: "mesh_mcp_list",
47488
+ description: "List MCP servers available in the mesh with their tools. Shows which peer hosts each server.",
47489
+ inputSchema: { type: "object", properties: {} }
47490
+ },
47491
+ {
47492
+ name: "mesh_tool_call",
47493
+ description: "Call a tool on a mesh-registered MCP server. Route: you -> broker -> hosting peer -> execute -> result back. Timeout: 30s.",
47494
+ inputSchema: {
47495
+ type: "object",
47496
+ properties: {
47497
+ server_name: { type: "string", description: "Name of the MCP server" },
47498
+ tool_name: { type: "string", description: "Name of the tool to call" },
47499
+ args: { type: "object", description: "Tool arguments (JSON object)" }
47500
+ },
47501
+ required: ["server_name", "tool_name"]
47502
+ }
47503
+ },
47504
+ {
47505
+ name: "mesh_mcp_remove",
47506
+ description: "Unregister an MCP server you previously registered with the mesh.",
47507
+ inputSchema: {
47508
+ type: "object",
47509
+ properties: {
47510
+ server_name: { type: "string", description: "Name of the MCP server to remove" }
47511
+ },
47512
+ required: ["server_name"]
47513
+ }
47514
+ },
47515
+ {
47516
+ name: "mesh_set_clock",
47517
+ description: "Set the simulation clock speed. x1 = real-time, x10 = 10x faster, x100 = 100x. Peers receive heartbeat ticks at the simulated rate.",
47518
+ inputSchema: {
47519
+ type: "object",
47520
+ properties: {
47521
+ speed: {
47522
+ type: "number",
47523
+ description: "Speed multiplier (1-100). x1 = tick every 60s, x10 = tick every 6s, x100 = tick every 600ms."
47524
+ }
47525
+ },
47526
+ required: ["speed"]
47527
+ }
47528
+ },
47529
+ {
47530
+ name: "mesh_pause_clock",
47531
+ description: "Pause the simulation clock. Ticks stop until resumed.",
47532
+ inputSchema: { type: "object", properties: {} }
47533
+ },
47534
+ {
47535
+ name: "mesh_resume_clock",
47536
+ description: "Resume a paused simulation clock.",
47537
+ inputSchema: { type: "object", properties: {} }
47538
+ },
47539
+ {
47540
+ name: "mesh_clock",
47541
+ description: "Get current simulation clock status: speed, tick count, simulated time.",
47542
+ inputSchema: { type: "object", properties: {} }
47543
+ },
47544
+ {
47545
+ name: "share_skill",
47546
+ description: "Publish a reusable skill to the mesh. Other peers can discover and load it. If a skill with the same name exists, it is updated.",
47547
+ inputSchema: {
47548
+ type: "object",
47549
+ properties: {
47550
+ name: { type: "string", description: "Unique skill name (e.g. 'code-review', 'deploy-checklist')" },
47551
+ description: { type: "string", description: "Short description of what the skill does" },
47552
+ instructions: { type: "string", description: "Full instructions/prompt that a peer loads to acquire this capability" },
47553
+ tags: {
47554
+ type: "array",
47555
+ items: { type: "string" },
47556
+ description: "Tags for discoverability"
47557
+ }
47558
+ },
47559
+ required: ["name", "description", "instructions"]
47560
+ }
47561
+ },
47562
+ {
47563
+ name: "get_skill",
47564
+ description: "Load a skill's full instructions by name. Use to acquire capabilities shared by other peers.",
47565
+ inputSchema: {
47566
+ type: "object",
47567
+ properties: {
47568
+ name: { type: "string", description: "Skill name to load" }
47569
+ },
47570
+ required: ["name"]
47571
+ }
47572
+ },
47573
+ {
47574
+ name: "list_skills",
47575
+ description: "Browse available skills in the mesh. Optionally filter by keyword across name, description, and tags.",
47576
+ inputSchema: {
47577
+ type: "object",
47578
+ properties: {
47579
+ query: { type: "string", description: "Search keyword (optional)" }
47580
+ }
47581
+ }
47582
+ },
47583
+ {
47584
+ name: "remove_skill",
47585
+ description: "Remove a skill you published from the mesh.",
47586
+ inputSchema: {
47587
+ type: "object",
47588
+ properties: {
47589
+ name: { type: "string", description: "Skill name to remove" }
47590
+ },
47591
+ required: ["name"]
47592
+ }
47593
+ },
47415
47594
  {
47416
47595
  name: "ping_mesh",
47417
47596
  description: "Send test messages through the full pipeline and measure round-trip timing per priority. Diagnoses push delivery issues.",
@@ -47425,6 +47604,61 @@ var TOOLS = [
47425
47604
  }
47426
47605
  }
47427
47606
  }
47607
+ },
47608
+ {
47609
+ name: "read_peer_file",
47610
+ description: "Read a file from another peer's project. Specify the peer (by name) and the file path relative to their working directory. The peer must be online and sharing files. Max file size: 1MB.",
47611
+ inputSchema: {
47612
+ type: "object",
47613
+ properties: {
47614
+ peer: { type: "string", description: "Peer display name or pubkey" },
47615
+ path: { type: "string", description: "File path relative to peer's working directory" }
47616
+ },
47617
+ required: ["peer", "path"]
47618
+ }
47619
+ },
47620
+ {
47621
+ name: "list_peer_files",
47622
+ description: "List files in a peer's shared directory. Returns a tree of file names (not contents). The peer must be online and sharing files.",
47623
+ inputSchema: {
47624
+ type: "object",
47625
+ properties: {
47626
+ peer: { type: "string", description: "Peer display name or pubkey" },
47627
+ path: { type: "string", description: "Directory path relative to peer's cwd (default: root)" },
47628
+ pattern: { type: "string", description: "Glob-like filter pattern (e.g. '*.ts', 'src/*')" }
47629
+ },
47630
+ required: ["peer"]
47631
+ }
47632
+ },
47633
+ {
47634
+ name: "create_webhook",
47635
+ description: "Create an inbound webhook. Returns a URL that external services (GitHub, CI/CD, monitoring) can POST to — the payload becomes a mesh message to all peers.",
47636
+ inputSchema: {
47637
+ type: "object",
47638
+ properties: {
47639
+ name: {
47640
+ type: "string",
47641
+ description: "Webhook name (e.g. 'github-ci', 'datadog-alerts')"
47642
+ }
47643
+ },
47644
+ required: ["name"]
47645
+ }
47646
+ },
47647
+ {
47648
+ name: "list_webhooks",
47649
+ description: "List active webhooks for this mesh.",
47650
+ inputSchema: { type: "object", properties: {} }
47651
+ },
47652
+ {
47653
+ name: "delete_webhook",
47654
+ description: "Deactivate a webhook.",
47655
+ inputSchema: {
47656
+ type: "object",
47657
+ properties: {
47658
+ name: { type: "string", description: "Webhook name to deactivate" }
47659
+ },
47660
+ required: ["name"]
47661
+ }
47428
47662
  }
47429
47663
  ];
47430
47664
 
@@ -47510,10 +47744,21 @@ class BrokerClient {
47510
47744
  sessionPubkey = null;
47511
47745
  sessionSecretKey = null;
47512
47746
  grantFileAccessResolvers = new Map;
47747
+ peerFileResponseResolvers = new Map;
47748
+ peerDirResponseResolvers = new Map;
47749
+ sharedDirs = [process.cwd()];
47513
47750
  closed = false;
47514
47751
  reconnectAttempt = 0;
47515
47752
  helloTimer = null;
47516
47753
  reconnectTimer = null;
47754
+ _statsCounters = {
47755
+ messagesIn: 0,
47756
+ messagesOut: 0,
47757
+ toolCalls: 0,
47758
+ errors: 0
47759
+ };
47760
+ _sessionStartedAt = Date.now();
47761
+ _statsReportTimer = null;
47517
47762
  constructor(mesh, opts = {}) {
47518
47763
  this.mesh = mesh;
47519
47764
  this.opts = opts;
@@ -47565,6 +47810,9 @@ class BrokerClient {
47565
47810
  sessionId: `${process.pid}-${Date.now()}`,
47566
47811
  pid: process.pid,
47567
47812
  cwd: process.cwd(),
47813
+ peerType: "ai",
47814
+ channel: "claude-code",
47815
+ model: process.env.CLAUDE_MODEL || undefined,
47568
47816
  timestamp,
47569
47817
  signature
47570
47818
  }));
@@ -47592,6 +47840,7 @@ class BrokerClient {
47592
47840
  this.setConnStatus("open");
47593
47841
  this.reconnectAttempt = 0;
47594
47842
  this.flushOutbound();
47843
+ this.startStatsReporting();
47595
47844
  resolve();
47596
47845
  return;
47597
47846
  }
@@ -47631,6 +47880,7 @@ class BrokerClient {
47631
47880
  nonce = randomNonce();
47632
47881
  ciphertext = Buffer.from(message, "utf-8").toString("base64");
47633
47882
  }
47883
+ this._statsCounters.messagesOut++;
47634
47884
  return new Promise((resolve) => {
47635
47885
  if (this.pendingSends.size >= MAX_QUEUED) {
47636
47886
  resolve({ ok: false, error: "outbound queue full" });
@@ -47688,6 +47938,16 @@ class BrokerClient {
47688
47938
  return;
47689
47939
  this.ws.send(JSON.stringify({ type: "set_status", status }));
47690
47940
  }
47941
+ async setVisible(visible) {
47942
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
47943
+ return;
47944
+ this.ws.send(JSON.stringify({ type: "set_visible", visible }));
47945
+ }
47946
+ async setProfile(profile) {
47947
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
47948
+ return;
47949
+ this.ws.send(JSON.stringify({ type: "set_profile", ...profile }));
47950
+ }
47691
47951
  async listPeers() {
47692
47952
  if (!this.ws || this.ws.readyState !== this.ws.OPEN)
47693
47953
  return [];
@@ -47705,6 +47965,34 @@ class BrokerClient {
47705
47965
  return;
47706
47966
  this.ws.send(JSON.stringify({ type: "set_summary", summary }));
47707
47967
  }
47968
+ setStats(stats) {
47969
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
47970
+ return;
47971
+ const payload = stats ?? {
47972
+ ...this._statsCounters,
47973
+ uptime: Math.round((Date.now() - this._sessionStartedAt) / 1000)
47974
+ };
47975
+ this.ws.send(JSON.stringify({ type: "set_stats", stats: payload }));
47976
+ }
47977
+ incrementToolCalls() {
47978
+ this._statsCounters.toolCalls++;
47979
+ }
47980
+ incrementErrors() {
47981
+ this._statsCounters.errors++;
47982
+ }
47983
+ startStatsReporting() {
47984
+ if (this._statsReportTimer)
47985
+ return;
47986
+ this._statsReportTimer = setInterval(() => {
47987
+ this.setStats();
47988
+ }, 60000);
47989
+ }
47990
+ stopStatsReporting() {
47991
+ if (this._statsReportTimer) {
47992
+ clearInterval(this._statsReportTimer);
47993
+ this._statsReportTimer = null;
47994
+ }
47995
+ }
47708
47996
  async joinGroup(name, role) {
47709
47997
  if (!this.ws || this.ws.readyState !== this.ws.OPEN)
47710
47998
  return;
@@ -47773,7 +48061,7 @@ class BrokerClient {
47773
48061
  return;
47774
48062
  this.ws.send(JSON.stringify({ type: "forget", memoryId }));
47775
48063
  }
47776
- async scheduleMessage(to, message, deliverAt, isReminder = false) {
48064
+ async scheduleMessage(to, message, deliverAt, isReminder = false, cron) {
47777
48065
  if (!this.ws || this.ws.readyState !== this.ws.OPEN)
47778
48066
  return null;
47779
48067
  return new Promise((resolve) => {
@@ -47788,6 +48076,7 @@ class BrokerClient {
47788
48076
  message,
47789
48077
  deliverAt,
47790
48078
  ...isReminder ? { subtype: "reminder" } : {},
48079
+ ...cron ? { cron, recurring: true } : {},
47791
48080
  _reqId: reqId
47792
48081
  }));
47793
48082
  });
@@ -47836,6 +48125,10 @@ class BrokerClient {
47836
48125
  scheduledAckResolvers = new Map;
47837
48126
  scheduledListResolvers = new Map;
47838
48127
  cancelScheduledResolvers = new Map;
48128
+ mcpRegisterResolvers = new Map;
48129
+ mcpListResolvers = new Map;
48130
+ mcpCallResolvers = new Map;
48131
+ mcpCallForwardHandler = null;
47839
48132
  async messageStatus(messageId) {
47840
48133
  if (!this.ws || this.ws.readyState !== this.ws.OPEN)
47841
48134
  return null;
@@ -48134,7 +48427,105 @@ class BrokerClient {
48134
48427
  this.stateChangeHandlers.add(handler);
48135
48428
  return () => this.stateChangeHandlers.delete(handler);
48136
48429
  }
48430
+ async mcpRegister(serverName, description, tools) {
48431
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
48432
+ return null;
48433
+ return new Promise((resolve) => {
48434
+ const reqId = this.makeReqId();
48435
+ this.mcpRegisterResolvers.set(reqId, { resolve, timer: setTimeout(() => {
48436
+ if (this.mcpRegisterResolvers.delete(reqId))
48437
+ resolve(null);
48438
+ }, 5000) });
48439
+ this.ws.send(JSON.stringify({ type: "mcp_register", serverName, description, tools, _reqId: reqId }));
48440
+ });
48441
+ }
48442
+ async mcpUnregister(serverName) {
48443
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
48444
+ return;
48445
+ this.ws.send(JSON.stringify({ type: "mcp_unregister", serverName }));
48446
+ }
48447
+ async mcpList() {
48448
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
48449
+ return [];
48450
+ return new Promise((resolve) => {
48451
+ const reqId = this.makeReqId();
48452
+ this.mcpListResolvers.set(reqId, { resolve, timer: setTimeout(() => {
48453
+ if (this.mcpListResolvers.delete(reqId))
48454
+ resolve([]);
48455
+ }, 5000) });
48456
+ this.ws.send(JSON.stringify({ type: "mcp_list", _reqId: reqId }));
48457
+ });
48458
+ }
48459
+ async mcpCall(serverName, toolName, args) {
48460
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
48461
+ return { error: "not connected" };
48462
+ return new Promise((resolve) => {
48463
+ const reqId = this.makeReqId();
48464
+ this.mcpCallResolvers.set(reqId, { resolve, timer: setTimeout(() => {
48465
+ if (this.mcpCallResolvers.delete(reqId))
48466
+ resolve({ error: "MCP call timed out (30s)" });
48467
+ }, 30000) });
48468
+ this.ws.send(JSON.stringify({ type: "mcp_call", serverName, toolName, args, _reqId: reqId }));
48469
+ });
48470
+ }
48471
+ onMcpCallForward(handler) {
48472
+ this.mcpCallForwardHandler = handler;
48473
+ }
48474
+ sendMcpCallResponse(callId, result, error2) {
48475
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
48476
+ return;
48477
+ this.ws.send(JSON.stringify({ type: "mcp_call_response", callId, result, error: error2 }));
48478
+ }
48137
48479
  meshInfoResolvers = new Map;
48480
+ clockStatusResolvers = new Map;
48481
+ async setClock(speed) {
48482
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
48483
+ return null;
48484
+ return new Promise((resolve) => {
48485
+ const reqId = this.makeReqId();
48486
+ this.clockStatusResolvers.set(reqId, { resolve, timer: setTimeout(() => {
48487
+ if (this.clockStatusResolvers.delete(reqId))
48488
+ resolve(null);
48489
+ }, 5000) });
48490
+ this.ws.send(JSON.stringify({ type: "set_clock", speed, _reqId: reqId }));
48491
+ });
48492
+ }
48493
+ async pauseClock() {
48494
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
48495
+ return null;
48496
+ return new Promise((resolve) => {
48497
+ const reqId = this.makeReqId();
48498
+ this.clockStatusResolvers.set(reqId, { resolve, timer: setTimeout(() => {
48499
+ if (this.clockStatusResolvers.delete(reqId))
48500
+ resolve(null);
48501
+ }, 5000) });
48502
+ this.ws.send(JSON.stringify({ type: "pause_clock", _reqId: reqId }));
48503
+ });
48504
+ }
48505
+ async resumeClock() {
48506
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
48507
+ return null;
48508
+ return new Promise((resolve) => {
48509
+ const reqId = this.makeReqId();
48510
+ this.clockStatusResolvers.set(reqId, { resolve, timer: setTimeout(() => {
48511
+ if (this.clockStatusResolvers.delete(reqId))
48512
+ resolve(null);
48513
+ }, 5000) });
48514
+ this.ws.send(JSON.stringify({ type: "resume_clock", _reqId: reqId }));
48515
+ });
48516
+ }
48517
+ async getClock() {
48518
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
48519
+ return null;
48520
+ return new Promise((resolve) => {
48521
+ const reqId = this.makeReqId();
48522
+ this.clockStatusResolvers.set(reqId, { resolve, timer: setTimeout(() => {
48523
+ if (this.clockStatusResolvers.delete(reqId))
48524
+ resolve(null);
48525
+ }, 5000) });
48526
+ this.ws.send(JSON.stringify({ type: "get_clock", _reqId: reqId }));
48527
+ });
48528
+ }
48138
48529
  async meshInfo() {
48139
48530
  if (!this.ws || this.ws.readyState !== this.ws.OPEN)
48140
48531
  return null;
@@ -48147,8 +48538,132 @@ class BrokerClient {
48147
48538
  this.ws.send(JSON.stringify({ type: "mesh_info", _reqId: reqId }));
48148
48539
  });
48149
48540
  }
48541
+ skillAckResolvers = new Map;
48542
+ skillDataResolvers = new Map;
48543
+ skillListResolvers = new Map;
48544
+ async shareSkill(name, description, instructions, tags) {
48545
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
48546
+ return null;
48547
+ return new Promise((resolve) => {
48548
+ const reqId = this.makeReqId();
48549
+ this.skillAckResolvers.set(reqId, { resolve: (result) => {
48550
+ resolve(result ? { ok: true, action: result.action } : null);
48551
+ }, timer: setTimeout(() => {
48552
+ if (this.skillAckResolvers.delete(reqId))
48553
+ resolve(null);
48554
+ }, 5000) });
48555
+ this.ws.send(JSON.stringify({ type: "share_skill", name, description, instructions, tags, _reqId: reqId }));
48556
+ });
48557
+ }
48558
+ async getSkill(name) {
48559
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
48560
+ return null;
48561
+ return new Promise((resolve) => {
48562
+ const reqId = this.makeReqId();
48563
+ this.skillDataResolvers.set(reqId, { resolve, timer: setTimeout(() => {
48564
+ if (this.skillDataResolvers.delete(reqId))
48565
+ resolve(null);
48566
+ }, 5000) });
48567
+ this.ws.send(JSON.stringify({ type: "get_skill", name, _reqId: reqId }));
48568
+ });
48569
+ }
48570
+ async listSkills(query) {
48571
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
48572
+ return [];
48573
+ return new Promise((resolve) => {
48574
+ const reqId = this.makeReqId();
48575
+ this.skillListResolvers.set(reqId, { resolve, timer: setTimeout(() => {
48576
+ if (this.skillListResolvers.delete(reqId))
48577
+ resolve([]);
48578
+ }, 5000) });
48579
+ this.ws.send(JSON.stringify({ type: "list_skills", query, _reqId: reqId }));
48580
+ });
48581
+ }
48582
+ async removeSkill(name) {
48583
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
48584
+ return false;
48585
+ return new Promise((resolve) => {
48586
+ const reqId = this.makeReqId();
48587
+ this.skillAckResolvers.set(reqId, { resolve: (result) => {
48588
+ resolve(result?.action === "removed");
48589
+ }, timer: setTimeout(() => {
48590
+ if (this.skillAckResolvers.delete(reqId))
48591
+ resolve(false);
48592
+ }, 5000) });
48593
+ this.ws.send(JSON.stringify({ type: "remove_skill", name, _reqId: reqId }));
48594
+ });
48595
+ }
48596
+ webhookAckResolvers = new Map;
48597
+ webhookListResolvers = new Map;
48598
+ async createWebhook(name) {
48599
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
48600
+ return null;
48601
+ return new Promise((resolve) => {
48602
+ const reqId = this.makeReqId();
48603
+ this.webhookAckResolvers.set(reqId, { resolve, timer: setTimeout(() => {
48604
+ if (this.webhookAckResolvers.delete(reqId))
48605
+ resolve(null);
48606
+ }, 5000) });
48607
+ this.ws.send(JSON.stringify({ type: "create_webhook", name, _reqId: reqId }));
48608
+ });
48609
+ }
48610
+ async listWebhooks() {
48611
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
48612
+ return [];
48613
+ return new Promise((resolve) => {
48614
+ const reqId = this.makeReqId();
48615
+ this.webhookListResolvers.set(reqId, { resolve, timer: setTimeout(() => {
48616
+ if (this.webhookListResolvers.delete(reqId))
48617
+ resolve([]);
48618
+ }, 5000) });
48619
+ this.ws.send(JSON.stringify({ type: "list_webhooks", _reqId: reqId }));
48620
+ });
48621
+ }
48622
+ async deleteWebhook(name) {
48623
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
48624
+ return false;
48625
+ return new Promise((resolve) => {
48626
+ const reqId = this.makeReqId();
48627
+ this.webhookAckResolvers.set(reqId, { resolve: () => resolve(true), timer: setTimeout(() => {
48628
+ if (this.webhookAckResolvers.delete(reqId))
48629
+ resolve(false);
48630
+ }, 5000) });
48631
+ this.ws.send(JSON.stringify({ type: "delete_webhook", name, _reqId: reqId }));
48632
+ });
48633
+ }
48634
+ setSharedDirs(dirs) {
48635
+ this.sharedDirs = dirs.map((d) => {
48636
+ const { resolve } = __require("node:path");
48637
+ return resolve(d);
48638
+ });
48639
+ }
48640
+ async requestFile(targetPubkey, filePath) {
48641
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
48642
+ return { error: "not connected" };
48643
+ return new Promise((resolve) => {
48644
+ const reqId = this.makeReqId();
48645
+ this.peerFileResponseResolvers.set(reqId, { resolve, timer: setTimeout(() => {
48646
+ if (this.peerFileResponseResolvers.delete(reqId))
48647
+ resolve({ error: "timeout waiting for peer response" });
48648
+ }, 15000) });
48649
+ this.ws.send(JSON.stringify({ type: "peer_file_request", targetPubkey, filePath, _reqId: reqId }));
48650
+ });
48651
+ }
48652
+ async requestDir(targetPubkey, dirPath, pattern) {
48653
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
48654
+ return { error: "not connected" };
48655
+ return new Promise((resolve) => {
48656
+ const reqId = this.makeReqId();
48657
+ this.peerDirResponseResolvers.set(reqId, { resolve, timer: setTimeout(() => {
48658
+ if (this.peerDirResponseResolvers.delete(reqId))
48659
+ resolve({ error: "timeout waiting for peer response" });
48660
+ }, 15000) });
48661
+ this.ws.send(JSON.stringify({ type: "peer_dir_request", targetPubkey, dirPath, ...pattern ? { pattern } : {}, _reqId: reqId }));
48662
+ });
48663
+ }
48150
48664
  close() {
48151
48665
  this.closed = true;
48666
+ this.stopStatsReporting();
48152
48667
  if (this.helloTimer)
48153
48668
  clearTimeout(this.helloTimer);
48154
48669
  if (this.reconnectTimer)
@@ -48160,6 +48675,141 @@ class BrokerClient {
48160
48675
  }
48161
48676
  this.setConnStatus("closed");
48162
48677
  }
48678
+ static MAX_FILE_SIZE = 1048576;
48679
+ async handlePeerFileRequest(msg) {
48680
+ const { resolve, join: join2, normalize } = await import("node:path");
48681
+ const { readFileSync: readFileSync2, statSync } = await import("node:fs");
48682
+ const reqId = msg._reqId;
48683
+ const sendResponse = (content, error2) => {
48684
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
48685
+ return;
48686
+ this.ws.send(JSON.stringify({
48687
+ type: "peer_file_response",
48688
+ requesterPubkey: msg.requesterPubkey,
48689
+ filePath: msg.filePath,
48690
+ ...content !== undefined ? { content } : {},
48691
+ ...error2 ? { error: error2 } : {},
48692
+ ...reqId ? { _reqId: reqId } : {}
48693
+ }));
48694
+ };
48695
+ if (msg.filePath.includes("..")) {
48696
+ sendResponse(undefined, "path traversal not allowed");
48697
+ return;
48698
+ }
48699
+ let resolvedPath = null;
48700
+ for (const dir of this.sharedDirs) {
48701
+ const candidate = resolve(join2(dir, msg.filePath));
48702
+ const normalizedCandidate = normalize(candidate);
48703
+ const normalizedDir = normalize(dir);
48704
+ if (normalizedCandidate.startsWith(normalizedDir + "/") || normalizedCandidate === normalizedDir) {
48705
+ resolvedPath = candidate;
48706
+ break;
48707
+ }
48708
+ }
48709
+ if (!resolvedPath) {
48710
+ sendResponse(undefined, "file outside shared directories");
48711
+ return;
48712
+ }
48713
+ try {
48714
+ const stat = statSync(resolvedPath);
48715
+ if (!stat.isFile()) {
48716
+ sendResponse(undefined, "not a file");
48717
+ return;
48718
+ }
48719
+ if (stat.size > BrokerClient.MAX_FILE_SIZE) {
48720
+ sendResponse(undefined, `file too large (${stat.size} bytes, max ${BrokerClient.MAX_FILE_SIZE})`);
48721
+ return;
48722
+ }
48723
+ const content = readFileSync2(resolvedPath);
48724
+ sendResponse(content.toString("base64"));
48725
+ } catch (e) {
48726
+ const errMsg = e instanceof Error ? e.message : String(e);
48727
+ if (errMsg.includes("ENOENT")) {
48728
+ sendResponse(undefined, "file not found");
48729
+ } else {
48730
+ sendResponse(undefined, `read error: ${errMsg}`);
48731
+ }
48732
+ }
48733
+ }
48734
+ async handlePeerDirRequest(msg) {
48735
+ const { resolve, join: join2, normalize, relative } = await import("node:path");
48736
+ const { readdirSync, statSync } = await import("node:fs");
48737
+ const reqId = msg._reqId;
48738
+ const sendResponse = (entries, error2) => {
48739
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
48740
+ return;
48741
+ this.ws.send(JSON.stringify({
48742
+ type: "peer_dir_response",
48743
+ requesterPubkey: msg.requesterPubkey,
48744
+ dirPath: msg.dirPath,
48745
+ ...entries ? { entries } : {},
48746
+ ...error2 ? { error: error2 } : {},
48747
+ ...reqId ? { _reqId: reqId } : {}
48748
+ }));
48749
+ };
48750
+ const dirPath = msg.dirPath || ".";
48751
+ if (dirPath.includes("..")) {
48752
+ sendResponse(undefined, "path traversal not allowed");
48753
+ return;
48754
+ }
48755
+ let resolvedPath = null;
48756
+ for (const dir of this.sharedDirs) {
48757
+ const candidate = resolve(join2(dir, dirPath));
48758
+ const normalizedCandidate = normalize(candidate);
48759
+ const normalizedDir = normalize(dir);
48760
+ if (normalizedCandidate.startsWith(normalizedDir + "/") || normalizedCandidate === normalizedDir) {
48761
+ resolvedPath = candidate;
48762
+ break;
48763
+ }
48764
+ }
48765
+ if (!resolvedPath) {
48766
+ sendResponse(undefined, "directory outside shared directories");
48767
+ return;
48768
+ }
48769
+ try {
48770
+ const stat = statSync(resolvedPath);
48771
+ if (!stat.isDirectory()) {
48772
+ sendResponse(undefined, "not a directory");
48773
+ return;
48774
+ }
48775
+ const entries = [];
48776
+ const MAX_ENTRIES = 500;
48777
+ const MAX_DEPTH = 2;
48778
+ const pattern = msg.pattern ? new RegExp(msg.pattern.replace(/\*/g, ".*").replace(/\?/g, "."), "i") : null;
48779
+ const walk = (dir, depth) => {
48780
+ if (entries.length >= MAX_ENTRIES || depth > MAX_DEPTH)
48781
+ return;
48782
+ try {
48783
+ const items = readdirSync(dir, { withFileTypes: true });
48784
+ for (const item of items) {
48785
+ if (entries.length >= MAX_ENTRIES)
48786
+ break;
48787
+ if (item.name.startsWith("."))
48788
+ continue;
48789
+ const relPath = relative(resolvedPath, join2(dir, item.name));
48790
+ const label = item.isDirectory() ? relPath + "/" : relPath;
48791
+ if (pattern && !pattern.test(item.name)) {
48792
+ if (item.isDirectory())
48793
+ walk(join2(dir, item.name), depth + 1);
48794
+ continue;
48795
+ }
48796
+ entries.push(label);
48797
+ if (item.isDirectory())
48798
+ walk(join2(dir, item.name), depth + 1);
48799
+ }
48800
+ } catch {}
48801
+ };
48802
+ walk(resolvedPath, 0);
48803
+ sendResponse(entries.sort());
48804
+ } catch (e) {
48805
+ const errMsg = e instanceof Error ? e.message : String(e);
48806
+ if (errMsg.includes("ENOENT")) {
48807
+ sendResponse(undefined, "directory not found");
48808
+ } else {
48809
+ sendResponse(undefined, `read error: ${errMsg}`);
48810
+ }
48811
+ }
48812
+ }
48163
48813
  resolveFromMap(map2, reqId, value) {
48164
48814
  let entry = reqId ? map2.get(reqId) : undefined;
48165
48815
  if (!entry) {
@@ -48197,6 +48847,7 @@ class BrokerClient {
48197
48847
  return;
48198
48848
  }
48199
48849
  if (msg.type === "push") {
48850
+ this._statsCounters.messagesIn++;
48200
48851
  const nonce = String(msg.nonce ?? "");
48201
48852
  const ciphertext = String(msg.ciphertext ?? "");
48202
48853
  const senderPubkey = String(msg.senderPubkey ?? "");
@@ -48234,7 +48885,9 @@ class BrokerClient {
48234
48885
  receivedAt: new Date().toISOString(),
48235
48886
  plaintext,
48236
48887
  kind,
48237
- ...msg.subtype ? { subtype: msg.subtype } : {}
48888
+ ...msg.subtype ? { subtype: msg.subtype } : {},
48889
+ ...msg.event ? { event: String(msg.event) } : {},
48890
+ ...msg.eventData ? { eventData: msg.eventData } : {}
48238
48891
  };
48239
48892
  this.pushBuffer.push(push);
48240
48893
  if (this.pushBuffer.length > 500)
@@ -48388,31 +49041,125 @@ class BrokerClient {
48388
49041
  data: msg.data,
48389
49042
  publishedBy: String(msg.publishedBy ?? "")
48390
49043
  };
48391
- for (const h of this.streamDataHandlers) {
48392
- try {
48393
- h(evt);
48394
- } catch {}
49044
+ for (const h of this.streamDataHandlers) {
49045
+ try {
49046
+ h(evt);
49047
+ } catch {}
49048
+ }
49049
+ return;
49050
+ }
49051
+ if (msg.type === "clock_status") {
49052
+ this.resolveFromMap(this.clockStatusResolvers, msgReqId, {
49053
+ speed: Number(msg.speed ?? 0),
49054
+ paused: Boolean(msg.paused),
49055
+ tick: Number(msg.tick ?? 0),
49056
+ simTime: String(msg.simTime ?? ""),
49057
+ startedAt: String(msg.startedAt ?? "")
49058
+ });
49059
+ return;
49060
+ }
49061
+ if (msg.type === "mesh_info_result") {
49062
+ this.resolveFromMap(this.meshInfoResolvers, msgReqId, msg);
49063
+ return;
49064
+ }
49065
+ if (msg.type === "skill_ack") {
49066
+ this.resolveFromMap(this.skillAckResolvers, msgReqId, { name: String(msg.name ?? ""), action: String(msg.action ?? "") });
49067
+ return;
49068
+ }
49069
+ if (msg.type === "skill_data") {
49070
+ const skill = msg.skill;
49071
+ this.resolveFromMap(this.skillDataResolvers, msgReqId, skill ?? null);
49072
+ return;
49073
+ }
49074
+ if (msg.type === "skill_list") {
49075
+ const skills = msg.skills ?? [];
49076
+ this.resolveFromMap(this.skillListResolvers, msgReqId, skills);
49077
+ return;
49078
+ }
49079
+ if (msg.type === "scheduled_ack") {
49080
+ this.resolveFromMap(this.scheduledAckResolvers, msgReqId, {
49081
+ scheduledId: String(msg.scheduledId ?? ""),
49082
+ deliverAt: Number(msg.deliverAt ?? 0),
49083
+ ...msg.cron ? { cron: String(msg.cron) } : {}
49084
+ });
49085
+ return;
49086
+ }
49087
+ if (msg.type === "scheduled_list") {
49088
+ const messages = msg.messages ?? [];
49089
+ this.resolveFromMap(this.scheduledListResolvers, msgReqId, messages);
49090
+ return;
49091
+ }
49092
+ if (msg.type === "cancel_scheduled_ack") {
49093
+ this.resolveFromMap(this.cancelScheduledResolvers, msgReqId, Boolean(msg.ok));
49094
+ return;
49095
+ }
49096
+ if (msg.type === "mcp_register_ack") {
49097
+ this.resolveFromMap(this.mcpRegisterResolvers, msgReqId, {
49098
+ serverName: String(msg.serverName ?? ""),
49099
+ toolCount: Number(msg.toolCount ?? 0)
49100
+ });
49101
+ return;
49102
+ }
49103
+ if (msg.type === "mcp_list_result") {
49104
+ const servers = msg.servers ?? [];
49105
+ this.resolveFromMap(this.mcpListResolvers, msgReqId, servers);
49106
+ return;
49107
+ }
49108
+ if (msg.type === "mcp_call_result") {
49109
+ this.resolveFromMap(this.mcpCallResolvers, msgReqId, {
49110
+ ...msg.result !== undefined ? { result: msg.result } : {},
49111
+ ...msg.error ? { error: String(msg.error) } : {}
49112
+ });
49113
+ return;
49114
+ }
49115
+ if (msg.type === "mcp_call_forward") {
49116
+ const forward = {
49117
+ callId: String(msg.callId ?? ""),
49118
+ serverName: String(msg.serverName ?? ""),
49119
+ toolName: String(msg.toolName ?? ""),
49120
+ args: msg.args ?? {},
49121
+ callerName: String(msg.callerName ?? "")
49122
+ };
49123
+ if (this.mcpCallForwardHandler) {
49124
+ this.mcpCallForwardHandler(forward).then((res) => this.sendMcpCallResponse(forward.callId, res.result, res.error)).catch((e) => this.sendMcpCallResponse(forward.callId, undefined, e instanceof Error ? e.message : String(e)));
49125
+ } else {
49126
+ this.sendMcpCallResponse(forward.callId, undefined, "No MCP call handler registered on this peer");
48395
49127
  }
48396
49128
  return;
48397
49129
  }
48398
- if (msg.type === "mesh_info_result") {
48399
- this.resolveFromMap(this.meshInfoResolvers, msgReqId, msg);
49130
+ if (msg.type === "peer_file_request_forward") {
49131
+ this.handlePeerFileRequest(msg);
48400
49132
  return;
48401
49133
  }
48402
- if (msg.type === "scheduled_ack") {
48403
- this.resolveFromMap(this.scheduledAckResolvers, msgReqId, {
48404
- scheduledId: String(msg.scheduledId ?? ""),
48405
- deliverAt: Number(msg.deliverAt ?? 0)
49134
+ if (msg.type === "peer_file_response_forward") {
49135
+ this.resolveFromMap(this.peerFileResponseResolvers, msgReqId, {
49136
+ content: msg.content ? String(msg.content) : undefined,
49137
+ error: msg.error ? String(msg.error) : undefined
48406
49138
  });
48407
49139
  return;
48408
49140
  }
48409
- if (msg.type === "scheduled_list") {
48410
- const messages = msg.messages ?? [];
48411
- this.resolveFromMap(this.scheduledListResolvers, msgReqId, messages);
49141
+ if (msg.type === "peer_dir_request_forward") {
49142
+ this.handlePeerDirRequest(msg);
48412
49143
  return;
48413
49144
  }
48414
- if (msg.type === "cancel_scheduled_ack") {
48415
- this.resolveFromMap(this.cancelScheduledResolvers, msgReqId, Boolean(msg.ok));
49145
+ if (msg.type === "peer_dir_response_forward") {
49146
+ this.resolveFromMap(this.peerDirResponseResolvers, msgReqId, {
49147
+ entries: msg.entries ?? undefined,
49148
+ error: msg.error ? String(msg.error) : undefined
49149
+ });
49150
+ return;
49151
+ }
49152
+ if (msg.type === "webhook_ack") {
49153
+ this.resolveFromMap(this.webhookAckResolvers, msgReqId, {
49154
+ name: String(msg.name ?? ""),
49155
+ url: String(msg.url ?? ""),
49156
+ secret: String(msg.secret ?? "")
49157
+ });
49158
+ return;
49159
+ }
49160
+ if (msg.type === "webhook_list") {
49161
+ const webhooks = msg.webhooks ?? [];
49162
+ this.resolveFromMap(this.webhookListResolvers, msgReqId, webhooks);
48416
49163
  return;
48417
49164
  }
48418
49165
  if (msg.type === "error") {
@@ -48457,7 +49204,18 @@ class BrokerClient {
48457
49204
  [this.taskCreatedResolvers, null],
48458
49205
  [this.streamCreatedResolvers, null],
48459
49206
  [this.listPeersResolvers, []],
48460
- [this.meshInfoResolvers, null]
49207
+ [this.meshInfoResolvers, null],
49208
+ [this.clockStatusResolvers, null],
49209
+ [this.mcpRegisterResolvers, null],
49210
+ [this.mcpListResolvers, []],
49211
+ [this.mcpCallResolvers, { error: "broker error" }],
49212
+ [this.skillAckResolvers, null],
49213
+ [this.skillDataResolvers, null],
49214
+ [this.skillListResolvers, []],
49215
+ [this.peerFileResponseResolvers, { error: "broker error" }],
49216
+ [this.peerDirResponseResolvers, { error: "broker error" }],
49217
+ [this.webhookAckResolvers, null],
49218
+ [this.webhookListResolvers, []]
48461
49219
  ];
48462
49220
  for (const [map2, defaultVal] of allMaps) {
48463
49221
  const first = map2.entries().next().value;
@@ -48660,6 +49418,8 @@ If the channel meta contains \`subtype: reminder\`, this is a scheduled reminder
48660
49418
  | check_messages() | Drain buffered inbound messages (auto-pushed in most cases, use as fallback). |
48661
49419
  | set_summary(summary) | Set 1-2 sentence description of your current work, visible to all peers. |
48662
49420
  | set_status(status) | Override status: idle, working, or dnd. |
49421
+ | set_visible(visible) | Toggle visibility. Hidden peers skip list_peers and broadcasts; direct messages still arrive. |
49422
+ | set_profile(avatar?, title?, bio?, capabilities?) | Set public profile: emoji avatar, short title, bio, capabilities list. |
48663
49423
  | join_group(name, role?) | Join a @group with optional role (lead, member, observer, or any string). |
48664
49424
  | leave_group(name) | Leave a @group. |
48665
49425
  | set_state(key, value) | Write shared state; pushes change to all peers. |
@@ -48696,6 +49456,12 @@ If the channel meta contains \`subtype: reminder\`, this is a scheduled reminder
48696
49456
  | 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. |
48697
49457
  | list_scheduled() | List pending scheduled reminders and messages. |
48698
49458
  | cancel_scheduled(id) | Cancel a pending scheduled item. |
49459
+ | read_peer_file(peer, path) | Read a file from another peer's project (max 1MB). |
49460
+ | list_peer_files(peer, path?, pattern?) | List files in a peer's shared directory. |
49461
+ | mesh_mcp_register(server_name, description, tools) | Register an MCP server with the mesh. Other peers can call its tools. |
49462
+ | mesh_mcp_list() | List MCP servers available in the mesh with their tools. |
49463
+ | mesh_tool_call(server_name, tool_name, args?) | Call a tool on a mesh-registered MCP server (30s timeout). |
49464
+ | mesh_mcp_remove(server_name) | Unregister an MCP server you registered. |
48699
49465
 
48700
49466
  If multiple meshes are joined, prefix \`to\` with \`<mesh-slug>:\` to disambiguate (e.g. \`dev-team:Alice\`).
48701
49467
 
@@ -48759,6 +49525,9 @@ Your message mode is "${messageMode}".
48759
49525
  }));
48760
49526
  server.setRequestHandler(CallToolRequestSchema, async (req) => {
48761
49527
  const { name, arguments: args } = req.params;
49528
+ for (const c of allClients()) {
49529
+ c.incrementToolCalls();
49530
+ }
48762
49531
  if (config2.meshes.length === 0) {
48763
49532
  return text("No meshes joined. Run `claudemesh join https://claudemesh.com/join/<token>` first.", true);
48764
49533
  }
@@ -48805,7 +49574,19 @@ No peers connected.`);
48805
49574
  const peerLines = peers.map((p) => {
48806
49575
  const summary = p.summary ? ` — "${p.summary}"` : "";
48807
49576
  const groupsStr = p.groups?.length ? ` [${p.groups.map((g) => `@${g.name}${g.role ? ":" + g.role : ""}`).join(", ")}]` : "";
48808
- return `- **${p.displayName}** [${p.status}]${groupsStr} (${p.pubkey.slice(0, 12)}…)${summary}`;
49577
+ const meta2 = [];
49578
+ if (p.peerType)
49579
+ meta2.push(`type:${p.peerType}`);
49580
+ if (p.channel)
49581
+ meta2.push(`channel:${p.channel}`);
49582
+ if (p.model)
49583
+ meta2.push(`model:${p.model}`);
49584
+ const metaStr = meta2.length ? ` {${meta2.join(", ")}}` : "";
49585
+ const cwdStr = p.cwd ? ` cwd:${p.cwd}` : "";
49586
+ const profileAvatar = p.profile?.avatar ? `${p.profile.avatar} ` : "";
49587
+ const profileTitle = p.profile?.title ? ` (${p.profile.title})` : "";
49588
+ const hiddenTag = p.visible === false ? " [hidden]" : "";
49589
+ return `- ${profileAvatar}**${p.displayName}**${profileTitle} [${p.status}]${hiddenTag}${groupsStr}${metaStr} (${p.pubkey.slice(0, 12)}…)${cwdStr}${summary}`;
48809
49590
  });
48810
49591
  sections.push(`${header}
48811
49592
  ${peerLines.join(`
@@ -48872,6 +49653,32 @@ ${drained.join(`
48872
49653
  await c.setStatus(s);
48873
49654
  return text(`Status set to ${s} across ${allClients().length} mesh(es).`);
48874
49655
  }
49656
+ case "set_visible": {
49657
+ const { visible } = args ?? {};
49658
+ if (visible === undefined)
49659
+ return text("set_visible: `visible` required", true);
49660
+ for (const c of allClients())
49661
+ await c.setVisible(visible);
49662
+ return text(visible ? "You are now visible to peers." : "You are now hidden. Direct messages still reach you, but you won't appear in list_peers or receive broadcasts.");
49663
+ }
49664
+ case "set_profile": {
49665
+ const { avatar, title, bio, capabilities } = args ?? {};
49666
+ const profile = { avatar, title, bio, capabilities };
49667
+ for (const c of allClients())
49668
+ await c.setProfile(profile);
49669
+ const parts = [];
49670
+ if (avatar)
49671
+ parts.push(`Avatar: ${avatar}`);
49672
+ if (title)
49673
+ parts.push(`Title: ${title}`);
49674
+ if (bio)
49675
+ parts.push(`Bio: ${bio}`);
49676
+ if (capabilities?.length)
49677
+ parts.push(`Capabilities: ${capabilities.join(", ")}`);
49678
+ return text(parts.length > 0 ? `Profile updated:
49679
+ ${parts.join(`
49680
+ `)}` : "Profile cleared.");
49681
+ }
48875
49682
  case "join_group": {
48876
49683
  const { name: groupName, role } = args ?? {};
48877
49684
  if (!groupName)
@@ -48958,13 +49765,16 @@ ${lines.join(`
48958
49765
  const sArgs = args ?? {};
48959
49766
  if (!sArgs.message)
48960
49767
  return text("schedule_reminder: `message` required", true);
49768
+ const isCron = !!sArgs.cron;
48961
49769
  let deliverAt;
48962
- if (sArgs.deliver_at) {
49770
+ if (isCron) {
49771
+ deliverAt = 0;
49772
+ } else if (sArgs.deliver_at) {
48963
49773
  deliverAt = Number(sArgs.deliver_at);
48964
49774
  } else if (sArgs.in_seconds) {
48965
49775
  deliverAt = Date.now() + Number(sArgs.in_seconds) * 1000;
48966
49776
  } else {
48967
- return text("schedule_reminder: provide `deliver_at` (ms timestamp) or `in_seconds`", true);
49777
+ return text("schedule_reminder: provide `deliver_at` (ms timestamp), `in_seconds`, or `cron` expression", true);
48968
49778
  }
48969
49779
  const isSelf = !sArgs.to;
48970
49780
  let targetSpec;
@@ -48984,9 +49794,13 @@ ${lines.join(`
48984
49794
  targetSpec = to;
48985
49795
  }
48986
49796
  }
48987
- const result = await client.scheduleMessage(targetSpec, sArgs.message, deliverAt, true);
49797
+ const result = await client.scheduleMessage(targetSpec, sArgs.message, deliverAt, true, sArgs.cron);
48988
49798
  if (!result)
48989
49799
  return text("schedule_reminder: broker did not acknowledge — check connection", true);
49800
+ if (isCron) {
49801
+ const nextFire = new Date(result.deliverAt).toISOString();
49802
+ return text(isSelf ? `Recurring self-reminder scheduled (${result.scheduledId.slice(0, 8)}): "${sArgs.message.slice(0, 60)}" — cron: ${sArgs.cron}, next fire: ${nextFire}` : `Recurring reminder to "${sArgs.to}" scheduled (${result.scheduledId.slice(0, 8)}) — cron: ${sArgs.cron}, next fire: ${nextFire}`);
49803
+ }
48990
49804
  const when = new Date(result.deliverAt).toISOString();
48991
49805
  return text(isSelf ? `Self-reminder scheduled (${result.scheduledId.slice(0, 8)}): "${sArgs.message.slice(0, 60)}" at ${when}` : `Reminder to "${sArgs.to}" scheduled (${result.scheduledId.slice(0, 8)}) for ${when}`);
48992
49806
  }
@@ -49398,6 +50212,72 @@ ${rows.join(`
49398
50212
  return text("No active streams.");
49399
50213
  const lines = streams.map((s) => `- **${s.name}** (${s.id.slice(0, 8)}…) by ${s.createdBy}, ${s.subscriberCount} subscriber(s)`);
49400
50214
  return text(lines.join(`
50215
+ `));
50216
+ }
50217
+ case "mesh_set_clock": {
50218
+ const { speed } = args ?? {};
50219
+ if (!speed || speed < 1 || speed > 100)
50220
+ return text("mesh_set_clock: speed must be 1-100", true);
50221
+ const client2 = allClients()[0];
50222
+ if (!client2)
50223
+ return text("mesh_set_clock: not connected", true);
50224
+ const result = await client2.setClock(speed);
50225
+ if (!result)
50226
+ return text("mesh_set_clock: timed out", true);
50227
+ return text([
50228
+ `**Clock set to x${result.speed}**`,
50229
+ `Paused: ${result.paused}`,
50230
+ `Tick: ${result.tick}`,
50231
+ `Sim time: ${result.simTime}`,
50232
+ `Started at: ${result.startedAt}`
50233
+ ].join(`
50234
+ `));
50235
+ }
50236
+ case "mesh_pause_clock": {
50237
+ const client2 = allClients()[0];
50238
+ if (!client2)
50239
+ return text("mesh_pause_clock: not connected", true);
50240
+ const result = await client2.pauseClock();
50241
+ if (!result)
50242
+ return text("mesh_pause_clock: timed out", true);
50243
+ return text([
50244
+ "**Clock paused**",
50245
+ `Speed: x${result.speed}`,
50246
+ `Tick: ${result.tick}`,
50247
+ `Sim time: ${result.simTime}`
50248
+ ].join(`
50249
+ `));
50250
+ }
50251
+ case "mesh_resume_clock": {
50252
+ const client2 = allClients()[0];
50253
+ if (!client2)
50254
+ return text("mesh_resume_clock: not connected", true);
50255
+ const result = await client2.resumeClock();
50256
+ if (!result)
50257
+ return text("mesh_resume_clock: timed out", true);
50258
+ return text([
50259
+ "**Clock resumed**",
50260
+ `Speed: x${result.speed}`,
50261
+ `Tick: ${result.tick}`,
50262
+ `Sim time: ${result.simTime}`
50263
+ ].join(`
50264
+ `));
50265
+ }
50266
+ case "mesh_clock": {
50267
+ const client2 = allClients()[0];
50268
+ if (!client2)
50269
+ return text("mesh_clock: not connected", true);
50270
+ const result = await client2.getClock();
50271
+ if (!result)
50272
+ return text("mesh_clock: timed out", true);
50273
+ const statusLabel = result.speed === 0 ? "not started" : result.paused ? "paused" : "running";
50274
+ return text([
50275
+ `**Clock status: ${statusLabel}**`,
50276
+ `Speed: x${result.speed}`,
50277
+ `Tick: ${result.tick}`,
50278
+ `Sim time: ${result.simTime}`,
50279
+ `Started at: ${result.startedAt}`
50280
+ ].join(`
49401
50281
  `));
49402
50282
  }
49403
50283
  case "mesh_info": {
@@ -49423,6 +50303,89 @@ ${rows.join(`
49423
50303
  return text(lines.join(`
49424
50304
  `));
49425
50305
  }
50306
+ case "mesh_stats": {
50307
+ const clients2 = allClients();
50308
+ if (clients2.length === 0)
50309
+ return text("mesh_stats: no joined meshes", true);
50310
+ const sections = [];
50311
+ for (const c of clients2) {
50312
+ const peers = await c.listPeers();
50313
+ const header = `## ${c.meshSlug}`;
50314
+ const rows = peers.map((p) => {
50315
+ const s = p.stats;
50316
+ if (!s)
50317
+ return `| ${p.displayName} | - | - | - | - | - |`;
50318
+ const up = s.uptime != null ? `${Math.floor(s.uptime / 60)}m` : "-";
50319
+ return `| ${p.displayName} | ${s.messagesIn ?? 0} | ${s.messagesOut ?? 0} | ${s.toolCalls ?? 0} | ${up} | ${s.errors ?? 0} |`;
50320
+ });
50321
+ sections.push(`${header}
50322
+ | Peer | Msgs In | Msgs Out | Tool Calls | Uptime | Errors |
50323
+ |------|---------|----------|------------|--------|--------|
50324
+ ${rows.join(`
50325
+ `)}`);
50326
+ }
50327
+ return text(sections.join(`
50328
+
50329
+ `));
50330
+ }
50331
+ case "share_skill": {
50332
+ const { name: skillName, description: skillDesc, instructions: skillInstr, tags: skillTags } = args ?? {};
50333
+ if (!skillName || !skillDesc || !skillInstr)
50334
+ return text("share_skill: `name`, `description`, and `instructions` required", true);
50335
+ const client2 = allClients()[0];
50336
+ if (!client2)
50337
+ return text("share_skill: not connected", true);
50338
+ const result = await client2.shareSkill(skillName, skillDesc, skillInstr, skillTags);
50339
+ if (!result)
50340
+ return text("share_skill: broker did not acknowledge", true);
50341
+ return text(`Skill "${skillName}" published to the mesh.`);
50342
+ }
50343
+ case "get_skill": {
50344
+ const { name: gsName } = args ?? {};
50345
+ if (!gsName)
50346
+ return text("get_skill: `name` required", true);
50347
+ const client2 = allClients()[0];
50348
+ if (!client2)
50349
+ return text("get_skill: not connected", true);
50350
+ const skill = await client2.getSkill(gsName);
50351
+ if (!skill)
50352
+ return text(`Skill "${gsName}" not found in the mesh.`);
50353
+ return text(`# Skill: ${skill.name}
50354
+
50355
+ **Description:** ${skill.description}
50356
+ **Author:** ${skill.author}
50357
+ **Tags:** ${skill.tags.length ? skill.tags.join(", ") : "none"}
50358
+ **Created:** ${skill.createdAt}
50359
+
50360
+ ---
50361
+
50362
+ ## Instructions
50363
+
50364
+ ${skill.instructions}`);
50365
+ }
50366
+ case "list_skills": {
50367
+ const { query: skillQuery } = args ?? {};
50368
+ const client2 = allClients()[0];
50369
+ if (!client2)
50370
+ return text("list_skills: not connected", true);
50371
+ const skills = await client2.listSkills(skillQuery);
50372
+ if (skills.length === 0)
50373
+ return text(skillQuery ? `No skills found for "${skillQuery}".` : "No skills in the mesh yet.");
50374
+ const lines = skills.map((s) => `- **${s.name}**: ${s.description}${s.tags.length ? ` [${s.tags.join(", ")}]` : ""} (by ${s.author})`);
50375
+ return text(`${skills.length} skill(s):
50376
+ ${lines.join(`
50377
+ `)}`);
50378
+ }
50379
+ case "remove_skill": {
50380
+ const { name: rsName } = args ?? {};
50381
+ if (!rsName)
50382
+ return text("remove_skill: `name` required", true);
50383
+ const client2 = allClients()[0];
50384
+ if (!client2)
50385
+ return text("remove_skill: not connected", true);
50386
+ const removed = await client2.removeSkill(rsName);
50387
+ return text(removed ? `Skill "${rsName}" removed.` : `Skill "${rsName}" not found.`, !removed);
50388
+ }
49426
50389
  case "ping_mesh": {
49427
50390
  const { priorities: pingPriorities } = args ?? {};
49428
50391
  const toTest = pingPriorities ?? ["now", "next"];
@@ -49459,6 +50422,57 @@ ${rows.join(`
49459
50422
  return text(results.join(`
49460
50423
  `));
49461
50424
  }
50425
+ case "mesh_mcp_register": {
50426
+ const { server_name, description, tools: regTools } = args ?? {};
50427
+ if (!server_name || !description || !regTools?.length)
50428
+ return text("mesh_mcp_register: `server_name`, `description`, and `tools` required", true);
50429
+ const client2 = allClients()[0];
50430
+ if (!client2)
50431
+ return text("mesh_mcp_register: not connected", true);
50432
+ const result = await client2.mcpRegister(server_name, description, regTools);
50433
+ if (!result)
50434
+ return text("mesh_mcp_register: broker did not acknowledge", true);
50435
+ return text(`Registered MCP server "${result.serverName}" with ${result.toolCount} tool(s). Other peers can now call its tools via mesh_tool_call.`);
50436
+ }
50437
+ case "mesh_mcp_list": {
50438
+ const client2 = allClients()[0];
50439
+ if (!client2)
50440
+ return text("mesh_mcp_list: not connected", true);
50441
+ const servers = await client2.mcpList();
50442
+ if (servers.length === 0)
50443
+ return text("No MCP servers registered in the mesh.");
50444
+ const lines = servers.map((s) => {
50445
+ const toolList = s.tools.map((t) => ` - **${t.name}**: ${t.description}`).join(`
50446
+ `);
50447
+ return `- **${s.name}** (hosted by ${s.hostedBy}): ${s.description}
50448
+ ${toolList}`;
50449
+ });
50450
+ return text(`${servers.length} MCP server(s) in mesh:
50451
+ ${lines.join(`
50452
+ `)}`);
50453
+ }
50454
+ case "mesh_tool_call": {
50455
+ const { server_name: callServer, tool_name: callTool, args: callArgs } = args ?? {};
50456
+ if (!callServer || !callTool)
50457
+ return text("mesh_tool_call: `server_name` and `tool_name` required", true);
50458
+ const client2 = allClients()[0];
50459
+ if (!client2)
50460
+ return text("mesh_tool_call: not connected", true);
50461
+ const callResult = await client2.mcpCall(callServer, callTool, callArgs ?? {});
50462
+ if (callResult.error)
50463
+ return text(`mesh_tool_call error: ${callResult.error}`, true);
50464
+ return text(typeof callResult.result === "string" ? callResult.result : JSON.stringify(callResult.result, null, 2));
50465
+ }
50466
+ case "mesh_mcp_remove": {
50467
+ const { server_name: rmServer } = args ?? {};
50468
+ if (!rmServer)
50469
+ return text("mesh_mcp_remove: `server_name` required", true);
50470
+ const client2 = allClients()[0];
50471
+ if (!client2)
50472
+ return text("mesh_mcp_remove: not connected", true);
50473
+ await client2.mcpUnregister(rmServer);
50474
+ return text(`Unregistered MCP server "${rmServer}" from the mesh.`);
50475
+ }
49462
50476
  case "grant_file_access": {
49463
50477
  const { fileId, to: grantTo } = args ?? {};
49464
50478
  if (!fileId || !grantTo)
@@ -49491,6 +50505,117 @@ ${rows.join(`
49491
50505
  return text("grant_file_access: broker did not confirm", true);
49492
50506
  return text(`Access granted: ${targetPeer.displayName} can now download file ${fileId}`);
49493
50507
  }
50508
+ case "read_peer_file": {
50509
+ const { peer: peerName, path: filePath } = args ?? {};
50510
+ if (!peerName || !filePath)
50511
+ return text("read_peer_file: `peer` and `path` required", true);
50512
+ const client2 = allClients()[0];
50513
+ if (!client2)
50514
+ return text("read_peer_file: not connected", true);
50515
+ const peers = await client2.listPeers();
50516
+ const nameLower = peerName.toLowerCase();
50517
+ let targetPubkey = null;
50518
+ if (/^[0-9a-f]{64}$/.test(peerName)) {
50519
+ targetPubkey = peerName;
50520
+ } else {
50521
+ const match = peers.find((p) => p.displayName.toLowerCase() === nameLower);
50522
+ if (!match) {
50523
+ const partials = peers.filter((p) => p.displayName.toLowerCase().includes(nameLower));
50524
+ if (partials.length === 1) {
50525
+ targetPubkey = partials[0].pubkey;
50526
+ } else {
50527
+ const names = peers.map((p) => p.displayName).join(", ");
50528
+ return text(`read_peer_file: peer "${peerName}" not found. Online: ${names || "(none)"}`, true);
50529
+ }
50530
+ } else {
50531
+ targetPubkey = match.pubkey;
50532
+ }
50533
+ }
50534
+ const result = await client2.requestFile(targetPubkey, filePath);
50535
+ if (result.error)
50536
+ return text(`read_peer_file: ${result.error}`, true);
50537
+ if (!result.content)
50538
+ return text("read_peer_file: empty response from peer", true);
50539
+ try {
50540
+ const decoded = Buffer.from(result.content, "base64").toString("utf-8");
50541
+ return text(decoded);
50542
+ } catch {
50543
+ return text("read_peer_file: failed to decode file content (binary file?)", true);
50544
+ }
50545
+ }
50546
+ case "list_peer_files": {
50547
+ const { peer: peerName, path: dirPath, pattern } = args ?? {};
50548
+ if (!peerName)
50549
+ return text("list_peer_files: `peer` required", true);
50550
+ const client2 = allClients()[0];
50551
+ if (!client2)
50552
+ return text("list_peer_files: not connected", true);
50553
+ const peers = await client2.listPeers();
50554
+ const nameLower = peerName.toLowerCase();
50555
+ let targetPubkey = null;
50556
+ if (/^[0-9a-f]{64}$/.test(peerName)) {
50557
+ targetPubkey = peerName;
50558
+ } else {
50559
+ const match = peers.find((p) => p.displayName.toLowerCase() === nameLower);
50560
+ if (!match) {
50561
+ const partials = peers.filter((p) => p.displayName.toLowerCase().includes(nameLower));
50562
+ if (partials.length === 1) {
50563
+ targetPubkey = partials[0].pubkey;
50564
+ } else {
50565
+ const names = peers.map((p) => p.displayName).join(", ");
50566
+ return text(`list_peer_files: peer "${peerName}" not found. Online: ${names || "(none)"}`, true);
50567
+ }
50568
+ } else {
50569
+ targetPubkey = match.pubkey;
50570
+ }
50571
+ }
50572
+ const result = await client2.requestDir(targetPubkey, dirPath ?? ".", pattern);
50573
+ if (result.error)
50574
+ return text(`list_peer_files: ${result.error}`, true);
50575
+ if (!result.entries || result.entries.length === 0)
50576
+ return text("No files found.");
50577
+ return text(result.entries.join(`
50578
+ `));
50579
+ }
50580
+ case "create_webhook": {
50581
+ const { name: whName } = args ?? {};
50582
+ if (!whName)
50583
+ return text("create_webhook: `name` required", true);
50584
+ const client2 = allClients()[0];
50585
+ if (!client2)
50586
+ return text("create_webhook: not connected", true);
50587
+ const wh = await client2.createWebhook(whName);
50588
+ if (!wh)
50589
+ return text("create_webhook: broker did not acknowledge — check connection", true);
50590
+ return text(`Webhook **${wh.name}** created.
50591
+
50592
+ URL: ${wh.url}
50593
+ Secret: ${wh.secret}
50594
+
50595
+ External services can POST JSON to this URL. The payload will be pushed to all connected mesh peers.`);
50596
+ }
50597
+ case "list_webhooks": {
50598
+ const client2 = allClients()[0];
50599
+ if (!client2)
50600
+ return text("list_webhooks: not connected", true);
50601
+ const webhooks = await client2.listWebhooks();
50602
+ if (webhooks.length === 0)
50603
+ return text("No active webhooks.");
50604
+ const lines = webhooks.map((w) => `- **${w.name}** — ${w.url} (created ${w.createdAt})`);
50605
+ return text(`${webhooks.length} webhook(s):
50606
+ ${lines.join(`
50607
+ `)}`);
50608
+ }
50609
+ case "delete_webhook": {
50610
+ const { name: delName } = args ?? {};
50611
+ if (!delName)
50612
+ return text("delete_webhook: `name` required", true);
50613
+ const client2 = allClients()[0];
50614
+ if (!client2)
50615
+ return text("delete_webhook: not connected", true);
50616
+ const ok = await client2.deleteWebhook(delName);
50617
+ return text(ok ? `Webhook "${delName}" deactivated.` : `Failed to deactivate webhook "${delName}".`, !ok);
50618
+ }
49494
50619
  default:
49495
50620
  return text(`Unknown tool: ${name}`, true);
49496
50621
  }
@@ -49502,6 +50627,44 @@ ${rows.join(`
49502
50627
  client2.onPush(async (msg) => {
49503
50628
  if (messageMode === "off")
49504
50629
  return;
50630
+ if (msg.subtype === "system" && msg.event) {
50631
+ const eventName = msg.event;
50632
+ const data = msg.eventData ?? {};
50633
+ let content2;
50634
+ if (eventName === "tick") {
50635
+ const tick = data.tick ?? 0;
50636
+ const simTime = String(data.simTime ?? "").replace("T", " ").replace(/\..*/, "");
50637
+ const speed = data.speed ?? 1;
50638
+ content2 = `[heartbeat] tick ${tick} | sim time: ${simTime} | speed: x${speed}`;
50639
+ } else if (eventName === "peer_joined") {
50640
+ content2 = `[system] Peer "${data.name ?? "unknown"}" joined the mesh`;
50641
+ } else if (eventName === "peer_left") {
50642
+ content2 = `[system] Peer "${data.name ?? "unknown"}" left the mesh`;
50643
+ } else {
50644
+ content2 = `[system] ${eventName}: ${JSON.stringify(data)}`;
50645
+ }
50646
+ try {
50647
+ await server.notification({
50648
+ method: "notifications/claude/channel",
50649
+ params: {
50650
+ content: content2,
50651
+ meta: {
50652
+ kind: "system",
50653
+ event: eventName,
50654
+ mesh_slug: client2.meshSlug,
50655
+ mesh_id: client2.meshId,
50656
+ ...Object.keys(data).length > 0 ? { eventData: data } : {}
50657
+ }
50658
+ }
50659
+ });
50660
+ process.stderr.write(`[claudemesh] system: ${content2}
50661
+ `);
50662
+ } catch (pushErr) {
50663
+ process.stderr.write(`[claudemesh] system push FAILED: ${pushErr}
50664
+ `);
50665
+ }
50666
+ return;
50667
+ }
49505
50668
  const fromPubkey = msg.senderPubkey || "";
49506
50669
  const fromName = fromPubkey ? await resolvePeerName(client2, fromPubkey) : "unknown";
49507
50670
  if (messageMode === "inbox") {
@@ -49600,6 +50763,7 @@ ${rows.join(`
49600
50763
  }
49601
50764
 
49602
50765
  // src/commands/install.ts
50766
+ init_config();
49603
50767
  import {
49604
50768
  chmodSync as chmodSync2,
49605
50769
  copyFileSync,
@@ -49725,47 +50889,51 @@ function writeClaudeSettings(obj) {
49725
50889
  `, "utf-8");
49726
50890
  }
49727
50891
  var CLAUDEMESH_TOOLS = [
49728
- "mcp__claudemesh__send_message",
49729
- "mcp__claudemesh__list_peers",
50892
+ "mcp__claudemesh__cancel_scheduled",
49730
50893
  "mcp__claudemesh__check_messages",
49731
- "mcp__claudemesh__set_summary",
49732
- "mcp__claudemesh__set_status",
49733
- "mcp__claudemesh__join_group",
49734
- "mcp__claudemesh__leave_group",
49735
- "mcp__claudemesh__get_state",
49736
- "mcp__claudemesh__set_state",
49737
- "mcp__claudemesh__list_state",
49738
- "mcp__claudemesh__remember",
49739
- "mcp__claudemesh__recall",
50894
+ "mcp__claudemesh__claim_task",
50895
+ "mcp__claudemesh__complete_task",
50896
+ "mcp__claudemesh__create_stream",
50897
+ "mcp__claudemesh__create_task",
50898
+ "mcp__claudemesh__delete_file",
50899
+ "mcp__claudemesh__file_status",
49740
50900
  "mcp__claudemesh__forget",
49741
- "mcp__claudemesh__share_file",
50901
+ "mcp__claudemesh__get_context",
49742
50902
  "mcp__claudemesh__get_file",
49743
- "mcp__claudemesh__list_files",
49744
- "mcp__claudemesh__file_status",
49745
- "mcp__claudemesh__delete_file",
49746
- "mcp__claudemesh__vector_store",
49747
- "mcp__claudemesh__vector_search",
49748
- "mcp__claudemesh__vector_delete",
49749
- "mcp__claudemesh__list_collections",
49750
- "mcp__claudemesh__graph_query",
50903
+ "mcp__claudemesh__get_state",
50904
+ "mcp__claudemesh__grant_file_access",
49751
50905
  "mcp__claudemesh__graph_execute",
49752
- "mcp__claudemesh__mesh_info",
49753
- "mcp__claudemesh__ping_mesh",
49754
- "mcp__claudemesh__message_status",
49755
- "mcp__claudemesh__share_context",
49756
- "mcp__claudemesh__get_context",
50906
+ "mcp__claudemesh__graph_query",
50907
+ "mcp__claudemesh__join_group",
50908
+ "mcp__claudemesh__leave_group",
50909
+ "mcp__claudemesh__list_collections",
49757
50910
  "mcp__claudemesh__list_contexts",
49758
- "mcp__claudemesh__create_task",
49759
- "mcp__claudemesh__claim_task",
49760
- "mcp__claudemesh__complete_task",
49761
- "mcp__claudemesh__list_tasks",
49762
- "mcp__claudemesh__create_stream",
49763
- "mcp__claudemesh__publish",
49764
- "mcp__claudemesh__subscribe",
50911
+ "mcp__claudemesh__list_files",
50912
+ "mcp__claudemesh__list_peers",
50913
+ "mcp__claudemesh__list_scheduled",
50914
+ "mcp__claudemesh__list_state",
49765
50915
  "mcp__claudemesh__list_streams",
50916
+ "mcp__claudemesh__list_tasks",
49766
50917
  "mcp__claudemesh__mesh_execute",
50918
+ "mcp__claudemesh__mesh_info",
49767
50919
  "mcp__claudemesh__mesh_query",
49768
- "mcp__claudemesh__mesh_schema"
50920
+ "mcp__claudemesh__mesh_schema",
50921
+ "mcp__claudemesh__message_status",
50922
+ "mcp__claudemesh__ping_mesh",
50923
+ "mcp__claudemesh__publish",
50924
+ "mcp__claudemesh__recall",
50925
+ "mcp__claudemesh__remember",
50926
+ "mcp__claudemesh__schedule_reminder",
50927
+ "mcp__claudemesh__send_message",
50928
+ "mcp__claudemesh__set_state",
50929
+ "mcp__claudemesh__set_status",
50930
+ "mcp__claudemesh__set_summary",
50931
+ "mcp__claudemesh__share_context",
50932
+ "mcp__claudemesh__share_file",
50933
+ "mcp__claudemesh__subscribe",
50934
+ "mcp__claudemesh__vector_delete",
50935
+ "mcp__claudemesh__vector_search",
50936
+ "mcp__claudemesh__vector_store"
49769
50937
  ];
49770
50938
  function installAllowedTools() {
49771
50939
  const settings = readClaudeSettings();
@@ -49896,10 +51064,22 @@ function runInstall(args = []) {
49896
51064
  } else {
49897
51065
  console.log(dim("· Hooks skipped (--no-hooks)"));
49898
51066
  }
51067
+ let hasMeshes = false;
51068
+ try {
51069
+ const meshConfig = loadConfig();
51070
+ hasMeshes = meshConfig.meshes.length > 0;
51071
+ } catch {}
49899
51072
  console.log("");
49900
51073
  console.log(yellow(bold2("⚠ RESTART CLAUDE CODE")) + yellow(" for MCP tools to appear."));
49901
- console.log("");
49902
- console.log(`Next: ${bold2("claudemesh join https://claudemesh.com/join/<token>")}`);
51074
+ if (!hasMeshes) {
51075
+ console.log("");
51076
+ console.log(yellow("No meshes joined.") + " To connect with peers:");
51077
+ console.log(` ${bold2("claudemesh join <invite-url>")}` + dim(" — join an existing mesh"));
51078
+ console.log(` ${dim("Create one at")} ${bold2("https://claudemesh.com/dashboard")}`);
51079
+ } else {
51080
+ console.log("");
51081
+ console.log(`Next: ${bold2("claudemesh join https://claudemesh.com/join/<token>")}`);
51082
+ }
49903
51083
  console.log("");
49904
51084
  console.log(yellow("⚠ For real-time push messages from peers, launch with:"));
49905
51085
  console.log(` ${bold2("claudemesh launch")}` + dim(" (or: claude --dangerously-load-development-channels server:claudemesh)"));
@@ -50559,7 +51739,7 @@ init_config();
50559
51739
  // package.json
50560
51740
  var package_default = {
50561
51741
  name: "claudemesh-cli",
50562
- version: "0.6.8",
51742
+ version: "0.7.0",
50563
51743
  description: "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.",
50564
51744
  keywords: [
50565
51745
  "claude-code",
@@ -51025,8 +52205,19 @@ async function runPeers(flags) {
51025
52205
  const groups = p.groups.length ? " [" + p.groups.map((g) => `@${g.name}${g.role ? `:${g.role}` : ""}`).join(", ") + "]" : "";
51026
52206
  const statusIcon = p.status === "working" ? yellow("●") : green("●");
51027
52207
  const name = bold2(p.displayName);
52208
+ const meta2 = [];
52209
+ if (p.peerType)
52210
+ meta2.push(p.peerType);
52211
+ if (p.channel)
52212
+ meta2.push(p.channel);
52213
+ if (p.model)
52214
+ meta2.push(p.model);
52215
+ const metaStr = meta2.length ? dim(` (${meta2.join(", ")})`) : "";
52216
+ const cwdStr = p.cwd ? dim(` cwd: ${p.cwd}`) : "";
51028
52217
  const summary = p.summary ? dim(` ${p.summary}`) : "";
51029
- console.log(` ${statusIcon} ${name}${groups}${summary}`);
52218
+ console.log(` ${statusIcon} ${name}${groups}${metaStr}${summary}`);
52219
+ if (cwdStr)
52220
+ console.log(` ${cwdStr}`);
51030
52221
  }
51031
52222
  console.log("");
51032
52223
  });
@@ -51313,13 +52504,15 @@ async function runRemind(flags, positional) {
51313
52504
  if (!message) {
51314
52505
  console.error("Usage: claudemesh remind <message> --in <duration>");
51315
52506
  console.error(" claudemesh remind <message> --at <time>");
52507
+ console.error(' claudemesh remind <message> --cron "0 */2 * * *"');
51316
52508
  console.error(" claudemesh remind list");
51317
52509
  console.error(" claudemesh remind cancel <id>");
51318
52510
  process.exit(1);
51319
52511
  }
51320
- const deliverAt = parseDeliverAt(flags);
51321
- if (deliverAt === null) {
51322
- console.error('Specify when: --in <duration> (e.g. "2h", "30m") or --at <time> (e.g. "15:00")');
52512
+ const isCron = !!flags.cron;
52513
+ const deliverAt = isCron ? 0 : parseDeliverAt(flags);
52514
+ if (!isCron && deliverAt === null) {
52515
+ console.error('Specify when: --in <duration> (e.g. "2h", "30m"), --at <time> (e.g. "15:00"), or --cron <expression>');
51323
52516
  process.exit(1);
51324
52517
  }
51325
52518
  await withMesh({ meshSlug: flags.mesh ?? null }, async (client2) => {
@@ -51339,7 +52532,7 @@ async function runRemind(flags, positional) {
51339
52532
  } else {
51340
52533
  targetSpec = client2.getSessionPubkey() ?? "*";
51341
52534
  }
51342
- const result = await client2.scheduleMessage(targetSpec, message, deliverAt);
52535
+ const result = await client2.scheduleMessage(targetSpec, message, deliverAt ?? 0, false, flags.cron);
51343
52536
  if (!result) {
51344
52537
  console.error("✗ Broker did not acknowledge — check connection");
51345
52538
  process.exit(1);
@@ -51348,34 +52541,170 @@ async function runRemind(flags, positional) {
51348
52541
  console.log(JSON.stringify(result));
51349
52542
  return;
51350
52543
  }
51351
- const when = new Date(result.deliverAt).toLocaleString();
51352
52544
  const toLabel = !flags.to || flags.to === "self" ? "yourself" : flags.to;
51353
- console.log(`✓ Reminder set (${result.scheduledId.slice(0, 8)}): "${message}" → ${toLabel} at ${when}`);
52545
+ if (isCron) {
52546
+ const nextFire = new Date(result.deliverAt).toLocaleString();
52547
+ console.log(`✓ Recurring reminder set (${result.scheduledId.slice(0, 8)}): "${message}" → ${toLabel} — cron: ${flags.cron}, next fire: ${nextFire}`);
52548
+ } else {
52549
+ const when = new Date(result.deliverAt).toLocaleString();
52550
+ console.log(`✓ Reminder set (${result.scheduledId.slice(0, 8)}): "${message}" → ${toLabel} at ${when}`);
52551
+ }
51354
52552
  });
51355
52553
  }
52554
+ // src/templates/dev-team.json
52555
+ var dev_team_default = {
52556
+ name: "dev-team",
52557
+ description: "Software development team with frontend, backend, and devops groups",
52558
+ groups: [
52559
+ { name: "frontend", roles: ["lead", "member"] },
52560
+ { name: "backend", roles: ["lead", "member"] },
52561
+ { name: "devops", roles: ["lead", "member"] },
52562
+ { name: "qa", roles: ["lead", "member"] }
52563
+ ],
52564
+ stateKeys: {
52565
+ sprint: "current",
52566
+ "deploy-frozen": "false",
52567
+ "pr-queue": "[]"
52568
+ },
52569
+ suggestedRoles: ["lead", "member", "reviewer"],
52570
+ systemPromptHint: "You are part of a dev team. Coordinate with @frontend, @backend, @devops groups. Check state keys for sprint status and deploy freezes before making changes."
52571
+ };
52572
+ // src/templates/research.json
52573
+ var research_default = {
52574
+ name: "research",
52575
+ description: "Research and analysis team focused on deep investigation and knowledge sharing",
52576
+ groups: [
52577
+ { name: "analysis", roles: ["lead", "analyst"] },
52578
+ { name: "writing", roles: ["lead", "writer", "reviewer"] },
52579
+ { name: "data", roles: ["engineer", "analyst"] }
52580
+ ],
52581
+ stateKeys: {
52582
+ "research-topic": "",
52583
+ phase: "exploration",
52584
+ "findings-count": "0"
52585
+ },
52586
+ suggestedRoles: ["lead", "analyst", "writer", "reviewer"],
52587
+ systemPromptHint: "You are part of a research team. Share findings via remember(), use recall() before starting new analysis. Coordinate phases through state keys."
52588
+ };
52589
+ // src/templates/ops-incident.json
52590
+ var ops_incident_default = {
52591
+ name: "ops-incident",
52592
+ description: "Incident response team with oncall, comms, and engineering groups",
52593
+ groups: [
52594
+ { name: "oncall", roles: ["primary", "secondary"] },
52595
+ { name: "comms", roles: ["lead", "scribe"] },
52596
+ { name: "engineering", roles: ["lead", "responder"] }
52597
+ ],
52598
+ stateKeys: {
52599
+ "incident-status": "investigating",
52600
+ severity: "unknown",
52601
+ commander: "",
52602
+ timeline: "[]"
52603
+ },
52604
+ suggestedRoles: ["commander", "primary-oncall", "scribe", "responder"],
52605
+ systemPromptHint: "INCIDENT MODE. Priority: now for all messages. Update incident-status state. Commander coordinates. Scribe maintains timeline. Engineering fixes."
52606
+ };
52607
+ // src/templates/simulation.json
52608
+ var simulation_default = {
52609
+ name: "simulation",
52610
+ description: "Load testing simulation with configurable time multiplier and user personas",
52611
+ groups: [
52612
+ { name: "personas", roles: ["admin", "user", "customer"] },
52613
+ { name: "observers", roles: ["monitor", "analyst"] },
52614
+ { name: "control", roles: ["orchestrator"] }
52615
+ ],
52616
+ stateKeys: {
52617
+ "clock-speed": "x1",
52618
+ "sim-status": "paused",
52619
+ "tick-count": "0",
52620
+ scenario: ""
52621
+ },
52622
+ suggestedRoles: ["orchestrator", "persona", "monitor"],
52623
+ systemPromptHint: "SIMULATION MODE. Follow the clock-speed state for time multiplier. Act according to your persona role and the simulated time. Report actions to @observers."
52624
+ };
52625
+ // src/templates/personal.json
52626
+ var personal_default = {
52627
+ name: "personal",
52628
+ description: "Private mesh for a single user — all sessions auto-join",
52629
+ groups: [],
52630
+ stateKeys: {
52631
+ focus: "",
52632
+ todos: "[]"
52633
+ },
52634
+ suggestedRoles: [],
52635
+ systemPromptHint: "Personal workspace. All your Claude Code sessions share this mesh. Use state keys to track focus and todos across sessions."
52636
+ };
52637
+
52638
+ // src/templates/index.ts
52639
+ var TEMPLATES = {
52640
+ "dev-team": dev_team_default,
52641
+ research: research_default,
52642
+ "ops-incident": ops_incident_default,
52643
+ simulation: simulation_default,
52644
+ personal: personal_default
52645
+ };
52646
+ function listTemplates() {
52647
+ return Object.values(TEMPLATES);
52648
+ }
52649
+ function getTemplate(name) {
52650
+ return TEMPLATES[name];
52651
+ }
52652
+
52653
+ // src/commands/create.ts
52654
+ function runCreate(args) {
52655
+ if (args["list-templates"]) {
52656
+ console.log(`Available mesh templates:
52657
+ `);
52658
+ for (const t of listTemplates()) {
52659
+ console.log(` ${t.name}`);
52660
+ console.log(` ${t.description}`);
52661
+ console.log(` Groups: ${t.groups.map((g) => g.name).join(", ") || "(none)"}`);
52662
+ console.log(` State keys: ${Object.keys(t.stateKeys).join(", ") || "(none)"}`);
52663
+ console.log();
52664
+ }
52665
+ return;
52666
+ }
52667
+ const templateName = args.template;
52668
+ if (templateName) {
52669
+ const template = getTemplate(templateName);
52670
+ if (!template) {
52671
+ console.error(`Unknown template "${templateName}". Use --list-templates to see available options.`);
52672
+ process.exit(1);
52673
+ }
52674
+ console.log(`Template "${template.name}" loaded:`);
52675
+ console.log(` Groups: ${template.groups.map((g) => `@${g.name}`).join(", ")}`);
52676
+ console.log(` State keys: ${Object.keys(template.stateKeys).join(", ")}`);
52677
+ console.log(` Hint: ${template.systemPromptHint.slice(0, 80)}...`);
52678
+ console.log();
52679
+ console.log("Template applied. Use `claudemesh launch` with --groups to join the predefined groups.");
52680
+ return;
52681
+ }
52682
+ console.log("Usage: claudemesh create --template <name>");
52683
+ console.log(" claudemesh create --list-templates");
52684
+ }
51356
52685
 
51357
52686
  // src/index.ts
51358
52687
  var launch = defineCommand({
51359
52688
  meta: {
51360
52689
  name: "launch",
51361
- description: "Launch Claude Code connected to a mesh with real-time peer messaging"
52690
+ description: "Spawn a Claude Code session with mesh connectivity and MCP tools"
51362
52691
  },
51363
52692
  args: {
51364
52693
  name: {
51365
52694
  type: "string",
51366
- description: "Display name for this session"
52695
+ description: "Display name visible to other peers"
51367
52696
  },
51368
52697
  role: {
51369
52698
  type: "string",
51370
- description: "Role tag (dev, lead, analyst — free-form)"
52699
+ description: "Free-form role tag: `dev`, `lead`, `analyst`, etc"
51371
52700
  },
51372
52701
  groups: {
51373
52702
  type: "string",
51374
- description: 'Groups to join: "group:role,group2"colon sets role. Hierarchy via slash: "eng/frontend:lead"'
52703
+ description: 'Groups to join as `group:role,...`e.g. `"eng/frontend:lead,qa:member"`'
51375
52704
  },
51376
52705
  mesh: {
51377
52706
  type: "string",
51378
- description: "Select mesh by slug (interactive picker if omitted and >1 joined)"
52707
+ description: "Mesh slug (interactive picker if omitted and >1 joined)"
51379
52708
  },
51380
52709
  join: {
51381
52710
  type: "string",
@@ -51383,21 +52712,21 @@ var launch = defineCommand({
51383
52712
  },
51384
52713
  "message-mode": {
51385
52714
  type: "string",
51386
- description: "push (default) | inbox | off — controls how peer messages are delivered"
52715
+ description: '`"push"` (default) | `"inbox"` | `"off"` — how peer messages arrive'
51387
52716
  },
51388
52717
  "system-prompt": {
51389
52718
  type: "string",
51390
- description: "Set Claude's system prompt for this session"
52719
+ description: "Custom system prompt for this Claude session"
51391
52720
  },
51392
52721
  yes: {
51393
52722
  type: "boolean",
51394
52723
  alias: "y",
51395
- description: "Skip permission confirmation",
52724
+ description: "Skip the --dangerously-skip-permissions confirmation",
51396
52725
  default: false
51397
52726
  },
51398
52727
  quiet: {
51399
52728
  type: "boolean",
51400
- description: "Skip banner and all interactive prompts",
52729
+ description: "Suppress banner and interactive prompts",
51401
52730
  default: false
51402
52731
  }
51403
52732
  },
@@ -51408,7 +52737,7 @@ var launch = defineCommand({
51408
52737
  var install = defineCommand({
51409
52738
  meta: {
51410
52739
  name: "install",
51411
- description: "Register MCP server + status hooks with Claude Code"
52740
+ description: "Register MCP server and status hooks with Claude Code"
51412
52741
  },
51413
52742
  args: {
51414
52743
  "no-hooks": {
@@ -51424,12 +52753,12 @@ var install = defineCommand({
51424
52753
  var join7 = defineCommand({
51425
52754
  meta: {
51426
52755
  name: "join",
51427
- description: "Join a mesh via invite URL"
52756
+ description: "Join a mesh via invite URL or token"
51428
52757
  },
51429
52758
  args: {
51430
52759
  url: {
51431
52760
  type: "positional",
51432
- description: "Invite URL (https://claudemesh.com/join/...)",
52761
+ description: "Invite URL (`https://claudemesh.com/join/...`) or token",
51433
52762
  required: true
51434
52763
  }
51435
52764
  },
@@ -51440,12 +52769,12 @@ var join7 = defineCommand({
51440
52769
  var leave = defineCommand({
51441
52770
  meta: {
51442
52771
  name: "leave",
51443
- description: "Leave a joined mesh"
52772
+ description: "Leave a joined mesh and remove its local keypair"
51444
52773
  },
51445
52774
  args: {
51446
52775
  slug: {
51447
52776
  type: "positional",
51448
- description: "Mesh slug to leave",
52777
+ description: "Mesh slug to leave (see `claudemesh list`)",
51449
52778
  required: true
51450
52779
  }
51451
52780
  },
@@ -51461,23 +52790,33 @@ var main = defineCommand({
51461
52790
  },
51462
52791
  subCommands: {
51463
52792
  launch,
52793
+ create: defineCommand({
52794
+ meta: { name: "create", description: "Create a new mesh from a template" },
52795
+ args: {
52796
+ template: { type: "string", description: "Template name: `dev-team`, `research`, `ops-incident`, `simulation`, `personal`" },
52797
+ "list-templates": { type: "boolean", description: "List available templates and exit", default: false }
52798
+ },
52799
+ run({ args }) {
52800
+ runCreate(args);
52801
+ }
52802
+ }),
51464
52803
  install,
51465
52804
  uninstall: defineCommand({
51466
- meta: { name: "uninstall", description: "Remove MCP server and hooks" },
52805
+ meta: { name: "uninstall", description: "Remove MCP server and hooks from Claude Code config" },
51467
52806
  run() {
51468
52807
  runUninstall();
51469
52808
  }
51470
52809
  }),
51471
52810
  join: join7,
51472
52811
  list: defineCommand({
51473
- meta: { name: "list", description: "Show joined meshes and identities" },
52812
+ meta: { name: "list", description: "Show joined meshes, slugs, and local identities" },
51474
52813
  run() {
51475
52814
  runList();
51476
52815
  }
51477
52816
  }),
51478
52817
  leave,
51479
52818
  peers: defineCommand({
51480
- meta: { name: "peers", description: "List connected peers in the mesh" },
52819
+ meta: { name: "peers", description: "List online peers with status, summary, and groups" },
51481
52820
  args: {
51482
52821
  mesh: { type: "string", description: "Mesh slug (auto-selected if only one joined)" },
51483
52822
  json: { type: "boolean", description: "Output as JSON", default: false }
@@ -51487,34 +52826,34 @@ var main = defineCommand({
51487
52826
  }
51488
52827
  }),
51489
52828
  send: defineCommand({
51490
- meta: { name: "send", description: "Send a message to a peer, group, or broadcast" },
52829
+ meta: { name: "send", description: "Send a message to a peer, group, or all peers" },
51491
52830
  args: {
51492
- to: { type: "positional", description: "Recipient: display name, @group, pubkey, or *", required: true },
52831
+ to: { type: "positional", description: "Recipient: display name, `@group`, `*` (broadcast), or pubkey hex", required: true },
51493
52832
  message: { type: "positional", description: "Message text", required: true },
51494
52833
  mesh: { type: "string", description: "Mesh slug (auto-selected if only one joined)" },
51495
- priority: { type: "string", description: "now | next (default) | low" }
52834
+ priority: { type: "string", description: '`"now"` | `"next"` (default) | `"low"`' }
51496
52835
  },
51497
52836
  async run({ args }) {
51498
52837
  await runSend(args, args.to, args.message);
51499
52838
  }
51500
52839
  }),
51501
52840
  inbox: defineCommand({
51502
- meta: { name: "inbox", description: "Read pending peer messages" },
52841
+ meta: { name: "inbox", description: "Drain pending inbound messages" },
51503
52842
  args: {
51504
52843
  mesh: { type: "string", description: "Mesh slug (auto-selected if only one joined)" },
51505
52844
  json: { type: "boolean", description: "Output as JSON", default: false },
51506
- wait: { type: "string", description: "Seconds to wait for broker delivery (default: 1)" }
52845
+ wait: { type: "string", description: "Seconds to wait for broker delivery (default: `1`)" }
51507
52846
  },
51508
52847
  async run({ args }) {
51509
52848
  await runInbox({ ...args, wait: args.wait ? parseInt(args.wait, 10) : undefined });
51510
52849
  }
51511
52850
  }),
51512
52851
  state: defineCommand({
51513
- meta: { name: "state", description: "Read or write shared mesh state" },
52852
+ meta: { name: "state", description: "Get, set, or list shared key-value state in the mesh" },
51514
52853
  args: {
51515
- action: { type: "positional", description: "get | set | list", required: true },
51516
- key: { type: "positional", description: "State key (required for get/set)" },
51517
- value: { type: "positional", description: "Value to set (required for set)" },
52854
+ action: { type: "positional", description: "`get <key>` | `set <key> <value>` | `list`", required: true },
52855
+ key: { type: "positional", description: "State key (required for `get` and `set`)" },
52856
+ value: { type: "positional", description: "Value to store (required for `set`)" },
51518
52857
  mesh: { type: "string", description: "Mesh slug (auto-selected if only one joined)" },
51519
52858
  json: { type: "boolean", description: "Output as JSON", default: false }
51520
52859
  },
@@ -51550,11 +52889,11 @@ var main = defineCommand({
51550
52889
  }
51551
52890
  }),
51552
52891
  remember: defineCommand({
51553
- meta: { name: "remember", description: "Store a memory in the mesh (accessible to all peers)" },
52892
+ meta: { name: "remember", description: "Store a persistent memory visible to all peers" },
51554
52893
  args: {
51555
- content: { type: "positional", description: "Text to remember", required: true },
52894
+ content: { type: "positional", description: "Text to store", required: true },
51556
52895
  mesh: { type: "string", description: "Mesh slug (auto-selected if only one joined)" },
51557
- tags: { type: "string", description: "Comma-separated tags (e.g. task,context)" },
52896
+ tags: { type: "string", description: "Comma-separated tags, e.g. `task,context`" },
51558
52897
  json: { type: "boolean", description: "Output as JSON", default: false }
51559
52898
  },
51560
52899
  async run({ args }) {
@@ -51562,9 +52901,9 @@ var main = defineCommand({
51562
52901
  }
51563
52902
  }),
51564
52903
  recall: defineCommand({
51565
- meta: { name: "recall", description: "Search mesh memory by keyword or phrase" },
52904
+ meta: { name: "recall", description: "Search mesh memories by keyword or phrase" },
51566
52905
  args: {
51567
- query: { type: "positional", description: "Search query", required: true },
52906
+ query: { type: "positional", description: "Full-text search query", required: true },
51568
52907
  mesh: { type: "string", description: "Mesh slug (auto-selected if only one joined)" },
51569
52908
  json: { type: "boolean", description: "Output as JSON", default: false }
51570
52909
  },
@@ -51573,13 +52912,14 @@ var main = defineCommand({
51573
52912
  }
51574
52913
  }),
51575
52914
  remind: defineCommand({
51576
- meta: { name: "remind", description: "Schedule a reminder or delayed message via the broker" },
52915
+ meta: { name: "remind", description: "Schedule a delayed message. Also: `remind list`, `remind cancel <id>`" },
51577
52916
  args: {
51578
- message: { type: "positional", description: "Message text, or: list | cancel <id>", required: false },
51579
- extra: { type: "positional", description: "Additional positional args", required: false },
51580
- in: { type: "string", description: 'Deliver after duration: "2h", "30m", "90s"' },
51581
- at: { type: "string", description: 'Deliver at time: "15:00" or ISO timestamp' },
51582
- to: { type: "string", description: "Recipient (default: self). Name, @group, pubkey, or *" },
52917
+ message: { type: "positional", description: "Message text or `list` / `cancel <id>` to manage reminders", required: false },
52918
+ extra: { type: "positional", description: "Reminder ID for `cancel`", required: false },
52919
+ in: { type: "string", description: 'Deliver after duration: `"2h"`, `"30m"`, `"90s"`' },
52920
+ at: { type: "string", description: 'Deliver at time: `"15:00"` or ISO timestamp' },
52921
+ cron: { type: "string", description: 'Recurring cron expression: `"0 */2 * * *"` (every 2h), `"30 9 * * 1-5"` (9:30 weekdays)' },
52922
+ to: { type: "string", description: "Recipient (default: self). Name, `@group`, `*`, or pubkey" },
51583
52923
  mesh: { type: "string", description: "Mesh slug (auto-selected if only one joined)" },
51584
52924
  json: { type: "boolean", description: "Output as JSON", default: false }
51585
52925
  },
@@ -51589,31 +52929,31 @@ var main = defineCommand({
51589
52929
  }
51590
52930
  }),
51591
52931
  status: defineCommand({
51592
- meta: { name: "status", description: "Check broker reachability for each joined mesh" },
52932
+ meta: { name: "status", description: "Check broker connectivity for each joined mesh" },
51593
52933
  async run() {
51594
52934
  await runStatus();
51595
52935
  }
51596
52936
  }),
51597
52937
  doctor: defineCommand({
51598
- meta: { name: "doctor", description: "Diagnose install, config, keypairs, and PATH" },
52938
+ meta: { name: "doctor", description: "Diagnose install, config, keypairs, and PATH issues" },
51599
52939
  async run() {
51600
52940
  await runDoctor();
51601
52941
  }
51602
52942
  }),
51603
52943
  mcp: defineCommand({
51604
- meta: { name: "mcp", description: "Start MCP server (stdio invoked by Claude Code, not users)" },
52944
+ meta: { name: "mcp", description: "Start MCP server on stdio (called by Claude Code, not users)" },
51605
52945
  async run() {
51606
52946
  await startMcpServer();
51607
52947
  }
51608
52948
  }),
51609
52949
  "seed-test-mesh": defineCommand({
51610
- meta: { name: "seed-test-mesh", description: "Dev only: inject a mesh into config (skips invite flow)" },
52950
+ meta: { name: "seed-test-mesh", description: "Dev: inject a mesh into local config, skip invite flow" },
51611
52951
  run({ rawArgs }) {
51612
52952
  runSeedTestMesh(rawArgs);
51613
52953
  }
51614
52954
  }),
51615
52955
  hook: defineCommand({
51616
- meta: { name: "hook", description: "Internal hook handler (invoked by Claude Code hooks)" },
52956
+ meta: { name: "hook", description: "Internal: handle Claude Code hook events" },
51617
52957
  async run({ rawArgs }) {
51618
52958
  await runHook(rawArgs);
51619
52959
  }