claudemesh-cli 0.6.9 → 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.
- package/dist/index.js +1125 -6
- package/package.json +4 -4
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.",
|
|
@@ -47413,6 +47453,144 @@ var TOOLS = [
|
|
|
47413
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.",
|
|
47414
47454
|
inputSchema: { type: "object", properties: {} }
|
|
47415
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
|
+
},
|
|
47416
47594
|
{
|
|
47417
47595
|
name: "ping_mesh",
|
|
47418
47596
|
description: "Send test messages through the full pipeline and measure round-trip timing per priority. Diagnoses push delivery issues.",
|
|
@@ -47426,6 +47604,61 @@ var TOOLS = [
|
|
|
47426
47604
|
}
|
|
47427
47605
|
}
|
|
47428
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
|
+
}
|
|
47429
47662
|
}
|
|
47430
47663
|
];
|
|
47431
47664
|
|
|
@@ -47511,10 +47744,21 @@ class BrokerClient {
|
|
|
47511
47744
|
sessionPubkey = null;
|
|
47512
47745
|
sessionSecretKey = null;
|
|
47513
47746
|
grantFileAccessResolvers = new Map;
|
|
47747
|
+
peerFileResponseResolvers = new Map;
|
|
47748
|
+
peerDirResponseResolvers = new Map;
|
|
47749
|
+
sharedDirs = [process.cwd()];
|
|
47514
47750
|
closed = false;
|
|
47515
47751
|
reconnectAttempt = 0;
|
|
47516
47752
|
helloTimer = null;
|
|
47517
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;
|
|
47518
47762
|
constructor(mesh, opts = {}) {
|
|
47519
47763
|
this.mesh = mesh;
|
|
47520
47764
|
this.opts = opts;
|
|
@@ -47596,6 +47840,7 @@ class BrokerClient {
|
|
|
47596
47840
|
this.setConnStatus("open");
|
|
47597
47841
|
this.reconnectAttempt = 0;
|
|
47598
47842
|
this.flushOutbound();
|
|
47843
|
+
this.startStatsReporting();
|
|
47599
47844
|
resolve();
|
|
47600
47845
|
return;
|
|
47601
47846
|
}
|
|
@@ -47635,6 +47880,7 @@ class BrokerClient {
|
|
|
47635
47880
|
nonce = randomNonce();
|
|
47636
47881
|
ciphertext = Buffer.from(message, "utf-8").toString("base64");
|
|
47637
47882
|
}
|
|
47883
|
+
this._statsCounters.messagesOut++;
|
|
47638
47884
|
return new Promise((resolve) => {
|
|
47639
47885
|
if (this.pendingSends.size >= MAX_QUEUED) {
|
|
47640
47886
|
resolve({ ok: false, error: "outbound queue full" });
|
|
@@ -47692,6 +47938,16 @@ class BrokerClient {
|
|
|
47692
47938
|
return;
|
|
47693
47939
|
this.ws.send(JSON.stringify({ type: "set_status", status }));
|
|
47694
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
|
+
}
|
|
47695
47951
|
async listPeers() {
|
|
47696
47952
|
if (!this.ws || this.ws.readyState !== this.ws.OPEN)
|
|
47697
47953
|
return [];
|
|
@@ -47709,6 +47965,34 @@ class BrokerClient {
|
|
|
47709
47965
|
return;
|
|
47710
47966
|
this.ws.send(JSON.stringify({ type: "set_summary", summary }));
|
|
47711
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
|
+
}
|
|
47712
47996
|
async joinGroup(name, role) {
|
|
47713
47997
|
if (!this.ws || this.ws.readyState !== this.ws.OPEN)
|
|
47714
47998
|
return;
|
|
@@ -47841,6 +48125,10 @@ class BrokerClient {
|
|
|
47841
48125
|
scheduledAckResolvers = new Map;
|
|
47842
48126
|
scheduledListResolvers = new Map;
|
|
47843
48127
|
cancelScheduledResolvers = new Map;
|
|
48128
|
+
mcpRegisterResolvers = new Map;
|
|
48129
|
+
mcpListResolvers = new Map;
|
|
48130
|
+
mcpCallResolvers = new Map;
|
|
48131
|
+
mcpCallForwardHandler = null;
|
|
47844
48132
|
async messageStatus(messageId) {
|
|
47845
48133
|
if (!this.ws || this.ws.readyState !== this.ws.OPEN)
|
|
47846
48134
|
return null;
|
|
@@ -48139,7 +48427,105 @@ class BrokerClient {
|
|
|
48139
48427
|
this.stateChangeHandlers.add(handler);
|
|
48140
48428
|
return () => this.stateChangeHandlers.delete(handler);
|
|
48141
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
|
+
}
|
|
48142
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
|
+
}
|
|
48143
48529
|
async meshInfo() {
|
|
48144
48530
|
if (!this.ws || this.ws.readyState !== this.ws.OPEN)
|
|
48145
48531
|
return null;
|
|
@@ -48152,8 +48538,132 @@ class BrokerClient {
|
|
|
48152
48538
|
this.ws.send(JSON.stringify({ type: "mesh_info", _reqId: reqId }));
|
|
48153
48539
|
});
|
|
48154
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
|
+
}
|
|
48155
48664
|
close() {
|
|
48156
48665
|
this.closed = true;
|
|
48666
|
+
this.stopStatsReporting();
|
|
48157
48667
|
if (this.helloTimer)
|
|
48158
48668
|
clearTimeout(this.helloTimer);
|
|
48159
48669
|
if (this.reconnectTimer)
|
|
@@ -48165,6 +48675,141 @@ class BrokerClient {
|
|
|
48165
48675
|
}
|
|
48166
48676
|
this.setConnStatus("closed");
|
|
48167
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
|
+
}
|
|
48168
48813
|
resolveFromMap(map2, reqId, value) {
|
|
48169
48814
|
let entry = reqId ? map2.get(reqId) : undefined;
|
|
48170
48815
|
if (!entry) {
|
|
@@ -48202,6 +48847,7 @@ class BrokerClient {
|
|
|
48202
48847
|
return;
|
|
48203
48848
|
}
|
|
48204
48849
|
if (msg.type === "push") {
|
|
48850
|
+
this._statsCounters.messagesIn++;
|
|
48205
48851
|
const nonce = String(msg.nonce ?? "");
|
|
48206
48852
|
const ciphertext = String(msg.ciphertext ?? "");
|
|
48207
48853
|
const senderPubkey = String(msg.senderPubkey ?? "");
|
|
@@ -48402,10 +49048,34 @@ class BrokerClient {
|
|
|
48402
49048
|
}
|
|
48403
49049
|
return;
|
|
48404
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
|
+
}
|
|
48405
49061
|
if (msg.type === "mesh_info_result") {
|
|
48406
49062
|
this.resolveFromMap(this.meshInfoResolvers, msgReqId, msg);
|
|
48407
49063
|
return;
|
|
48408
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
|
+
}
|
|
48409
49079
|
if (msg.type === "scheduled_ack") {
|
|
48410
49080
|
this.resolveFromMap(this.scheduledAckResolvers, msgReqId, {
|
|
48411
49081
|
scheduledId: String(msg.scheduledId ?? ""),
|
|
@@ -48423,6 +49093,75 @@ class BrokerClient {
|
|
|
48423
49093
|
this.resolveFromMap(this.cancelScheduledResolvers, msgReqId, Boolean(msg.ok));
|
|
48424
49094
|
return;
|
|
48425
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");
|
|
49127
|
+
}
|
|
49128
|
+
return;
|
|
49129
|
+
}
|
|
49130
|
+
if (msg.type === "peer_file_request_forward") {
|
|
49131
|
+
this.handlePeerFileRequest(msg);
|
|
49132
|
+
return;
|
|
49133
|
+
}
|
|
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
|
|
49138
|
+
});
|
|
49139
|
+
return;
|
|
49140
|
+
}
|
|
49141
|
+
if (msg.type === "peer_dir_request_forward") {
|
|
49142
|
+
this.handlePeerDirRequest(msg);
|
|
49143
|
+
return;
|
|
49144
|
+
}
|
|
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);
|
|
49163
|
+
return;
|
|
49164
|
+
}
|
|
48426
49165
|
if (msg.type === "error") {
|
|
48427
49166
|
this.debug(`broker error: ${msg.code} ${msg.message}`);
|
|
48428
49167
|
const id = msg.id ? String(msg.id) : null;
|
|
@@ -48465,7 +49204,18 @@ class BrokerClient {
|
|
|
48465
49204
|
[this.taskCreatedResolvers, null],
|
|
48466
49205
|
[this.streamCreatedResolvers, null],
|
|
48467
49206
|
[this.listPeersResolvers, []],
|
|
48468
|
-
[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, []]
|
|
48469
49219
|
];
|
|
48470
49220
|
for (const [map2, defaultVal] of allMaps) {
|
|
48471
49221
|
const first = map2.entries().next().value;
|
|
@@ -48668,6 +49418,8 @@ If the channel meta contains \`subtype: reminder\`, this is a scheduled reminder
|
|
|
48668
49418
|
| check_messages() | Drain buffered inbound messages (auto-pushed in most cases, use as fallback). |
|
|
48669
49419
|
| set_summary(summary) | Set 1-2 sentence description of your current work, visible to all peers. |
|
|
48670
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. |
|
|
48671
49423
|
| join_group(name, role?) | Join a @group with optional role (lead, member, observer, or any string). |
|
|
48672
49424
|
| leave_group(name) | Leave a @group. |
|
|
48673
49425
|
| set_state(key, value) | Write shared state; pushes change to all peers. |
|
|
@@ -48704,6 +49456,12 @@ If the channel meta contains \`subtype: reminder\`, this is a scheduled reminder
|
|
|
48704
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. |
|
|
48705
49457
|
| list_scheduled() | List pending scheduled reminders and messages. |
|
|
48706
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. |
|
|
48707
49465
|
|
|
48708
49466
|
If multiple meshes are joined, prefix \`to\` with \`<mesh-slug>:\` to disambiguate (e.g. \`dev-team:Alice\`).
|
|
48709
49467
|
|
|
@@ -48767,6 +49525,9 @@ Your message mode is "${messageMode}".
|
|
|
48767
49525
|
}));
|
|
48768
49526
|
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
48769
49527
|
const { name, arguments: args } = req.params;
|
|
49528
|
+
for (const c of allClients()) {
|
|
49529
|
+
c.incrementToolCalls();
|
|
49530
|
+
}
|
|
48770
49531
|
if (config2.meshes.length === 0) {
|
|
48771
49532
|
return text("No meshes joined. Run `claudemesh join https://claudemesh.com/join/<token>` first.", true);
|
|
48772
49533
|
}
|
|
@@ -48822,7 +49583,10 @@ No peers connected.`);
|
|
|
48822
49583
|
meta2.push(`model:${p.model}`);
|
|
48823
49584
|
const metaStr = meta2.length ? ` {${meta2.join(", ")}}` : "";
|
|
48824
49585
|
const cwdStr = p.cwd ? ` cwd:${p.cwd}` : "";
|
|
48825
|
-
|
|
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}`;
|
|
48826
49590
|
});
|
|
48827
49591
|
sections.push(`${header}
|
|
48828
49592
|
${peerLines.join(`
|
|
@@ -48889,6 +49653,32 @@ ${drained.join(`
|
|
|
48889
49653
|
await c.setStatus(s);
|
|
48890
49654
|
return text(`Status set to ${s} across ${allClients().length} mesh(es).`);
|
|
48891
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
|
+
}
|
|
48892
49682
|
case "join_group": {
|
|
48893
49683
|
const { name: groupName, role } = args ?? {};
|
|
48894
49684
|
if (!groupName)
|
|
@@ -49422,6 +50212,72 @@ ${rows.join(`
|
|
|
49422
50212
|
return text("No active streams.");
|
|
49423
50213
|
const lines = streams.map((s) => `- **${s.name}** (${s.id.slice(0, 8)}…) by ${s.createdBy}, ${s.subscriberCount} subscriber(s)`);
|
|
49424
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(`
|
|
49425
50281
|
`));
|
|
49426
50282
|
}
|
|
49427
50283
|
case "mesh_info": {
|
|
@@ -49447,6 +50303,89 @@ ${rows.join(`
|
|
|
49447
50303
|
return text(lines.join(`
|
|
49448
50304
|
`));
|
|
49449
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
|
+
}
|
|
49450
50389
|
case "ping_mesh": {
|
|
49451
50390
|
const { priorities: pingPriorities } = args ?? {};
|
|
49452
50391
|
const toTest = pingPriorities ?? ["now", "next"];
|
|
@@ -49483,6 +50422,57 @@ ${rows.join(`
|
|
|
49483
50422
|
return text(results.join(`
|
|
49484
50423
|
`));
|
|
49485
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
|
+
}
|
|
49486
50476
|
case "grant_file_access": {
|
|
49487
50477
|
const { fileId, to: grantTo } = args ?? {};
|
|
49488
50478
|
if (!fileId || !grantTo)
|
|
@@ -49515,6 +50505,117 @@ ${rows.join(`
|
|
|
49515
50505
|
return text("grant_file_access: broker did not confirm", true);
|
|
49516
50506
|
return text(`Access granted: ${targetPeer.displayName} can now download file ${fileId}`);
|
|
49517
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
|
+
}
|
|
49518
50619
|
default:
|
|
49519
50620
|
return text(`Unknown tool: ${name}`, true);
|
|
49520
50621
|
}
|
|
@@ -49530,7 +50631,12 @@ ${rows.join(`
|
|
|
49530
50631
|
const eventName = msg.event;
|
|
49531
50632
|
const data = msg.eventData ?? {};
|
|
49532
50633
|
let content2;
|
|
49533
|
-
if (eventName === "
|
|
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") {
|
|
49534
50640
|
content2 = `[system] Peer "${data.name ?? "unknown"}" joined the mesh`;
|
|
49535
50641
|
} else if (eventName === "peer_left") {
|
|
49536
50642
|
content2 = `[system] Peer "${data.name ?? "unknown"}" left the mesh`;
|
|
@@ -49657,6 +50763,7 @@ ${rows.join(`
|
|
|
49657
50763
|
}
|
|
49658
50764
|
|
|
49659
50765
|
// src/commands/install.ts
|
|
50766
|
+
init_config();
|
|
49660
50767
|
import {
|
|
49661
50768
|
chmodSync as chmodSync2,
|
|
49662
50769
|
copyFileSync,
|
|
@@ -49957,10 +51064,22 @@ function runInstall(args = []) {
|
|
|
49957
51064
|
} else {
|
|
49958
51065
|
console.log(dim("· Hooks skipped (--no-hooks)"));
|
|
49959
51066
|
}
|
|
51067
|
+
let hasMeshes = false;
|
|
51068
|
+
try {
|
|
51069
|
+
const meshConfig = loadConfig();
|
|
51070
|
+
hasMeshes = meshConfig.meshes.length > 0;
|
|
51071
|
+
} catch {}
|
|
49960
51072
|
console.log("");
|
|
49961
51073
|
console.log(yellow(bold2("⚠ RESTART CLAUDE CODE")) + yellow(" for MCP tools to appear."));
|
|
49962
|
-
|
|
49963
|
-
|
|
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
|
+
}
|
|
49964
51083
|
console.log("");
|
|
49965
51084
|
console.log(yellow("⚠ For real-time push messages from peers, launch with:"));
|
|
49966
51085
|
console.log(` ${bold2("claudemesh launch")}` + dim(" (or: claude --dangerously-load-development-channels server:claudemesh)"));
|
|
@@ -50620,7 +51739,7 @@ init_config();
|
|
|
50620
51739
|
// package.json
|
|
50621
51740
|
var package_default = {
|
|
50622
51741
|
name: "claudemesh-cli",
|
|
50623
|
-
version: "0.
|
|
51742
|
+
version: "0.7.0",
|
|
50624
51743
|
description: "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.",
|
|
50625
51744
|
keywords: [
|
|
50626
51745
|
"claude-code",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claudemesh-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
|
@@ -48,10 +48,10 @@
|
|
|
48
48
|
"prettier": "3.6.2",
|
|
49
49
|
"typescript": "5.9.3",
|
|
50
50
|
"vitest": "4.0.14",
|
|
51
|
-
"@turbostarter/eslint-config": "0.1.0",
|
|
52
|
-
"@turbostarter/prettier-config": "0.1.0",
|
|
53
51
|
"@turbostarter/tsconfig": "0.1.0",
|
|
54
|
-
"@turbostarter/
|
|
52
|
+
"@turbostarter/prettier-config": "0.1.0",
|
|
53
|
+
"@turbostarter/vitest-config": "0.1.0",
|
|
54
|
+
"@turbostarter/eslint-config": "0.1.0"
|
|
55
55
|
},
|
|
56
56
|
"scripts": {
|
|
57
57
|
"build": "bun build src/index.ts --target=node --outfile dist/index.js --banner \"#!/usr/bin/env node\" && chmod +x dist/index.js",
|