claudemesh-cli 0.8.9 → 0.9.1
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 +680 -134
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6514,7 +6514,7 @@ function loadConfig() {
|
|
|
6514
6514
|
if (!parsed || !Array.isArray(parsed.meshes)) {
|
|
6515
6515
|
return { version: 1, meshes: [] };
|
|
6516
6516
|
}
|
|
6517
|
-
return { version: 1, meshes: parsed.meshes, displayName: parsed.displayName, role: parsed.role, groups: parsed.groups, messageMode: parsed.messageMode };
|
|
6517
|
+
return { version: 1, meshes: parsed.meshes, displayName: parsed.displayName, role: parsed.role, groups: parsed.groups, messageMode: parsed.messageMode, accountId: parsed.accountId };
|
|
6518
6518
|
} catch (e) {
|
|
6519
6519
|
throw new Error(`Failed to load ${CONFIG_PATH}: ${e instanceof Error ? e.message : String(e)}`);
|
|
6520
6520
|
}
|
|
@@ -40005,6 +40005,48 @@ var init_file_crypto = __esm(() => {
|
|
|
40005
40005
|
init_keypair();
|
|
40006
40006
|
});
|
|
40007
40007
|
|
|
40008
|
+
// src/auth/sync-with-broker.ts
|
|
40009
|
+
var exports_sync_with_broker = {};
|
|
40010
|
+
__export(exports_sync_with_broker, {
|
|
40011
|
+
syncWithBroker: () => syncWithBroker
|
|
40012
|
+
});
|
|
40013
|
+
async function syncWithBroker(syncToken, peerPubkey, displayName, brokerBaseUrl) {
|
|
40014
|
+
const base = brokerBaseUrl ?? deriveHttpUrl(process.env.CLAUDEMESH_BROKER_URL ?? "wss://ic.claudemesh.com/ws");
|
|
40015
|
+
const res = await fetch(`${base}/cli-sync`, {
|
|
40016
|
+
method: "POST",
|
|
40017
|
+
headers: { "Content-Type": "application/json" },
|
|
40018
|
+
body: JSON.stringify({
|
|
40019
|
+
sync_token: syncToken,
|
|
40020
|
+
peer_pubkey: peerPubkey,
|
|
40021
|
+
display_name: displayName
|
|
40022
|
+
})
|
|
40023
|
+
});
|
|
40024
|
+
if (!res.ok) {
|
|
40025
|
+
const body2 = await res.text();
|
|
40026
|
+
let msg;
|
|
40027
|
+
try {
|
|
40028
|
+
msg = JSON.parse(body2).error ?? body2;
|
|
40029
|
+
} catch {
|
|
40030
|
+
msg = body2;
|
|
40031
|
+
}
|
|
40032
|
+
throw new Error(`Broker sync failed (${res.status}): ${msg}`);
|
|
40033
|
+
}
|
|
40034
|
+
const body = await res.json();
|
|
40035
|
+
if (!body.ok) {
|
|
40036
|
+
throw new Error(`Broker sync failed: ${body.error ?? "unknown error"}`);
|
|
40037
|
+
}
|
|
40038
|
+
return {
|
|
40039
|
+
account_id: body.account_id,
|
|
40040
|
+
meshes: body.meshes
|
|
40041
|
+
};
|
|
40042
|
+
}
|
|
40043
|
+
function deriveHttpUrl(wssUrl) {
|
|
40044
|
+
const url = new URL(wssUrl);
|
|
40045
|
+
url.protocol = url.protocol === "wss:" ? "https:" : "http:";
|
|
40046
|
+
url.pathname = url.pathname.replace(/\/ws\/?$/, "");
|
|
40047
|
+
return url.toString().replace(/\/$/, "");
|
|
40048
|
+
}
|
|
40049
|
+
|
|
40008
40050
|
// ../../node_modules/citty/dist/_chunks/libs/scule.mjs
|
|
40009
40051
|
var NUMBER_CHAR_RE = /\d/;
|
|
40010
40052
|
var STR_SPLITTERS = [
|
|
@@ -47547,18 +47589,29 @@ var TOOLS = [
|
|
|
47547
47589
|
},
|
|
47548
47590
|
{
|
|
47549
47591
|
name: "share_skill",
|
|
47550
|
-
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.",
|
|
47592
|
+
description: "Publish a reusable skill to the mesh. Other peers can discover and load it as a slash command. If a skill with the same name exists, it is updated. Skills are automatically exposed as MCP prompts and skill:// resources for native Claude Code integration.",
|
|
47551
47593
|
inputSchema: {
|
|
47552
47594
|
type: "object",
|
|
47553
47595
|
properties: {
|
|
47554
|
-
name: { type: "string", description: "Unique skill name (e.g. 'code-review', 'deploy-checklist')" },
|
|
47596
|
+
name: { type: "string", description: "Unique skill name (e.g. 'code-review', 'deploy-checklist'). Becomes the slash command name." },
|
|
47555
47597
|
description: { type: "string", description: "Short description of what the skill does" },
|
|
47556
|
-
instructions: { type: "string", description: "Full instructions/prompt
|
|
47598
|
+
instructions: { type: "string", description: "Full instructions/prompt markdown. Can include frontmatter (---) block." },
|
|
47557
47599
|
tags: {
|
|
47558
47600
|
type: "array",
|
|
47559
47601
|
items: { type: "string" },
|
|
47560
47602
|
description: "Tags for discoverability"
|
|
47561
|
-
}
|
|
47603
|
+
},
|
|
47604
|
+
when_to_use: { type: "string", description: "Detailed description of when Claude should auto-invoke this skill" },
|
|
47605
|
+
allowed_tools: {
|
|
47606
|
+
type: "array",
|
|
47607
|
+
items: { type: "string" },
|
|
47608
|
+
description: "Tool names this skill is allowed to use (e.g. ['Bash', 'Read', 'Edit'])"
|
|
47609
|
+
},
|
|
47610
|
+
model: { type: "string", description: "Model override (e.g. 'sonnet', 'opus', 'haiku')" },
|
|
47611
|
+
context: { type: "string", enum: ["inline", "fork"], description: "Execution context: 'inline' (default) or 'fork' (sub-agent)" },
|
|
47612
|
+
agent: { type: "string", description: "Agent type when forked (e.g. 'general-purpose')" },
|
|
47613
|
+
user_invocable: { type: "boolean", description: "Whether users can invoke via /skill-name (default: true)" },
|
|
47614
|
+
argument_hint: { type: "string", description: "Hint text for arguments (e.g. '<file-path>')" }
|
|
47562
47615
|
},
|
|
47563
47616
|
required: ["name", "description", "instructions"]
|
|
47564
47617
|
}
|
|
@@ -48702,7 +48755,7 @@ class BrokerClient {
|
|
|
48702
48755
|
skillAckResolvers = new Map;
|
|
48703
48756
|
skillDataResolvers = new Map;
|
|
48704
48757
|
skillListResolvers = new Map;
|
|
48705
|
-
async shareSkill(name, description, instructions, tags) {
|
|
48758
|
+
async shareSkill(name, description, instructions, tags, manifest) {
|
|
48706
48759
|
if (!this.ws || this.ws.readyState !== this.ws.OPEN)
|
|
48707
48760
|
return null;
|
|
48708
48761
|
return new Promise((resolve) => {
|
|
@@ -48713,7 +48766,7 @@ class BrokerClient {
|
|
|
48713
48766
|
if (this.skillAckResolvers.delete(reqId))
|
|
48714
48767
|
resolve(null);
|
|
48715
48768
|
}, 5000) });
|
|
48716
|
-
this.ws.send(JSON.stringify({ type: "share_skill", name, description, instructions, tags, _reqId: reqId }));
|
|
48769
|
+
this.ws.send(JSON.stringify({ type: "share_skill", name, description, instructions, tags, manifest, _reqId: reqId }));
|
|
48717
48770
|
});
|
|
48718
48771
|
}
|
|
48719
48772
|
async getSkill(name) {
|
|
@@ -49890,7 +49943,9 @@ async function startMcpServer() {
|
|
|
49890
49943
|
const server = new Server({ name: "claudemesh", version: "0.3.0" }, {
|
|
49891
49944
|
capabilities: {
|
|
49892
49945
|
experimental: { "claude/channel": {} },
|
|
49893
|
-
tools: {}
|
|
49946
|
+
tools: {},
|
|
49947
|
+
prompts: {},
|
|
49948
|
+
resources: {}
|
|
49894
49949
|
},
|
|
49895
49950
|
instructions: `## Identity
|
|
49896
49951
|
You are "${myName}"${myRole ? ` (${myRole})` : ""} — a peer in the claudemesh network. Your groups: ${myGroups}. You are one of several Claude Code sessions connected to the same mesh. No orchestrator exists — peers are equals. Your identity comes from your name and group roles, not from a central authority.
|
|
@@ -50018,6 +50073,129 @@ Your message mode is "${messageMode}".
|
|
|
50018
50073
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
50019
50074
|
tools: TOOLS
|
|
50020
50075
|
}));
|
|
50076
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
50077
|
+
const client2 = allClients()[0];
|
|
50078
|
+
if (!client2)
|
|
50079
|
+
return { prompts: [] };
|
|
50080
|
+
const skills = await client2.listSkills();
|
|
50081
|
+
return {
|
|
50082
|
+
prompts: skills.map((s) => ({
|
|
50083
|
+
name: s.name,
|
|
50084
|
+
description: s.description,
|
|
50085
|
+
arguments: []
|
|
50086
|
+
}))
|
|
50087
|
+
};
|
|
50088
|
+
});
|
|
50089
|
+
server.setRequestHandler(GetPromptRequestSchema, async (req) => {
|
|
50090
|
+
const { name, arguments: promptArgs } = req.params;
|
|
50091
|
+
const client2 = allClients()[0];
|
|
50092
|
+
if (!client2)
|
|
50093
|
+
throw new Error("Not connected to any mesh");
|
|
50094
|
+
const skill = await client2.getSkill(name);
|
|
50095
|
+
if (!skill)
|
|
50096
|
+
throw new Error(`Skill "${name}" not found in the mesh`);
|
|
50097
|
+
let content = skill.instructions;
|
|
50098
|
+
const manifest = skill.manifest;
|
|
50099
|
+
if (manifest && typeof manifest === "object") {
|
|
50100
|
+
const fm = ["---"];
|
|
50101
|
+
if (manifest.description)
|
|
50102
|
+
fm.push(`description: "${manifest.description}"`);
|
|
50103
|
+
if (manifest.when_to_use)
|
|
50104
|
+
fm.push(`when_to_use: "${manifest.when_to_use}"`);
|
|
50105
|
+
if (manifest.allowed_tools?.length)
|
|
50106
|
+
fm.push(`allowed-tools:
|
|
50107
|
+
${manifest.allowed_tools.map((t) => ` - ${t}`).join(`
|
|
50108
|
+
`)}`);
|
|
50109
|
+
if (manifest.model)
|
|
50110
|
+
fm.push(`model: ${manifest.model}`);
|
|
50111
|
+
if (manifest.context)
|
|
50112
|
+
fm.push(`context: ${manifest.context}`);
|
|
50113
|
+
if (manifest.agent)
|
|
50114
|
+
fm.push(`agent: ${manifest.agent}`);
|
|
50115
|
+
if (manifest.user_invocable === false)
|
|
50116
|
+
fm.push(`user-invocable: false`);
|
|
50117
|
+
if (manifest.argument_hint)
|
|
50118
|
+
fm.push(`argument-hint: "${manifest.argument_hint}"`);
|
|
50119
|
+
fm.push(`---
|
|
50120
|
+
`);
|
|
50121
|
+
if (fm.length > 3)
|
|
50122
|
+
content = fm.join(`
|
|
50123
|
+
`) + content;
|
|
50124
|
+
}
|
|
50125
|
+
return {
|
|
50126
|
+
description: skill.description,
|
|
50127
|
+
messages: [
|
|
50128
|
+
{
|
|
50129
|
+
role: "user",
|
|
50130
|
+
content: { type: "text", text: content }
|
|
50131
|
+
}
|
|
50132
|
+
]
|
|
50133
|
+
};
|
|
50134
|
+
});
|
|
50135
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
50136
|
+
const client2 = allClients()[0];
|
|
50137
|
+
if (!client2)
|
|
50138
|
+
return { resources: [] };
|
|
50139
|
+
const skills = await client2.listSkills();
|
|
50140
|
+
return {
|
|
50141
|
+
resources: skills.map((s) => ({
|
|
50142
|
+
uri: `skill://claudemesh/${encodeURIComponent(s.name)}`,
|
|
50143
|
+
name: s.name,
|
|
50144
|
+
description: s.description,
|
|
50145
|
+
mimeType: "text/markdown"
|
|
50146
|
+
}))
|
|
50147
|
+
};
|
|
50148
|
+
});
|
|
50149
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (req) => {
|
|
50150
|
+
const { uri } = req.params;
|
|
50151
|
+
const match = uri.match(/^skill:\/\/claudemesh\/(.+)$/);
|
|
50152
|
+
if (!match)
|
|
50153
|
+
throw new Error(`Unknown resource URI: ${uri}`);
|
|
50154
|
+
const name = decodeURIComponent(match[1]);
|
|
50155
|
+
const client2 = allClients()[0];
|
|
50156
|
+
if (!client2)
|
|
50157
|
+
throw new Error("Not connected to any mesh");
|
|
50158
|
+
const skill = await client2.getSkill(name);
|
|
50159
|
+
if (!skill)
|
|
50160
|
+
throw new Error(`Skill "${name}" not found`);
|
|
50161
|
+
const manifest = skill.manifest;
|
|
50162
|
+
const fmLines = ["---"];
|
|
50163
|
+
fmLines.push(`name: ${skill.name}`);
|
|
50164
|
+
fmLines.push(`description: "${skill.description}"`);
|
|
50165
|
+
if (skill.tags.length)
|
|
50166
|
+
fmLines.push(`tags: [${skill.tags.join(", ")}]`);
|
|
50167
|
+
if (manifest && typeof manifest === "object") {
|
|
50168
|
+
if (manifest.when_to_use)
|
|
50169
|
+
fmLines.push(`when_to_use: "${manifest.when_to_use}"`);
|
|
50170
|
+
if (manifest.allowed_tools?.length)
|
|
50171
|
+
fmLines.push(`allowed-tools:
|
|
50172
|
+
${manifest.allowed_tools.map((t) => ` - ${t}`).join(`
|
|
50173
|
+
`)}`);
|
|
50174
|
+
if (manifest.model)
|
|
50175
|
+
fmLines.push(`model: ${manifest.model}`);
|
|
50176
|
+
if (manifest.context)
|
|
50177
|
+
fmLines.push(`context: ${manifest.context}`);
|
|
50178
|
+
if (manifest.agent)
|
|
50179
|
+
fmLines.push(`agent: ${manifest.agent}`);
|
|
50180
|
+
if (manifest.user_invocable === false)
|
|
50181
|
+
fmLines.push(`user-invocable: false`);
|
|
50182
|
+
if (manifest.argument_hint)
|
|
50183
|
+
fmLines.push(`argument-hint: "${manifest.argument_hint}"`);
|
|
50184
|
+
}
|
|
50185
|
+
fmLines.push(`---
|
|
50186
|
+
`);
|
|
50187
|
+
const fullContent = fmLines.join(`
|
|
50188
|
+
`) + skill.instructions;
|
|
50189
|
+
return {
|
|
50190
|
+
contents: [
|
|
50191
|
+
{
|
|
50192
|
+
uri,
|
|
50193
|
+
mimeType: "text/markdown",
|
|
50194
|
+
text: fullContent
|
|
50195
|
+
}
|
|
50196
|
+
]
|
|
50197
|
+
};
|
|
50198
|
+
});
|
|
50021
50199
|
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
50022
50200
|
const { name, arguments: args } = req.params;
|
|
50023
50201
|
for (const c of allClients()) {
|
|
@@ -50826,16 +51004,45 @@ ${rows.join(`
|
|
|
50826
51004
|
`));
|
|
50827
51005
|
}
|
|
50828
51006
|
case "share_skill": {
|
|
50829
|
-
const {
|
|
51007
|
+
const {
|
|
51008
|
+
name: skillName,
|
|
51009
|
+
description: skillDesc,
|
|
51010
|
+
instructions: skillInstr,
|
|
51011
|
+
tags: skillTags,
|
|
51012
|
+
when_to_use,
|
|
51013
|
+
allowed_tools,
|
|
51014
|
+
model,
|
|
51015
|
+
context: skillContext,
|
|
51016
|
+
agent,
|
|
51017
|
+
user_invocable,
|
|
51018
|
+
argument_hint
|
|
51019
|
+
} = args ?? {};
|
|
50830
51020
|
if (!skillName || !skillDesc || !skillInstr)
|
|
50831
51021
|
return text("share_skill: `name`, `description`, and `instructions` required", true);
|
|
50832
51022
|
const client2 = allClients()[0];
|
|
50833
51023
|
if (!client2)
|
|
50834
51024
|
return text("share_skill: not connected", true);
|
|
50835
|
-
const
|
|
51025
|
+
const manifest = {};
|
|
51026
|
+
if (when_to_use)
|
|
51027
|
+
manifest.when_to_use = when_to_use;
|
|
51028
|
+
if (allowed_tools?.length)
|
|
51029
|
+
manifest.allowed_tools = allowed_tools;
|
|
51030
|
+
if (model)
|
|
51031
|
+
manifest.model = model;
|
|
51032
|
+
if (skillContext)
|
|
51033
|
+
manifest.context = skillContext;
|
|
51034
|
+
if (agent)
|
|
51035
|
+
manifest.agent = agent;
|
|
51036
|
+
if (user_invocable === false)
|
|
51037
|
+
manifest.user_invocable = false;
|
|
51038
|
+
if (argument_hint)
|
|
51039
|
+
manifest.argument_hint = argument_hint;
|
|
51040
|
+
const result = await client2.shareSkill(skillName, skillDesc, skillInstr, skillTags, Object.keys(manifest).length > 0 ? manifest : undefined);
|
|
50836
51041
|
if (!result)
|
|
50837
51042
|
return text("share_skill: broker did not acknowledge", true);
|
|
50838
|
-
|
|
51043
|
+
server.notification({ method: "notifications/prompts/list_changed" });
|
|
51044
|
+
server.notification({ method: "notifications/resources/list_changed" });
|
|
51045
|
+
return text(`Skill "${skillName}" published to the mesh. It will appear as /claudemesh:${skillName} in Claude Code.`);
|
|
50839
51046
|
}
|
|
50840
51047
|
case "get_skill": {
|
|
50841
51048
|
const { name: gsName } = args ?? {};
|
|
@@ -50847,13 +51054,30 @@ ${rows.join(`
|
|
|
50847
51054
|
const skill = await client2.getSkill(gsName);
|
|
50848
51055
|
if (!skill)
|
|
50849
51056
|
return text(`Skill "${gsName}" not found in the mesh.`);
|
|
51057
|
+
const manifest = skill.manifest;
|
|
51058
|
+
const metaLines = [];
|
|
51059
|
+
if (manifest) {
|
|
51060
|
+
if (manifest.when_to_use)
|
|
51061
|
+
metaLines.push(`**When to use:** ${manifest.when_to_use}`);
|
|
51062
|
+
if (manifest.allowed_tools)
|
|
51063
|
+
metaLines.push(`**Allowed tools:** ${manifest.allowed_tools.join(", ")}`);
|
|
51064
|
+
if (manifest.model)
|
|
51065
|
+
metaLines.push(`**Model:** ${manifest.model}`);
|
|
51066
|
+
if (manifest.context)
|
|
51067
|
+
metaLines.push(`**Context:** ${manifest.context}`);
|
|
51068
|
+
if (manifest.agent)
|
|
51069
|
+
metaLines.push(`**Agent:** ${manifest.agent}`);
|
|
51070
|
+
}
|
|
50850
51071
|
return text(`# Skill: ${skill.name}
|
|
50851
51072
|
|
|
50852
51073
|
**Description:** ${skill.description}
|
|
50853
51074
|
**Author:** ${skill.author}
|
|
50854
51075
|
**Tags:** ${skill.tags.length ? skill.tags.join(", ") : "none"}
|
|
50855
51076
|
**Created:** ${skill.createdAt}
|
|
50856
|
-
|
|
51077
|
+
**Slash command:** /claudemesh:${skill.name}
|
|
51078
|
+
` + (metaLines.length ? metaLines.join(`
|
|
51079
|
+
`) + `
|
|
51080
|
+
` : "") + `
|
|
50857
51081
|
---
|
|
50858
51082
|
|
|
50859
51083
|
## Instructions
|
|
@@ -50881,6 +51105,10 @@ ${lines.join(`
|
|
|
50881
51105
|
if (!client2)
|
|
50882
51106
|
return text("remove_skill: not connected", true);
|
|
50883
51107
|
const removed = await client2.removeSkill(rsName);
|
|
51108
|
+
if (removed) {
|
|
51109
|
+
server.notification({ method: "notifications/prompts/list_changed" });
|
|
51110
|
+
server.notification({ method: "notifications/resources/list_changed" });
|
|
51111
|
+
}
|
|
50884
51112
|
return text(removed ? `Skill "${rsName}" removed.` : `Skill "${rsName}" not found.`, !removed);
|
|
50885
51113
|
}
|
|
50886
51114
|
case "ping_mesh": {
|
|
@@ -51394,159 +51622,163 @@ ${lines.join(`
|
|
|
51394
51622
|
return text(`Unknown tool: ${name}`, true);
|
|
51395
51623
|
}
|
|
51396
51624
|
});
|
|
51397
|
-
await startClients(config2);
|
|
51398
51625
|
const transport = new StdioServerTransport;
|
|
51399
51626
|
await server.connect(transport);
|
|
51400
|
-
|
|
51401
|
-
|
|
51402
|
-
|
|
51403
|
-
|
|
51404
|
-
|
|
51405
|
-
|
|
51406
|
-
|
|
51407
|
-
|
|
51408
|
-
if (
|
|
51409
|
-
|
|
51410
|
-
|
|
51411
|
-
const
|
|
51412
|
-
|
|
51413
|
-
|
|
51414
|
-
|
|
51415
|
-
|
|
51416
|
-
|
|
51417
|
-
|
|
51418
|
-
|
|
51419
|
-
|
|
51420
|
-
|
|
51421
|
-
|
|
51422
|
-
|
|
51423
|
-
|
|
51424
|
-
|
|
51425
|
-
|
|
51426
|
-
|
|
51427
|
-
|
|
51428
|
-
|
|
51429
|
-
|
|
51430
|
-
|
|
51431
|
-
|
|
51432
|
-
|
|
51433
|
-
|
|
51434
|
-
|
|
51435
|
-
|
|
51436
|
-
|
|
51437
|
-
|
|
51438
|
-
|
|
51439
|
-
|
|
51627
|
+
startClients(config2).then(() => {
|
|
51628
|
+
wirePushHandlers();
|
|
51629
|
+
}).catch(() => {
|
|
51630
|
+
wirePushHandlers();
|
|
51631
|
+
});
|
|
51632
|
+
async function wirePushHandlers() {
|
|
51633
|
+
for (const client2 of allClients()) {
|
|
51634
|
+
client2.onPush(async (msg) => {
|
|
51635
|
+
if (messageMode === "off")
|
|
51636
|
+
return;
|
|
51637
|
+
if (msg.subtype === "system" && msg.event) {
|
|
51638
|
+
const eventName = msg.event;
|
|
51639
|
+
const data = msg.eventData ?? {};
|
|
51640
|
+
let content2;
|
|
51641
|
+
if (eventName === "tick") {
|
|
51642
|
+
const tick = data.tick ?? 0;
|
|
51643
|
+
const simTime = String(data.simTime ?? "").replace("T", " ").replace(/\..*/, "");
|
|
51644
|
+
const speed = data.speed ?? 1;
|
|
51645
|
+
content2 = `[heartbeat] tick ${tick} | sim time: ${simTime} | speed: x${speed}`;
|
|
51646
|
+
} else if (eventName === "peer_joined") {
|
|
51647
|
+
content2 = `[system] Peer "${data.name ?? "unknown"}" joined the mesh`;
|
|
51648
|
+
} else if (eventName === "peer_returned") {
|
|
51649
|
+
const peerName = String(data.name ?? "unknown");
|
|
51650
|
+
const lastSeenAt = data.lastSeenAt ? relativeTime(String(data.lastSeenAt)) : "unknown";
|
|
51651
|
+
const groups = Array.isArray(data.groups) ? data.groups.map((g) => g.role ? `@${g.name}:${g.role}` : `@${g.name}`).join(", ") : "";
|
|
51652
|
+
const summary = data.summary ? ` Summary: "${data.summary}"` : "";
|
|
51653
|
+
content2 = `[system] Welcome back, "${peerName}"! Last seen ${lastSeenAt}.${groups ? ` Restored: ${groups}` : ""}${summary}`;
|
|
51654
|
+
} else if (eventName === "peer_left") {
|
|
51655
|
+
content2 = `[system] Peer "${data.name ?? "unknown"}" left the mesh`;
|
|
51656
|
+
} else if (eventName === "mcp_registered") {
|
|
51657
|
+
const tools = Array.isArray(data.tools) ? data.tools.join(", ") : "";
|
|
51658
|
+
content2 = `[system] New MCP server available: "${data.serverName}" (hosted by ${data.hostedBy}). Tools: ${tools}. Use mesh_tool_call to invoke.`;
|
|
51659
|
+
} else if (eventName === "mcp_unregistered") {
|
|
51660
|
+
content2 = `[system] MCP server "${data.serverName}" removed (was hosted by ${data.hostedBy})`;
|
|
51661
|
+
} else if (eventName === "mcp_restored") {
|
|
51662
|
+
content2 = `[system] MCP server "${data.serverName}" is back online (hosted by ${data.hostedBy})`;
|
|
51663
|
+
} else if (eventName === "watch_triggered") {
|
|
51664
|
+
content2 = `[WATCH] ${data.label ?? data.url}: ${data.oldValue} → ${data.newValue}`;
|
|
51665
|
+
} else if (eventName === "mcp_deployed") {
|
|
51666
|
+
content2 = `[SERVICE] "${data.name}" deployed (${data.tool_count} tools) by ${data.deployed_by}`;
|
|
51667
|
+
} else if (eventName === "mcp_undeployed") {
|
|
51668
|
+
content2 = `[SERVICE] "${data.name}" undeployed by ${data.by}`;
|
|
51669
|
+
} else if (eventName === "mcp_scope_changed") {
|
|
51670
|
+
content2 = `[SERVICE] "${data.name}" scope changed to ${JSON.stringify(data.scope)} by ${data.by}`;
|
|
51671
|
+
} else {
|
|
51672
|
+
content2 = `[system] ${eventName}: ${JSON.stringify(data)}`;
|
|
51673
|
+
}
|
|
51674
|
+
try {
|
|
51675
|
+
await server.notification({
|
|
51676
|
+
method: "notifications/claude/channel",
|
|
51677
|
+
params: {
|
|
51678
|
+
content: content2,
|
|
51679
|
+
meta: {
|
|
51680
|
+
kind: "system",
|
|
51681
|
+
event: eventName,
|
|
51682
|
+
mesh_slug: client2.meshSlug,
|
|
51683
|
+
mesh_id: client2.meshId,
|
|
51684
|
+
...Object.keys(data).length > 0 ? { eventData: data } : {}
|
|
51685
|
+
}
|
|
51686
|
+
}
|
|
51687
|
+
});
|
|
51688
|
+
process.stderr.write(`[claudemesh] system: ${content2}
|
|
51689
|
+
`);
|
|
51690
|
+
} catch (pushErr) {
|
|
51691
|
+
process.stderr.write(`[claudemesh] system push FAILED: ${pushErr}
|
|
51692
|
+
`);
|
|
51693
|
+
}
|
|
51694
|
+
return;
|
|
51695
|
+
}
|
|
51696
|
+
const fromPubkey = msg.senderPubkey || "";
|
|
51697
|
+
const fromName = fromPubkey ? await resolvePeerName(client2, fromPubkey) : "unknown";
|
|
51698
|
+
if (messageMode === "inbox") {
|
|
51699
|
+
try {
|
|
51700
|
+
await server.notification({
|
|
51701
|
+
method: "notifications/claude/channel",
|
|
51702
|
+
params: {
|
|
51703
|
+
content: `[inbox] New message from ${fromName}. Use check_messages to read.`,
|
|
51704
|
+
meta: { kind: "inbox_notification", from_name: fromName }
|
|
51705
|
+
}
|
|
51706
|
+
});
|
|
51707
|
+
} catch {}
|
|
51708
|
+
return;
|
|
51440
51709
|
}
|
|
51710
|
+
const content = msg.plaintext ?? decryptFailedWarning(fromPubkey);
|
|
51441
51711
|
try {
|
|
51442
51712
|
await server.notification({
|
|
51443
51713
|
method: "notifications/claude/channel",
|
|
51444
51714
|
params: {
|
|
51445
|
-
content
|
|
51715
|
+
content,
|
|
51446
51716
|
meta: {
|
|
51447
|
-
|
|
51448
|
-
|
|
51717
|
+
from_id: fromPubkey,
|
|
51718
|
+
from_name: fromName,
|
|
51449
51719
|
mesh_slug: client2.meshSlug,
|
|
51450
51720
|
mesh_id: client2.meshId,
|
|
51451
|
-
|
|
51721
|
+
priority: msg.priority,
|
|
51722
|
+
sent_at: msg.createdAt,
|
|
51723
|
+
delivered_at: msg.receivedAt,
|
|
51724
|
+
kind: msg.kind,
|
|
51725
|
+
...msg.subtype ? { subtype: msg.subtype } : {}
|
|
51452
51726
|
}
|
|
51453
51727
|
}
|
|
51454
51728
|
});
|
|
51455
|
-
process.stderr.write(`[claudemesh]
|
|
51729
|
+
process.stderr.write(`[claudemesh] pushed: from=${fromName} content=${content.slice(0, 60)}
|
|
51456
51730
|
`);
|
|
51457
51731
|
} catch (pushErr) {
|
|
51458
|
-
process.stderr.write(`[claudemesh]
|
|
51732
|
+
process.stderr.write(`[claudemesh] push FAILED: ${pushErr}
|
|
51459
51733
|
`);
|
|
51460
51734
|
}
|
|
51461
|
-
|
|
51462
|
-
|
|
51463
|
-
const fromPubkey = msg.senderPubkey || "";
|
|
51464
|
-
const fromName = fromPubkey ? await resolvePeerName(client2, fromPubkey) : "unknown";
|
|
51465
|
-
if (messageMode === "inbox") {
|
|
51735
|
+
});
|
|
51736
|
+
client2.onStreamData(async (evt) => {
|
|
51466
51737
|
try {
|
|
51467
51738
|
await server.notification({
|
|
51468
51739
|
method: "notifications/claude/channel",
|
|
51469
51740
|
params: {
|
|
51470
|
-
content: `[
|
|
51471
|
-
meta: {
|
|
51741
|
+
content: `[stream:${evt.stream}] from ${evt.publishedBy}: ${JSON.stringify(evt.data)}`,
|
|
51742
|
+
meta: {
|
|
51743
|
+
kind: "stream_data",
|
|
51744
|
+
stream: evt.stream,
|
|
51745
|
+
published_by: evt.publishedBy
|
|
51746
|
+
}
|
|
51472
51747
|
}
|
|
51473
51748
|
});
|
|
51474
51749
|
} catch {}
|
|
51475
|
-
|
|
51476
|
-
|
|
51477
|
-
|
|
51478
|
-
|
|
51479
|
-
|
|
51480
|
-
|
|
51481
|
-
|
|
51482
|
-
|
|
51483
|
-
|
|
51484
|
-
|
|
51485
|
-
|
|
51486
|
-
|
|
51487
|
-
mesh_id: client2.meshId,
|
|
51488
|
-
priority: msg.priority,
|
|
51489
|
-
sent_at: msg.createdAt,
|
|
51490
|
-
delivered_at: msg.receivedAt,
|
|
51491
|
-
kind: msg.kind,
|
|
51492
|
-
...msg.subtype ? { subtype: msg.subtype } : {}
|
|
51493
|
-
}
|
|
51494
|
-
}
|
|
51495
|
-
});
|
|
51496
|
-
process.stderr.write(`[claudemesh] pushed: from=${fromName} content=${content.slice(0, 60)}
|
|
51497
|
-
`);
|
|
51498
|
-
} catch (pushErr) {
|
|
51499
|
-
process.stderr.write(`[claudemesh] push FAILED: ${pushErr}
|
|
51500
|
-
`);
|
|
51501
|
-
}
|
|
51502
|
-
});
|
|
51503
|
-
client2.onStreamData(async (evt) => {
|
|
51504
|
-
try {
|
|
51505
|
-
await server.notification({
|
|
51506
|
-
method: "notifications/claude/channel",
|
|
51507
|
-
params: {
|
|
51508
|
-
content: `[stream:${evt.stream}] from ${evt.publishedBy}: ${JSON.stringify(evt.data)}`,
|
|
51509
|
-
meta: {
|
|
51510
|
-
kind: "stream_data",
|
|
51511
|
-
stream: evt.stream,
|
|
51512
|
-
published_by: evt.publishedBy
|
|
51750
|
+
});
|
|
51751
|
+
client2.onStateChange(async (change) => {
|
|
51752
|
+
try {
|
|
51753
|
+
await server.notification({
|
|
51754
|
+
method: "notifications/claude/channel",
|
|
51755
|
+
params: {
|
|
51756
|
+
content: `[state] ${change.key} = ${JSON.stringify(change.value)} (set by ${change.updatedBy})`,
|
|
51757
|
+
meta: {
|
|
51758
|
+
kind: "state_change",
|
|
51759
|
+
key: change.key,
|
|
51760
|
+
updated_by: change.updatedBy
|
|
51761
|
+
}
|
|
51513
51762
|
}
|
|
51514
|
-
}
|
|
51515
|
-
}
|
|
51516
|
-
}
|
|
51517
|
-
}
|
|
51518
|
-
|
|
51763
|
+
});
|
|
51764
|
+
} catch {}
|
|
51765
|
+
});
|
|
51766
|
+
}
|
|
51767
|
+
const welcomeClient = allClients()[0];
|
|
51768
|
+
if (welcomeClient && welcomeClient.status === "open") {
|
|
51519
51769
|
try {
|
|
51770
|
+
const peers = await welcomeClient.listPeers();
|
|
51771
|
+
const peerNames = peers.filter((p) => p.displayName !== myName).map((p) => p.displayName).join(", ") || "none";
|
|
51520
51772
|
await server.notification({
|
|
51521
51773
|
method: "notifications/claude/channel",
|
|
51522
51774
|
params: {
|
|
51523
|
-
content: `[
|
|
51524
|
-
meta: {
|
|
51525
|
-
kind: "state_change",
|
|
51526
|
-
key: change.key,
|
|
51527
|
-
updated_by: change.updatedBy
|
|
51528
|
-
}
|
|
51775
|
+
content: `[system] Connected as ${myName} to mesh ${welcomeClient.meshSlug}. ${peers.length} peer(s) online: ${peerNames}. Call mesh_info for full details or set_summary to announce yourself.`,
|
|
51776
|
+
meta: { kind: "welcome", mesh_slug: welcomeClient.meshSlug }
|
|
51529
51777
|
}
|
|
51530
51778
|
});
|
|
51531
51779
|
} catch {}
|
|
51532
|
-
}
|
|
51780
|
+
}
|
|
51533
51781
|
}
|
|
51534
|
-
setTimeout(async () => {
|
|
51535
|
-
const client2 = allClients()[0];
|
|
51536
|
-
if (!client2 || client2.status !== "open")
|
|
51537
|
-
return;
|
|
51538
|
-
try {
|
|
51539
|
-
const peers = await client2.listPeers();
|
|
51540
|
-
const peerNames = peers.filter((p) => p.displayName !== myName).map((p) => p.displayName).join(", ") || "none";
|
|
51541
|
-
await server.notification({
|
|
51542
|
-
method: "notifications/claude/channel",
|
|
51543
|
-
params: {
|
|
51544
|
-
content: `[system] Connected as ${myName} to mesh ${client2.meshSlug}. ${peers.length} peer(s) online: ${peerNames}. Call mesh_info for full details or set_summary to announce yourself.`,
|
|
51545
|
-
meta: { kind: "welcome", mesh_slug: client2.meshSlug }
|
|
51546
|
-
}
|
|
51547
|
-
});
|
|
51548
|
-
} catch {}
|
|
51549
|
-
}, 3000);
|
|
51550
51782
|
const keepalive = setInterval(() => {}, 1000);
|
|
51551
51783
|
const shutdown = () => {
|
|
51552
51784
|
clearInterval(keepalive);
|
|
@@ -52371,6 +52603,84 @@ import { mkdtempSync, writeFileSync as writeFileSync4, rmSync, readdirSync, stat
|
|
|
52371
52603
|
import { tmpdir, hostname as hostname2, homedir as homedir4 } from "node:os";
|
|
52372
52604
|
import { join as join4 } from "node:path";
|
|
52373
52605
|
import { createInterface } from "node:readline";
|
|
52606
|
+
|
|
52607
|
+
// src/auth/callback-listener.ts
|
|
52608
|
+
import { createServer } from "node:http";
|
|
52609
|
+
function startCallbackListener() {
|
|
52610
|
+
return new Promise((resolveStart) => {
|
|
52611
|
+
let resolveToken;
|
|
52612
|
+
const tokenPromise = new Promise((r) => {
|
|
52613
|
+
resolveToken = r;
|
|
52614
|
+
});
|
|
52615
|
+
const server = createServer((req, res) => {
|
|
52616
|
+
const url = new URL(req.url, "http://localhost");
|
|
52617
|
+
if (req.method === "OPTIONS") {
|
|
52618
|
+
res.writeHead(204, {
|
|
52619
|
+
"Access-Control-Allow-Origin": "https://claudemesh.com",
|
|
52620
|
+
"Access-Control-Allow-Methods": "GET",
|
|
52621
|
+
"Access-Control-Allow-Headers": "Content-Type"
|
|
52622
|
+
});
|
|
52623
|
+
res.end();
|
|
52624
|
+
return;
|
|
52625
|
+
}
|
|
52626
|
+
if (url.pathname === "/ping") {
|
|
52627
|
+
res.writeHead(200, {
|
|
52628
|
+
"Content-Type": "text/plain",
|
|
52629
|
+
"Access-Control-Allow-Origin": "https://claudemesh.com"
|
|
52630
|
+
});
|
|
52631
|
+
res.end("ok");
|
|
52632
|
+
return;
|
|
52633
|
+
}
|
|
52634
|
+
if (url.pathname === "/callback") {
|
|
52635
|
+
const token = url.searchParams.get("token");
|
|
52636
|
+
if (token) {
|
|
52637
|
+
res.writeHead(200, {
|
|
52638
|
+
"Content-Type": "text/html",
|
|
52639
|
+
"Access-Control-Allow-Origin": "https://claudemesh.com"
|
|
52640
|
+
});
|
|
52641
|
+
res.end("<html><body><h2>Done! You can close this tab.</h2><p>Launching claudemesh...</p></body></html>");
|
|
52642
|
+
resolveToken(token);
|
|
52643
|
+
setTimeout(() => server.close(), 500);
|
|
52644
|
+
} else {
|
|
52645
|
+
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
52646
|
+
res.end("Missing token");
|
|
52647
|
+
}
|
|
52648
|
+
return;
|
|
52649
|
+
}
|
|
52650
|
+
res.writeHead(404);
|
|
52651
|
+
res.end();
|
|
52652
|
+
});
|
|
52653
|
+
server.listen(0, "127.0.0.1", () => {
|
|
52654
|
+
const addr = server.address();
|
|
52655
|
+
resolveStart({
|
|
52656
|
+
port: addr.port,
|
|
52657
|
+
token: tokenPromise,
|
|
52658
|
+
close: () => server.close()
|
|
52659
|
+
});
|
|
52660
|
+
});
|
|
52661
|
+
});
|
|
52662
|
+
}
|
|
52663
|
+
// src/auth/open-browser.ts
|
|
52664
|
+
import { exec } from "node:child_process";
|
|
52665
|
+
function openBrowser(url) {
|
|
52666
|
+
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
|
52667
|
+
return Promise.resolve(false);
|
|
52668
|
+
}
|
|
52669
|
+
const quoted = JSON.stringify(url);
|
|
52670
|
+
const browserCmd = process.env.BROWSER;
|
|
52671
|
+
const cmd = browserCmd ? `${browserCmd} ${quoted}` : process.platform === "darwin" ? `open ${quoted}` : process.platform === "win32" ? `rundll32 url.dll,FileProtocolHandler ${quoted}` : `xdg-open ${quoted}`;
|
|
52672
|
+
return new Promise((resolve2) => {
|
|
52673
|
+
exec(cmd, (err) => resolve2(!err));
|
|
52674
|
+
});
|
|
52675
|
+
}
|
|
52676
|
+
// src/auth/pairing-code.ts
|
|
52677
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
52678
|
+
var CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789";
|
|
52679
|
+
function generatePairingCode() {
|
|
52680
|
+
const bytes = randomBytes2(4);
|
|
52681
|
+
return Array.from(bytes, (b) => CHARS[b % CHARS.length]).join("");
|
|
52682
|
+
}
|
|
52683
|
+
// src/commands/launch.ts
|
|
52374
52684
|
async function pickMesh(meshes) {
|
|
52375
52685
|
if (meshes.length === 1)
|
|
52376
52686
|
return meshes[0];
|
|
@@ -52509,6 +52819,73 @@ async function runLaunch(flags, rawArgs) {
|
|
|
52509
52819
|
console.log(`✓ Joined "${invite.payload.mesh_slug}"${enroll.alreadyMember ? " (already member)" : ""}`);
|
|
52510
52820
|
}
|
|
52511
52821
|
const config2 = loadConfig();
|
|
52822
|
+
let justSynced = false;
|
|
52823
|
+
if (config2.meshes.length === 0 && !args.joinLink) {
|
|
52824
|
+
const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
|
|
52825
|
+
const bold2 = (s) => useColor ? `\x1B[1m${s}\x1B[22m` : s;
|
|
52826
|
+
const dim = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
|
|
52827
|
+
const green = (s) => useColor ? `\x1B[32m${s}\x1B[39m` : s;
|
|
52828
|
+
const code = generatePairingCode();
|
|
52829
|
+
const listener = await startCallbackListener();
|
|
52830
|
+
const url = `https://claudemesh.com/cli-auth?port=${listener.port}&code=${code}&action=sync`;
|
|
52831
|
+
console.log(`
|
|
52832
|
+
${bold2("Welcome to claudemesh!")} No meshes found.`);
|
|
52833
|
+
console.log(` Opening browser to sign in...
|
|
52834
|
+
`);
|
|
52835
|
+
const opened = await openBrowser(url);
|
|
52836
|
+
if (!opened) {
|
|
52837
|
+
console.log(` Couldn't open browser automatically.`);
|
|
52838
|
+
}
|
|
52839
|
+
console.log(` ${dim(`Visit: ${url}`)}`);
|
|
52840
|
+
console.log(` ${dim(`Or join with invite: claudemesh launch --join <url>`)}
|
|
52841
|
+
`);
|
|
52842
|
+
const manualPromise = new Promise((resolve2) => {
|
|
52843
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
52844
|
+
rl.question(" Paste sync token (or wait for browser): ", (answer) => {
|
|
52845
|
+
rl.close();
|
|
52846
|
+
if (answer.trim())
|
|
52847
|
+
resolve2(answer.trim());
|
|
52848
|
+
});
|
|
52849
|
+
});
|
|
52850
|
+
const timeoutPromise = new Promise((resolve2) => {
|
|
52851
|
+
setTimeout(() => resolve2(null), 900000);
|
|
52852
|
+
});
|
|
52853
|
+
const syncToken = await Promise.race([
|
|
52854
|
+
listener.token,
|
|
52855
|
+
manualPromise,
|
|
52856
|
+
timeoutPromise
|
|
52857
|
+
]);
|
|
52858
|
+
listener.close();
|
|
52859
|
+
if (!syncToken) {
|
|
52860
|
+
console.error(`
|
|
52861
|
+
Timed out waiting for sign-in.`);
|
|
52862
|
+
process.exit(1);
|
|
52863
|
+
}
|
|
52864
|
+
const { generateKeypair: generateKeypair3 } = await Promise.resolve().then(() => (init_keypair(), exports_keypair));
|
|
52865
|
+
const keypair = await generateKeypair3();
|
|
52866
|
+
const displayNameForSync = args.name ?? `${hostname2()}-${process.pid}`;
|
|
52867
|
+
const { syncWithBroker: syncWithBroker2 } = await Promise.resolve().then(() => exports_sync_with_broker);
|
|
52868
|
+
const result = await syncWithBroker2(syncToken, keypair.publicKey, displayNameForSync);
|
|
52869
|
+
const { saveConfig: saveConfig2 } = await Promise.resolve().then(() => (init_config(), exports_config));
|
|
52870
|
+
for (const m of result.meshes) {
|
|
52871
|
+
config2.meshes.push({
|
|
52872
|
+
meshId: m.mesh_id,
|
|
52873
|
+
memberId: m.member_id,
|
|
52874
|
+
slug: m.slug,
|
|
52875
|
+
name: m.slug,
|
|
52876
|
+
pubkey: keypair.publicKey,
|
|
52877
|
+
secretKey: keypair.secretKey,
|
|
52878
|
+
brokerUrl: m.broker_url,
|
|
52879
|
+
joinedAt: new Date().toISOString()
|
|
52880
|
+
});
|
|
52881
|
+
}
|
|
52882
|
+
config2.accountId = result.account_id;
|
|
52883
|
+
saveConfig2(config2);
|
|
52884
|
+
justSynced = true;
|
|
52885
|
+
console.log(`
|
|
52886
|
+
${green("✓")} Synced ${result.meshes.length} mesh(es): ${result.meshes.map((m) => m.slug).join(", ")}
|
|
52887
|
+
`);
|
|
52888
|
+
}
|
|
52512
52889
|
if (config2.meshes.length === 0) {
|
|
52513
52890
|
console.error("No meshes joined. Run `claudemesh join <url>` or use --join <url>.");
|
|
52514
52891
|
process.exit(1);
|
|
@@ -52528,7 +52905,7 @@ async function runLaunch(flags, rawArgs) {
|
|
|
52528
52905
|
let role = args.role;
|
|
52529
52906
|
let parsedGroups = args.groups ? parseGroupsString(args.groups) : [];
|
|
52530
52907
|
let messageMode = args.messageMode ?? "push";
|
|
52531
|
-
if (!args.quiet) {
|
|
52908
|
+
if (!args.quiet && !justSynced) {
|
|
52532
52909
|
if (role === null) {
|
|
52533
52910
|
const answer = await askLine(" Role (optional): ");
|
|
52534
52911
|
if (answer)
|
|
@@ -52754,7 +53131,7 @@ init_config();
|
|
|
52754
53131
|
// package.json
|
|
52755
53132
|
var package_default = {
|
|
52756
53133
|
name: "claudemesh-cli",
|
|
52757
|
-
version: "0.
|
|
53134
|
+
version: "0.9.1",
|
|
52758
53135
|
description: "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.",
|
|
52759
53136
|
keywords: [
|
|
52760
53137
|
"claude-code",
|
|
@@ -53698,6 +54075,151 @@ function runCreate(args) {
|
|
|
53698
54075
|
console.log(" claudemesh create --list-templates");
|
|
53699
54076
|
}
|
|
53700
54077
|
|
|
54078
|
+
// src/commands/sync.ts
|
|
54079
|
+
init_config();
|
|
54080
|
+
import { createInterface as createInterface2 } from "node:readline";
|
|
54081
|
+
import { hostname as hostname4 } from "node:os";
|
|
54082
|
+
init_keypair();
|
|
54083
|
+
async function runSync(args) {
|
|
54084
|
+
const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
|
|
54085
|
+
const dim = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
|
|
54086
|
+
const green = (s) => useColor ? `\x1B[32m${s}\x1B[39m` : s;
|
|
54087
|
+
const config2 = loadConfig();
|
|
54088
|
+
const code = generatePairingCode();
|
|
54089
|
+
const listener = await startCallbackListener();
|
|
54090
|
+
const url = `https://claudemesh.com/cli-auth?port=${listener.port}&code=${code}&action=sync`;
|
|
54091
|
+
console.log(`Opening browser to sync meshes...`);
|
|
54092
|
+
console.log(dim(`Visit: ${url}`));
|
|
54093
|
+
await openBrowser(url);
|
|
54094
|
+
const manualPromise = new Promise((resolve2) => {
|
|
54095
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
54096
|
+
rl.question("Paste sync token (or wait for browser): ", (answer) => {
|
|
54097
|
+
rl.close();
|
|
54098
|
+
if (answer.trim())
|
|
54099
|
+
resolve2(answer.trim());
|
|
54100
|
+
});
|
|
54101
|
+
});
|
|
54102
|
+
const timeoutPromise = new Promise((resolve2) => {
|
|
54103
|
+
setTimeout(() => resolve2(null), 15 * 60000);
|
|
54104
|
+
});
|
|
54105
|
+
const syncToken = await Promise.race([
|
|
54106
|
+
listener.token,
|
|
54107
|
+
manualPromise,
|
|
54108
|
+
timeoutPromise
|
|
54109
|
+
]);
|
|
54110
|
+
listener.close();
|
|
54111
|
+
if (!syncToken) {
|
|
54112
|
+
console.error("Timed out waiting for sign-in.");
|
|
54113
|
+
process.exit(1);
|
|
54114
|
+
}
|
|
54115
|
+
const keypair = config2.meshes.length > 0 ? { publicKey: config2.meshes[0].pubkey, secretKey: config2.meshes[0].secretKey } : await generateKeypair2();
|
|
54116
|
+
const displayName = config2.displayName ?? `${hostname4()}-${process.pid}`;
|
|
54117
|
+
const result = await syncWithBroker(syncToken, keypair.publicKey, displayName);
|
|
54118
|
+
let added = 0;
|
|
54119
|
+
for (const m of result.meshes) {
|
|
54120
|
+
if (config2.meshes.some((existing) => existing.meshId === m.mesh_id))
|
|
54121
|
+
continue;
|
|
54122
|
+
config2.meshes.push({
|
|
54123
|
+
meshId: m.mesh_id,
|
|
54124
|
+
memberId: m.member_id,
|
|
54125
|
+
slug: m.slug,
|
|
54126
|
+
name: m.slug,
|
|
54127
|
+
pubkey: keypair.publicKey,
|
|
54128
|
+
secretKey: keypair.secretKey,
|
|
54129
|
+
brokerUrl: m.broker_url,
|
|
54130
|
+
joinedAt: new Date().toISOString()
|
|
54131
|
+
});
|
|
54132
|
+
added++;
|
|
54133
|
+
}
|
|
54134
|
+
config2.accountId = result.account_id;
|
|
54135
|
+
saveConfig(config2);
|
|
54136
|
+
if (added > 0) {
|
|
54137
|
+
console.log(green(`✓ Added ${added} new mesh(es)`));
|
|
54138
|
+
} else {
|
|
54139
|
+
console.log(`Already up to date (${config2.meshes.length} meshes)`);
|
|
54140
|
+
}
|
|
54141
|
+
}
|
|
54142
|
+
|
|
54143
|
+
// src/commands/profile.ts
|
|
54144
|
+
init_config();
|
|
54145
|
+
async function runProfile(flags) {
|
|
54146
|
+
const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
|
|
54147
|
+
const dim = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
|
|
54148
|
+
const green = (s) => useColor ? `\x1B[32m${s}\x1B[39m` : s;
|
|
54149
|
+
const config2 = loadConfig();
|
|
54150
|
+
if (config2.meshes.length === 0) {
|
|
54151
|
+
console.error("No meshes joined. Run `claudemesh join <url>` first.");
|
|
54152
|
+
process.exit(1);
|
|
54153
|
+
}
|
|
54154
|
+
const mesh = flags.mesh ? config2.meshes.find((m) => m.slug === flags.mesh) : config2.meshes[0];
|
|
54155
|
+
if (!mesh) {
|
|
54156
|
+
console.error(`Mesh "${flags.mesh}" not found. Joined: ${config2.meshes.map((m) => m.slug).join(", ")}`);
|
|
54157
|
+
process.exit(1);
|
|
54158
|
+
}
|
|
54159
|
+
const brokerUrl = mesh.brokerUrl.replace("wss://", "https://").replace("ws://", "http://").replace(/\/ws\/?$/, "");
|
|
54160
|
+
const hasEdits = flags["role-tag"] !== undefined || flags.groups !== undefined || flags["message-mode"] !== undefined || flags.name !== undefined;
|
|
54161
|
+
if (hasEdits) {
|
|
54162
|
+
const targetMemberId = flags.member ?? mesh.memberId;
|
|
54163
|
+
const body = {};
|
|
54164
|
+
if (flags.name !== undefined)
|
|
54165
|
+
body.displayName = flags.name;
|
|
54166
|
+
if (flags["role-tag"] !== undefined)
|
|
54167
|
+
body.roleTag = flags["role-tag"];
|
|
54168
|
+
if (flags.groups !== undefined) {
|
|
54169
|
+
body.groups = flags.groups.split(",").map((s) => {
|
|
54170
|
+
const [name, role] = s.trim().split(":");
|
|
54171
|
+
return role ? { name, role } : { name };
|
|
54172
|
+
});
|
|
54173
|
+
}
|
|
54174
|
+
if (flags["message-mode"] !== undefined)
|
|
54175
|
+
body.messageMode = flags["message-mode"];
|
|
54176
|
+
const res = await fetch(`${brokerUrl}/mesh/${mesh.meshId}/member/${targetMemberId}`, {
|
|
54177
|
+
method: "PATCH",
|
|
54178
|
+
headers: {
|
|
54179
|
+
"Content-Type": "application/json",
|
|
54180
|
+
"X-Member-Id": mesh.memberId
|
|
54181
|
+
},
|
|
54182
|
+
body: JSON.stringify(body)
|
|
54183
|
+
});
|
|
54184
|
+
const result = await res.json();
|
|
54185
|
+
if (flags.json) {
|
|
54186
|
+
console.log(JSON.stringify(result, null, 2));
|
|
54187
|
+
} else if (result.ok) {
|
|
54188
|
+
console.log(green("✓ Profile updated"));
|
|
54189
|
+
const member = result.member;
|
|
54190
|
+
printProfile(member, dim);
|
|
54191
|
+
} else {
|
|
54192
|
+
console.error(`Error: ${result.error}`);
|
|
54193
|
+
process.exit(1);
|
|
54194
|
+
}
|
|
54195
|
+
} else {
|
|
54196
|
+
const res = await fetch(`${brokerUrl}/mesh/${mesh.meshId}/members`);
|
|
54197
|
+
const result = await res.json();
|
|
54198
|
+
if (!result.ok) {
|
|
54199
|
+
console.error(`Error: ${result.error}`);
|
|
54200
|
+
process.exit(1);
|
|
54201
|
+
}
|
|
54202
|
+
const me = result.members?.find((m) => m.id === mesh.memberId);
|
|
54203
|
+
if (flags.json) {
|
|
54204
|
+
console.log(JSON.stringify(me ?? {}, null, 2));
|
|
54205
|
+
} else if (me) {
|
|
54206
|
+
printProfile(me, dim);
|
|
54207
|
+
} else {
|
|
54208
|
+
console.log("Member not found in mesh.");
|
|
54209
|
+
}
|
|
54210
|
+
}
|
|
54211
|
+
}
|
|
54212
|
+
function printProfile(member, dim) {
|
|
54213
|
+
const groups = member.groups;
|
|
54214
|
+
const groupStr = groups?.length ? groups.map((g) => g.role ? `${g.name} (${g.role})` : g.name).join(", ") : dim("(none)");
|
|
54215
|
+
console.log(` Name: ${member.displayName ?? dim("(not set)")}`);
|
|
54216
|
+
console.log(` Role: ${member.roleTag ?? dim("(not set)")}`);
|
|
54217
|
+
console.log(` Groups: ${groupStr}`);
|
|
54218
|
+
console.log(` Messages: ${member.messageMode ?? "push"}`);
|
|
54219
|
+
console.log(` Access: ${member.permission ?? "member"}`);
|
|
54220
|
+
console.log(` Mesh: ${dim(String(member.id ?? ""))}`);
|
|
54221
|
+
}
|
|
54222
|
+
|
|
53701
54223
|
// src/index.ts
|
|
53702
54224
|
var launch = defineCommand({
|
|
53703
54225
|
meta: {
|
|
@@ -53954,6 +54476,30 @@ var main = defineCommand({
|
|
|
53954
54476
|
await runRemind(args, positionals);
|
|
53955
54477
|
}
|
|
53956
54478
|
}),
|
|
54479
|
+
sync: defineCommand({
|
|
54480
|
+
meta: { name: "sync", description: "Sync meshes from your dashboard account" },
|
|
54481
|
+
args: {
|
|
54482
|
+
force: { type: "boolean", description: "Re-link account even if already linked", default: false }
|
|
54483
|
+
},
|
|
54484
|
+
async run({ args }) {
|
|
54485
|
+
await runSync(args);
|
|
54486
|
+
}
|
|
54487
|
+
}),
|
|
54488
|
+
profile: defineCommand({
|
|
54489
|
+
meta: { name: "profile", description: "View or edit your member profile" },
|
|
54490
|
+
args: {
|
|
54491
|
+
mesh: { type: "string", description: "Mesh slug (auto-selected if only one joined)" },
|
|
54492
|
+
"role-tag": { type: "string", description: "Set role tag (e.g. 'backend-dev', 'lead')" },
|
|
54493
|
+
groups: { type: "string", description: "Set groups as 'group:role,...' (e.g. 'eng:lead,review')" },
|
|
54494
|
+
"message-mode": { type: "string", description: "'push' | 'inbox' | 'off'" },
|
|
54495
|
+
name: { type: "string", description: "Set display name" },
|
|
54496
|
+
member: { type: "string", description: "Edit another member (admin only)" },
|
|
54497
|
+
json: { type: "boolean", description: "Output as JSON", default: false }
|
|
54498
|
+
},
|
|
54499
|
+
async run({ args }) {
|
|
54500
|
+
await runProfile(args);
|
|
54501
|
+
}
|
|
54502
|
+
}),
|
|
53957
54503
|
status: defineCommand({
|
|
53958
54504
|
meta: { name: "status", description: "Check broker connectivity for each joined mesh" },
|
|
53959
54505
|
async run() {
|