claudemesh-cli 0.8.8 → 0.9.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 +564 -14
- package/package.json +2 -2
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": {
|
|
@@ -51427,6 +51655,14 @@ ${lines.join(`
|
|
|
51427
51655
|
content2 = `[system] MCP server "${data.serverName}" removed (was hosted by ${data.hostedBy})`;
|
|
51428
51656
|
} else if (eventName === "mcp_restored") {
|
|
51429
51657
|
content2 = `[system] MCP server "${data.serverName}" is back online (hosted by ${data.hostedBy})`;
|
|
51658
|
+
} else if (eventName === "watch_triggered") {
|
|
51659
|
+
content2 = `[WATCH] ${data.label ?? data.url}: ${data.oldValue} → ${data.newValue}`;
|
|
51660
|
+
} else if (eventName === "mcp_deployed") {
|
|
51661
|
+
content2 = `[SERVICE] "${data.name}" deployed (${data.tool_count} tools) by ${data.deployed_by}`;
|
|
51662
|
+
} else if (eventName === "mcp_undeployed") {
|
|
51663
|
+
content2 = `[SERVICE] "${data.name}" undeployed by ${data.by}`;
|
|
51664
|
+
} else if (eventName === "mcp_scope_changed") {
|
|
51665
|
+
content2 = `[SERVICE] "${data.name}" scope changed to ${JSON.stringify(data.scope)} by ${data.by}`;
|
|
51430
51666
|
} else {
|
|
51431
51667
|
content2 = `[system] ${eventName}: ${JSON.stringify(data)}`;
|
|
51432
51668
|
}
|
|
@@ -52363,6 +52599,84 @@ import { mkdtempSync, writeFileSync as writeFileSync4, rmSync, readdirSync, stat
|
|
|
52363
52599
|
import { tmpdir, hostname as hostname2, homedir as homedir4 } from "node:os";
|
|
52364
52600
|
import { join as join4 } from "node:path";
|
|
52365
52601
|
import { createInterface } from "node:readline";
|
|
52602
|
+
|
|
52603
|
+
// src/auth/callback-listener.ts
|
|
52604
|
+
import { createServer } from "node:http";
|
|
52605
|
+
function startCallbackListener() {
|
|
52606
|
+
return new Promise((resolveStart) => {
|
|
52607
|
+
let resolveToken;
|
|
52608
|
+
const tokenPromise = new Promise((r) => {
|
|
52609
|
+
resolveToken = r;
|
|
52610
|
+
});
|
|
52611
|
+
const server = createServer((req, res) => {
|
|
52612
|
+
const url = new URL(req.url, "http://localhost");
|
|
52613
|
+
if (req.method === "OPTIONS") {
|
|
52614
|
+
res.writeHead(204, {
|
|
52615
|
+
"Access-Control-Allow-Origin": "https://claudemesh.com",
|
|
52616
|
+
"Access-Control-Allow-Methods": "GET",
|
|
52617
|
+
"Access-Control-Allow-Headers": "Content-Type"
|
|
52618
|
+
});
|
|
52619
|
+
res.end();
|
|
52620
|
+
return;
|
|
52621
|
+
}
|
|
52622
|
+
if (url.pathname === "/ping") {
|
|
52623
|
+
res.writeHead(200, {
|
|
52624
|
+
"Content-Type": "text/plain",
|
|
52625
|
+
"Access-Control-Allow-Origin": "https://claudemesh.com"
|
|
52626
|
+
});
|
|
52627
|
+
res.end("ok");
|
|
52628
|
+
return;
|
|
52629
|
+
}
|
|
52630
|
+
if (url.pathname === "/callback") {
|
|
52631
|
+
const token = url.searchParams.get("token");
|
|
52632
|
+
if (token) {
|
|
52633
|
+
res.writeHead(200, {
|
|
52634
|
+
"Content-Type": "text/html",
|
|
52635
|
+
"Access-Control-Allow-Origin": "https://claudemesh.com"
|
|
52636
|
+
});
|
|
52637
|
+
res.end("<html><body><h2>Done! You can close this tab.</h2><p>Launching claudemesh...</p></body></html>");
|
|
52638
|
+
resolveToken(token);
|
|
52639
|
+
setTimeout(() => server.close(), 500);
|
|
52640
|
+
} else {
|
|
52641
|
+
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
52642
|
+
res.end("Missing token");
|
|
52643
|
+
}
|
|
52644
|
+
return;
|
|
52645
|
+
}
|
|
52646
|
+
res.writeHead(404);
|
|
52647
|
+
res.end();
|
|
52648
|
+
});
|
|
52649
|
+
server.listen(0, "127.0.0.1", () => {
|
|
52650
|
+
const addr = server.address();
|
|
52651
|
+
resolveStart({
|
|
52652
|
+
port: addr.port,
|
|
52653
|
+
token: tokenPromise,
|
|
52654
|
+
close: () => server.close()
|
|
52655
|
+
});
|
|
52656
|
+
});
|
|
52657
|
+
});
|
|
52658
|
+
}
|
|
52659
|
+
// src/auth/open-browser.ts
|
|
52660
|
+
import { exec } from "node:child_process";
|
|
52661
|
+
function openBrowser(url) {
|
|
52662
|
+
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
|
52663
|
+
return Promise.resolve(false);
|
|
52664
|
+
}
|
|
52665
|
+
const quoted = JSON.stringify(url);
|
|
52666
|
+
const browserCmd = process.env.BROWSER;
|
|
52667
|
+
const cmd = browserCmd ? `${browserCmd} ${quoted}` : process.platform === "darwin" ? `open ${quoted}` : process.platform === "win32" ? `rundll32 url.dll,FileProtocolHandler ${quoted}` : `xdg-open ${quoted}`;
|
|
52668
|
+
return new Promise((resolve2) => {
|
|
52669
|
+
exec(cmd, (err) => resolve2(!err));
|
|
52670
|
+
});
|
|
52671
|
+
}
|
|
52672
|
+
// src/auth/pairing-code.ts
|
|
52673
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
52674
|
+
var CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789";
|
|
52675
|
+
function generatePairingCode() {
|
|
52676
|
+
const bytes = randomBytes2(4);
|
|
52677
|
+
return Array.from(bytes, (b) => CHARS[b % CHARS.length]).join("");
|
|
52678
|
+
}
|
|
52679
|
+
// src/commands/launch.ts
|
|
52366
52680
|
async function pickMesh(meshes) {
|
|
52367
52681
|
if (meshes.length === 1)
|
|
52368
52682
|
return meshes[0];
|
|
@@ -52501,6 +52815,73 @@ async function runLaunch(flags, rawArgs) {
|
|
|
52501
52815
|
console.log(`✓ Joined "${invite.payload.mesh_slug}"${enroll.alreadyMember ? " (already member)" : ""}`);
|
|
52502
52816
|
}
|
|
52503
52817
|
const config2 = loadConfig();
|
|
52818
|
+
let justSynced = false;
|
|
52819
|
+
if (config2.meshes.length === 0 && !args.joinLink) {
|
|
52820
|
+
const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
|
|
52821
|
+
const bold2 = (s) => useColor ? `\x1B[1m${s}\x1B[22m` : s;
|
|
52822
|
+
const dim = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
|
|
52823
|
+
const green = (s) => useColor ? `\x1B[32m${s}\x1B[39m` : s;
|
|
52824
|
+
const code = generatePairingCode();
|
|
52825
|
+
const listener = await startCallbackListener();
|
|
52826
|
+
const url = `https://claudemesh.com/cli-auth?port=${listener.port}&code=${code}&action=sync`;
|
|
52827
|
+
console.log(`
|
|
52828
|
+
${bold2("Welcome to claudemesh!")} No meshes found.`);
|
|
52829
|
+
console.log(` Opening browser to sign in...
|
|
52830
|
+
`);
|
|
52831
|
+
const opened = await openBrowser(url);
|
|
52832
|
+
if (!opened) {
|
|
52833
|
+
console.log(` Couldn't open browser automatically.`);
|
|
52834
|
+
}
|
|
52835
|
+
console.log(` ${dim(`Visit: ${url}`)}`);
|
|
52836
|
+
console.log(` ${dim(`Or join with invite: claudemesh launch --join <url>`)}
|
|
52837
|
+
`);
|
|
52838
|
+
const manualPromise = new Promise((resolve2) => {
|
|
52839
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
52840
|
+
rl.question(" Paste sync token (or wait for browser): ", (answer) => {
|
|
52841
|
+
rl.close();
|
|
52842
|
+
if (answer.trim())
|
|
52843
|
+
resolve2(answer.trim());
|
|
52844
|
+
});
|
|
52845
|
+
});
|
|
52846
|
+
const timeoutPromise = new Promise((resolve2) => {
|
|
52847
|
+
setTimeout(() => resolve2(null), 900000);
|
|
52848
|
+
});
|
|
52849
|
+
const syncToken = await Promise.race([
|
|
52850
|
+
listener.token,
|
|
52851
|
+
manualPromise,
|
|
52852
|
+
timeoutPromise
|
|
52853
|
+
]);
|
|
52854
|
+
listener.close();
|
|
52855
|
+
if (!syncToken) {
|
|
52856
|
+
console.error(`
|
|
52857
|
+
Timed out waiting for sign-in.`);
|
|
52858
|
+
process.exit(1);
|
|
52859
|
+
}
|
|
52860
|
+
const { generateKeypair: generateKeypair3 } = await Promise.resolve().then(() => (init_keypair(), exports_keypair));
|
|
52861
|
+
const keypair = await generateKeypair3();
|
|
52862
|
+
const displayNameForSync = args.name ?? `${hostname2()}-${process.pid}`;
|
|
52863
|
+
const { syncWithBroker: syncWithBroker2 } = await Promise.resolve().then(() => exports_sync_with_broker);
|
|
52864
|
+
const result = await syncWithBroker2(syncToken, keypair.publicKey, displayNameForSync);
|
|
52865
|
+
const { saveConfig: saveConfig2 } = await Promise.resolve().then(() => (init_config(), exports_config));
|
|
52866
|
+
for (const m of result.meshes) {
|
|
52867
|
+
config2.meshes.push({
|
|
52868
|
+
meshId: m.mesh_id,
|
|
52869
|
+
memberId: m.member_id,
|
|
52870
|
+
slug: m.slug,
|
|
52871
|
+
name: m.slug,
|
|
52872
|
+
pubkey: keypair.publicKey,
|
|
52873
|
+
secretKey: keypair.secretKey,
|
|
52874
|
+
brokerUrl: m.broker_url,
|
|
52875
|
+
joinedAt: new Date().toISOString()
|
|
52876
|
+
});
|
|
52877
|
+
}
|
|
52878
|
+
config2.accountId = result.account_id;
|
|
52879
|
+
saveConfig2(config2);
|
|
52880
|
+
justSynced = true;
|
|
52881
|
+
console.log(`
|
|
52882
|
+
${green("✓")} Synced ${result.meshes.length} mesh(es): ${result.meshes.map((m) => m.slug).join(", ")}
|
|
52883
|
+
`);
|
|
52884
|
+
}
|
|
52504
52885
|
if (config2.meshes.length === 0) {
|
|
52505
52886
|
console.error("No meshes joined. Run `claudemesh join <url>` or use --join <url>.");
|
|
52506
52887
|
process.exit(1);
|
|
@@ -52520,7 +52901,7 @@ async function runLaunch(flags, rawArgs) {
|
|
|
52520
52901
|
let role = args.role;
|
|
52521
52902
|
let parsedGroups = args.groups ? parseGroupsString(args.groups) : [];
|
|
52522
52903
|
let messageMode = args.messageMode ?? "push";
|
|
52523
|
-
if (!args.quiet) {
|
|
52904
|
+
if (!args.quiet && !justSynced) {
|
|
52524
52905
|
if (role === null) {
|
|
52525
52906
|
const answer = await askLine(" Role (optional): ");
|
|
52526
52907
|
if (answer)
|
|
@@ -52746,7 +53127,7 @@ init_config();
|
|
|
52746
53127
|
// package.json
|
|
52747
53128
|
var package_default = {
|
|
52748
53129
|
name: "claudemesh-cli",
|
|
52749
|
-
version: "0.
|
|
53130
|
+
version: "0.9.0",
|
|
52750
53131
|
description: "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.",
|
|
52751
53132
|
keywords: [
|
|
52752
53133
|
"claude-code",
|
|
@@ -53690,6 +54071,151 @@ function runCreate(args) {
|
|
|
53690
54071
|
console.log(" claudemesh create --list-templates");
|
|
53691
54072
|
}
|
|
53692
54073
|
|
|
54074
|
+
// src/commands/sync.ts
|
|
54075
|
+
init_config();
|
|
54076
|
+
import { createInterface as createInterface2 } from "node:readline";
|
|
54077
|
+
import { hostname as hostname4 } from "node:os";
|
|
54078
|
+
init_keypair();
|
|
54079
|
+
async function runSync(args) {
|
|
54080
|
+
const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
|
|
54081
|
+
const dim = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
|
|
54082
|
+
const green = (s) => useColor ? `\x1B[32m${s}\x1B[39m` : s;
|
|
54083
|
+
const config2 = loadConfig();
|
|
54084
|
+
const code = generatePairingCode();
|
|
54085
|
+
const listener = await startCallbackListener();
|
|
54086
|
+
const url = `https://claudemesh.com/cli-auth?port=${listener.port}&code=${code}&action=sync`;
|
|
54087
|
+
console.log(`Opening browser to sync meshes...`);
|
|
54088
|
+
console.log(dim(`Visit: ${url}`));
|
|
54089
|
+
await openBrowser(url);
|
|
54090
|
+
const manualPromise = new Promise((resolve2) => {
|
|
54091
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
54092
|
+
rl.question("Paste sync token (or wait for browser): ", (answer) => {
|
|
54093
|
+
rl.close();
|
|
54094
|
+
if (answer.trim())
|
|
54095
|
+
resolve2(answer.trim());
|
|
54096
|
+
});
|
|
54097
|
+
});
|
|
54098
|
+
const timeoutPromise = new Promise((resolve2) => {
|
|
54099
|
+
setTimeout(() => resolve2(null), 15 * 60000);
|
|
54100
|
+
});
|
|
54101
|
+
const syncToken = await Promise.race([
|
|
54102
|
+
listener.token,
|
|
54103
|
+
manualPromise,
|
|
54104
|
+
timeoutPromise
|
|
54105
|
+
]);
|
|
54106
|
+
listener.close();
|
|
54107
|
+
if (!syncToken) {
|
|
54108
|
+
console.error("Timed out waiting for sign-in.");
|
|
54109
|
+
process.exit(1);
|
|
54110
|
+
}
|
|
54111
|
+
const keypair = config2.meshes.length > 0 ? { publicKey: config2.meshes[0].pubkey, secretKey: config2.meshes[0].secretKey } : await generateKeypair2();
|
|
54112
|
+
const displayName = config2.displayName ?? `${hostname4()}-${process.pid}`;
|
|
54113
|
+
const result = await syncWithBroker(syncToken, keypair.publicKey, displayName);
|
|
54114
|
+
let added = 0;
|
|
54115
|
+
for (const m of result.meshes) {
|
|
54116
|
+
if (config2.meshes.some((existing) => existing.meshId === m.mesh_id))
|
|
54117
|
+
continue;
|
|
54118
|
+
config2.meshes.push({
|
|
54119
|
+
meshId: m.mesh_id,
|
|
54120
|
+
memberId: m.member_id,
|
|
54121
|
+
slug: m.slug,
|
|
54122
|
+
name: m.slug,
|
|
54123
|
+
pubkey: keypair.publicKey,
|
|
54124
|
+
secretKey: keypair.secretKey,
|
|
54125
|
+
brokerUrl: m.broker_url,
|
|
54126
|
+
joinedAt: new Date().toISOString()
|
|
54127
|
+
});
|
|
54128
|
+
added++;
|
|
54129
|
+
}
|
|
54130
|
+
config2.accountId = result.account_id;
|
|
54131
|
+
saveConfig(config2);
|
|
54132
|
+
if (added > 0) {
|
|
54133
|
+
console.log(green(`✓ Added ${added} new mesh(es)`));
|
|
54134
|
+
} else {
|
|
54135
|
+
console.log(`Already up to date (${config2.meshes.length} meshes)`);
|
|
54136
|
+
}
|
|
54137
|
+
}
|
|
54138
|
+
|
|
54139
|
+
// src/commands/profile.ts
|
|
54140
|
+
init_config();
|
|
54141
|
+
async function runProfile(flags) {
|
|
54142
|
+
const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
|
|
54143
|
+
const dim = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
|
|
54144
|
+
const green = (s) => useColor ? `\x1B[32m${s}\x1B[39m` : s;
|
|
54145
|
+
const config2 = loadConfig();
|
|
54146
|
+
if (config2.meshes.length === 0) {
|
|
54147
|
+
console.error("No meshes joined. Run `claudemesh join <url>` first.");
|
|
54148
|
+
process.exit(1);
|
|
54149
|
+
}
|
|
54150
|
+
const mesh = flags.mesh ? config2.meshes.find((m) => m.slug === flags.mesh) : config2.meshes[0];
|
|
54151
|
+
if (!mesh) {
|
|
54152
|
+
console.error(`Mesh "${flags.mesh}" not found. Joined: ${config2.meshes.map((m) => m.slug).join(", ")}`);
|
|
54153
|
+
process.exit(1);
|
|
54154
|
+
}
|
|
54155
|
+
const brokerUrl = mesh.brokerUrl.replace("wss://", "https://").replace("ws://", "http://").replace(/\/ws\/?$/, "");
|
|
54156
|
+
const hasEdits = flags["role-tag"] !== undefined || flags.groups !== undefined || flags["message-mode"] !== undefined || flags.name !== undefined;
|
|
54157
|
+
if (hasEdits) {
|
|
54158
|
+
const targetMemberId = flags.member ?? mesh.memberId;
|
|
54159
|
+
const body = {};
|
|
54160
|
+
if (flags.name !== undefined)
|
|
54161
|
+
body.displayName = flags.name;
|
|
54162
|
+
if (flags["role-tag"] !== undefined)
|
|
54163
|
+
body.roleTag = flags["role-tag"];
|
|
54164
|
+
if (flags.groups !== undefined) {
|
|
54165
|
+
body.groups = flags.groups.split(",").map((s) => {
|
|
54166
|
+
const [name, role] = s.trim().split(":");
|
|
54167
|
+
return role ? { name, role } : { name };
|
|
54168
|
+
});
|
|
54169
|
+
}
|
|
54170
|
+
if (flags["message-mode"] !== undefined)
|
|
54171
|
+
body.messageMode = flags["message-mode"];
|
|
54172
|
+
const res = await fetch(`${brokerUrl}/mesh/${mesh.meshId}/member/${targetMemberId}`, {
|
|
54173
|
+
method: "PATCH",
|
|
54174
|
+
headers: {
|
|
54175
|
+
"Content-Type": "application/json",
|
|
54176
|
+
"X-Member-Id": mesh.memberId
|
|
54177
|
+
},
|
|
54178
|
+
body: JSON.stringify(body)
|
|
54179
|
+
});
|
|
54180
|
+
const result = await res.json();
|
|
54181
|
+
if (flags.json) {
|
|
54182
|
+
console.log(JSON.stringify(result, null, 2));
|
|
54183
|
+
} else if (result.ok) {
|
|
54184
|
+
console.log(green("✓ Profile updated"));
|
|
54185
|
+
const member = result.member;
|
|
54186
|
+
printProfile(member, dim);
|
|
54187
|
+
} else {
|
|
54188
|
+
console.error(`Error: ${result.error}`);
|
|
54189
|
+
process.exit(1);
|
|
54190
|
+
}
|
|
54191
|
+
} else {
|
|
54192
|
+
const res = await fetch(`${brokerUrl}/mesh/${mesh.meshId}/members`);
|
|
54193
|
+
const result = await res.json();
|
|
54194
|
+
if (!result.ok) {
|
|
54195
|
+
console.error(`Error: ${result.error}`);
|
|
54196
|
+
process.exit(1);
|
|
54197
|
+
}
|
|
54198
|
+
const me = result.members?.find((m) => m.id === mesh.memberId);
|
|
54199
|
+
if (flags.json) {
|
|
54200
|
+
console.log(JSON.stringify(me ?? {}, null, 2));
|
|
54201
|
+
} else if (me) {
|
|
54202
|
+
printProfile(me, dim);
|
|
54203
|
+
} else {
|
|
54204
|
+
console.log("Member not found in mesh.");
|
|
54205
|
+
}
|
|
54206
|
+
}
|
|
54207
|
+
}
|
|
54208
|
+
function printProfile(member, dim) {
|
|
54209
|
+
const groups = member.groups;
|
|
54210
|
+
const groupStr = groups?.length ? groups.map((g) => g.role ? `${g.name} (${g.role})` : g.name).join(", ") : dim("(none)");
|
|
54211
|
+
console.log(` Name: ${member.displayName ?? dim("(not set)")}`);
|
|
54212
|
+
console.log(` Role: ${member.roleTag ?? dim("(not set)")}`);
|
|
54213
|
+
console.log(` Groups: ${groupStr}`);
|
|
54214
|
+
console.log(` Messages: ${member.messageMode ?? "push"}`);
|
|
54215
|
+
console.log(` Access: ${member.permission ?? "member"}`);
|
|
54216
|
+
console.log(` Mesh: ${dim(String(member.id ?? ""))}`);
|
|
54217
|
+
}
|
|
54218
|
+
|
|
53693
54219
|
// src/index.ts
|
|
53694
54220
|
var launch = defineCommand({
|
|
53695
54221
|
meta: {
|
|
@@ -53946,6 +54472,30 @@ var main = defineCommand({
|
|
|
53946
54472
|
await runRemind(args, positionals);
|
|
53947
54473
|
}
|
|
53948
54474
|
}),
|
|
54475
|
+
sync: defineCommand({
|
|
54476
|
+
meta: { name: "sync", description: "Sync meshes from your dashboard account" },
|
|
54477
|
+
args: {
|
|
54478
|
+
force: { type: "boolean", description: "Re-link account even if already linked", default: false }
|
|
54479
|
+
},
|
|
54480
|
+
async run({ args }) {
|
|
54481
|
+
await runSync(args);
|
|
54482
|
+
}
|
|
54483
|
+
}),
|
|
54484
|
+
profile: defineCommand({
|
|
54485
|
+
meta: { name: "profile", description: "View or edit your member profile" },
|
|
54486
|
+
args: {
|
|
54487
|
+
mesh: { type: "string", description: "Mesh slug (auto-selected if only one joined)" },
|
|
54488
|
+
"role-tag": { type: "string", description: "Set role tag (e.g. 'backend-dev', 'lead')" },
|
|
54489
|
+
groups: { type: "string", description: "Set groups as 'group:role,...' (e.g. 'eng:lead,review')" },
|
|
54490
|
+
"message-mode": { type: "string", description: "'push' | 'inbox' | 'off'" },
|
|
54491
|
+
name: { type: "string", description: "Set display name" },
|
|
54492
|
+
member: { type: "string", description: "Edit another member (admin only)" },
|
|
54493
|
+
json: { type: "boolean", description: "Output as JSON", default: false }
|
|
54494
|
+
},
|
|
54495
|
+
async run({ args }) {
|
|
54496
|
+
await runProfile(args);
|
|
54497
|
+
}
|
|
54498
|
+
}),
|
|
53949
54499
|
status: defineCommand({
|
|
53950
54500
|
meta: { name: "status", description: "Check broker connectivity for each joined mesh" },
|
|
53951
54501
|
async run() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claudemesh-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
|
@@ -48,8 +48,8 @@
|
|
|
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
51
|
"@turbostarter/prettier-config": "0.1.0",
|
|
52
|
+
"@turbostarter/eslint-config": "0.1.0",
|
|
53
53
|
"@turbostarter/tsconfig": "0.1.0",
|
|
54
54
|
"@turbostarter/vitest-config": "0.1.0"
|
|
55
55
|
},
|