claudemesh-cli 0.1.0 → 0.1.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 +237 -22
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -55340,13 +55340,28 @@ ${body}`;
|
|
|
55340
55340
|
}
|
|
55341
55341
|
async function startMcpServer() {
|
|
55342
55342
|
const config2 = loadConfig();
|
|
55343
|
-
const server = new Server({ name: "claudemesh", version: "0.1.
|
|
55344
|
-
capabilities: {
|
|
55345
|
-
|
|
55343
|
+
const server = new Server({ name: "claudemesh", version: "0.1.1" }, {
|
|
55344
|
+
capabilities: {
|
|
55345
|
+
experimental: { "claude/channel": {} },
|
|
55346
|
+
tools: {}
|
|
55347
|
+
},
|
|
55348
|
+
instructions: `You are connected to claudemesh — a peer mesh for Claude Code sessions on this machine and elsewhere.
|
|
55349
|
+
|
|
55350
|
+
IMPORTANT: When you receive a <channel source="claudemesh" ...> message, RESPOND IMMEDIATELY. Do not wait until your current task is finished. Pause what you are doing, reply to the message using send_message, then resume your work. Treat incoming peer messages like a coworker tapping you on the shoulder — answer right away, even if you're in the middle of something.
|
|
55351
|
+
|
|
55352
|
+
Read the from_id, from_name, mesh_slug, and priority attributes to understand context. Reply by calling send_message with the same target (for direct messages the from_id is the sender's pubkey).
|
|
55346
55353
|
|
|
55347
|
-
|
|
55354
|
+
Available tools:
|
|
55355
|
+
- list_peers: see joined meshes + their connection status
|
|
55356
|
+
- send_message: send to a peer pubkey, channel, or broadcast (priority: now/next/low)
|
|
55357
|
+
- check_messages: drain buffered inbound messages (usually auto-pushed)
|
|
55358
|
+
- set_summary: 1-2 sentence summary of what you're working on
|
|
55359
|
+
- set_status: manually override your status (idle/working/dnd)
|
|
55348
55360
|
|
|
55349
|
-
|
|
55361
|
+
Message priority:
|
|
55362
|
+
- "now": delivered immediately regardless of recipient status (use sparingly)
|
|
55363
|
+
- "next" (default): delivered when recipient is idle
|
|
55364
|
+
- "low": pull-only (check_messages)
|
|
55350
55365
|
|
|
55351
55366
|
If you have multiple joined meshes, prefix the \`to\` argument of send_message with \`<mesh-slug>:\` to disambiguate. Otherwise claudemesh picks the single joined mesh.`
|
|
55352
55367
|
});
|
|
@@ -55422,6 +55437,31 @@ ${drained.join(`
|
|
|
55422
55437
|
await startClients(config2);
|
|
55423
55438
|
const transport = new StdioServerTransport;
|
|
55424
55439
|
await server.connect(transport);
|
|
55440
|
+
for (const client of allClients()) {
|
|
55441
|
+
client.onPush(async (msg) => {
|
|
55442
|
+
const fromPubkey = msg.senderPubkey || "";
|
|
55443
|
+
const fromName = fromPubkey ? `peer-${fromPubkey.slice(0, 8)}` : "unknown";
|
|
55444
|
+
const content = msg.plaintext ?? "(decryption failed)";
|
|
55445
|
+
try {
|
|
55446
|
+
await server.notification({
|
|
55447
|
+
method: "notifications/claude/channel",
|
|
55448
|
+
params: {
|
|
55449
|
+
content,
|
|
55450
|
+
meta: {
|
|
55451
|
+
from_id: fromPubkey,
|
|
55452
|
+
from_name: fromName,
|
|
55453
|
+
mesh_slug: client.meshSlug,
|
|
55454
|
+
mesh_id: client.meshId,
|
|
55455
|
+
priority: msg.priority,
|
|
55456
|
+
sent_at: msg.createdAt,
|
|
55457
|
+
delivered_at: msg.receivedAt,
|
|
55458
|
+
kind: msg.kind
|
|
55459
|
+
}
|
|
55460
|
+
}
|
|
55461
|
+
});
|
|
55462
|
+
} catch {}
|
|
55463
|
+
});
|
|
55464
|
+
}
|
|
55425
55465
|
const shutdown = () => {
|
|
55426
55466
|
stopAll();
|
|
55427
55467
|
process.exit(0);
|
|
@@ -55444,6 +55484,10 @@ import { fileURLToPath } from "node:url";
|
|
|
55444
55484
|
import { spawnSync } from "node:child_process";
|
|
55445
55485
|
var MCP_NAME = "claudemesh";
|
|
55446
55486
|
var CLAUDE_CONFIG = join2(homedir2(), ".claude.json");
|
|
55487
|
+
var CLAUDE_SETTINGS = join2(homedir2(), ".claude", "settings.json");
|
|
55488
|
+
var HOOK_COMMAND_STOP = "claudemesh hook idle";
|
|
55489
|
+
var HOOK_COMMAND_USER_PROMPT = "claudemesh hook working";
|
|
55490
|
+
var HOOK_MARKER = "claudemesh hook ";
|
|
55447
55491
|
function readClaudeConfig() {
|
|
55448
55492
|
if (!existsSync2(CLAUDE_CONFIG))
|
|
55449
55493
|
return {};
|
|
@@ -55491,7 +55535,71 @@ function buildMcpEntry(entryPath) {
|
|
|
55491
55535
|
function entriesEqual(a, b) {
|
|
55492
55536
|
return a.command === b.command && JSON.stringify(a.args ?? []) === JSON.stringify(b.args ?? []);
|
|
55493
55537
|
}
|
|
55494
|
-
function
|
|
55538
|
+
function readClaudeSettings() {
|
|
55539
|
+
if (!existsSync2(CLAUDE_SETTINGS))
|
|
55540
|
+
return {};
|
|
55541
|
+
const text2 = readFileSync2(CLAUDE_SETTINGS, "utf-8").trim();
|
|
55542
|
+
if (!text2)
|
|
55543
|
+
return {};
|
|
55544
|
+
try {
|
|
55545
|
+
return JSON.parse(text2);
|
|
55546
|
+
} catch (e) {
|
|
55547
|
+
throw new Error(`failed to parse ${CLAUDE_SETTINGS}: ${e instanceof Error ? e.message : String(e)}`);
|
|
55548
|
+
}
|
|
55549
|
+
}
|
|
55550
|
+
function writeClaudeSettings(obj) {
|
|
55551
|
+
mkdirSync2(dirname2(CLAUDE_SETTINGS), { recursive: true });
|
|
55552
|
+
writeFileSync2(CLAUDE_SETTINGS, JSON.stringify(obj, null, 2) + `
|
|
55553
|
+
`, "utf-8");
|
|
55554
|
+
}
|
|
55555
|
+
function installHooks() {
|
|
55556
|
+
const settings = readClaudeSettings();
|
|
55557
|
+
const hooks = (settings.hooks ??= {}) ?? {};
|
|
55558
|
+
let added = 0;
|
|
55559
|
+
let unchanged = 0;
|
|
55560
|
+
const ensure = (event, command) => {
|
|
55561
|
+
const list = hooks[event] ??= [];
|
|
55562
|
+
const alreadyPresent = list.some((entry) => (entry.hooks ?? []).some((h) => h.command === command));
|
|
55563
|
+
if (alreadyPresent) {
|
|
55564
|
+
unchanged += 1;
|
|
55565
|
+
return;
|
|
55566
|
+
}
|
|
55567
|
+
list.push({ hooks: [{ type: "command", command }] });
|
|
55568
|
+
added += 1;
|
|
55569
|
+
};
|
|
55570
|
+
ensure("Stop", HOOK_COMMAND_STOP);
|
|
55571
|
+
ensure("UserPromptSubmit", HOOK_COMMAND_USER_PROMPT);
|
|
55572
|
+
settings.hooks = hooks;
|
|
55573
|
+
writeClaudeSettings(settings);
|
|
55574
|
+
return { added, unchanged };
|
|
55575
|
+
}
|
|
55576
|
+
function uninstallHooks() {
|
|
55577
|
+
if (!existsSync2(CLAUDE_SETTINGS))
|
|
55578
|
+
return 0;
|
|
55579
|
+
const settings = readClaudeSettings();
|
|
55580
|
+
const hooks = settings.hooks;
|
|
55581
|
+
if (!hooks)
|
|
55582
|
+
return 0;
|
|
55583
|
+
let removed = 0;
|
|
55584
|
+
for (const event of Object.keys(hooks)) {
|
|
55585
|
+
const kept = [];
|
|
55586
|
+
for (const entry of hooks[event] ?? []) {
|
|
55587
|
+
const filtered = (entry.hooks ?? []).filter((h) => !(h.command ?? "").includes(HOOK_MARKER));
|
|
55588
|
+
removed += (entry.hooks ?? []).length - filtered.length;
|
|
55589
|
+
if (filtered.length > 0)
|
|
55590
|
+
kept.push({ ...entry, hooks: filtered });
|
|
55591
|
+
}
|
|
55592
|
+
if (kept.length === 0)
|
|
55593
|
+
delete hooks[event];
|
|
55594
|
+
else
|
|
55595
|
+
hooks[event] = kept;
|
|
55596
|
+
}
|
|
55597
|
+
settings.hooks = hooks;
|
|
55598
|
+
writeClaudeSettings(settings);
|
|
55599
|
+
return removed;
|
|
55600
|
+
}
|
|
55601
|
+
function runInstall(args = []) {
|
|
55602
|
+
const skipHooks = args.includes("--no-hooks");
|
|
55495
55603
|
console.log("claudemesh install");
|
|
55496
55604
|
console.log("------------------");
|
|
55497
55605
|
const entry = resolveEntry();
|
|
@@ -55534,6 +55642,22 @@ function runInstall() {
|
|
|
55534
55642
|
console.log(`✓ MCP server "${MCP_NAME}" ${action}`);
|
|
55535
55643
|
console.log(dim(` config: ${CLAUDE_CONFIG}`));
|
|
55536
55644
|
console.log(dim(` command: ${desired.command}${desired.args?.length ? " " + desired.args.join(" ") : ""}`));
|
|
55645
|
+
if (!skipHooks) {
|
|
55646
|
+
try {
|
|
55647
|
+
const { added, unchanged } = installHooks();
|
|
55648
|
+
if (added > 0) {
|
|
55649
|
+
console.log(`✓ Hooks registered (Stop + UserPromptSubmit) → ${added} added, ${unchanged} already present`);
|
|
55650
|
+
} else {
|
|
55651
|
+
console.log(`✓ Hooks already registered (${unchanged} present)`);
|
|
55652
|
+
}
|
|
55653
|
+
console.log(dim(` config: ${CLAUDE_SETTINGS}`));
|
|
55654
|
+
} catch (e) {
|
|
55655
|
+
console.error(`⚠ hook registration failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
55656
|
+
console.error(" (MCP is still installed — hooks just skip. Retry with --no-hooks to suppress.)");
|
|
55657
|
+
}
|
|
55658
|
+
} else {
|
|
55659
|
+
console.log(dim("· Hooks skipped (--no-hooks)"));
|
|
55660
|
+
}
|
|
55537
55661
|
console.log("");
|
|
55538
55662
|
console.log(yellow(bold("⚠ RESTART CLAUDE CODE")) + yellow(" for MCP tools to appear."));
|
|
55539
55663
|
console.log("");
|
|
@@ -55542,21 +55666,32 @@ function runInstall() {
|
|
|
55542
55666
|
function runUninstall() {
|
|
55543
55667
|
console.log("claudemesh uninstall");
|
|
55544
55668
|
console.log("--------------------");
|
|
55545
|
-
if (
|
|
55546
|
-
|
|
55547
|
-
|
|
55669
|
+
if (existsSync2(CLAUDE_CONFIG)) {
|
|
55670
|
+
const cfg = readClaudeConfig();
|
|
55671
|
+
const servers = cfg.mcpServers;
|
|
55672
|
+
if (servers && MCP_NAME in servers) {
|
|
55673
|
+
delete servers[MCP_NAME];
|
|
55674
|
+
cfg.mcpServers = servers;
|
|
55675
|
+
writeClaudeConfig(cfg);
|
|
55676
|
+
console.log(`✓ MCP server "${MCP_NAME}" removed`);
|
|
55677
|
+
} else {
|
|
55678
|
+
console.log(`· MCP server "${MCP_NAME}" not present`);
|
|
55679
|
+
}
|
|
55680
|
+
} else {
|
|
55681
|
+
console.log(`· no ${CLAUDE_CONFIG} — MCP entry skipped`);
|
|
55548
55682
|
}
|
|
55549
|
-
|
|
55550
|
-
|
|
55551
|
-
|
|
55552
|
-
|
|
55553
|
-
|
|
55683
|
+
try {
|
|
55684
|
+
const removed = uninstallHooks();
|
|
55685
|
+
if (removed > 0) {
|
|
55686
|
+
console.log(`✓ Hooks removed (${removed} entries)`);
|
|
55687
|
+
} else {
|
|
55688
|
+
console.log("· No claudemesh hooks to remove");
|
|
55689
|
+
}
|
|
55690
|
+
} catch (e) {
|
|
55691
|
+
console.error(`⚠ hook removal failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
55554
55692
|
}
|
|
55555
|
-
|
|
55556
|
-
|
|
55557
|
-
writeClaudeConfig(cfg);
|
|
55558
|
-
console.log(`✓ MCP server "${MCP_NAME}" removed`);
|
|
55559
|
-
console.log("Restart Claude Code to drop the MCP connection.");
|
|
55693
|
+
console.log("");
|
|
55694
|
+
console.log("Restart Claude Code to drop the MCP connection + hooks.");
|
|
55560
55695
|
}
|
|
55561
55696
|
|
|
55562
55697
|
// src/invite/parse.ts
|
|
@@ -55794,6 +55929,82 @@ function runSeedTestMesh(args) {
|
|
|
55794
55929
|
console.log(`Run \`claudemesh mcp\` to connect, or register with Claude Code via \`claudemesh install\`.`);
|
|
55795
55930
|
}
|
|
55796
55931
|
|
|
55932
|
+
// src/commands/hook.ts
|
|
55933
|
+
var DEBUG = process.env.CLAUDEMESH_HOOK_DEBUG === "1";
|
|
55934
|
+
function debug(msg) {
|
|
55935
|
+
if (DEBUG)
|
|
55936
|
+
console.error(`[claudemesh-hook] ${msg}`);
|
|
55937
|
+
}
|
|
55938
|
+
function wsToHttp2(wsUrl) {
|
|
55939
|
+
try {
|
|
55940
|
+
const u = new URL(wsUrl);
|
|
55941
|
+
const httpScheme = u.protocol === "wss:" ? "https:" : "http:";
|
|
55942
|
+
return `${httpScheme}//${u.host}`;
|
|
55943
|
+
} catch {
|
|
55944
|
+
return wsUrl;
|
|
55945
|
+
}
|
|
55946
|
+
}
|
|
55947
|
+
async function readStdinJson() {
|
|
55948
|
+
if (process.stdin.isTTY)
|
|
55949
|
+
return {};
|
|
55950
|
+
const chunks = [];
|
|
55951
|
+
const reader = process.stdin;
|
|
55952
|
+
try {
|
|
55953
|
+
for await (const chunk of reader) {
|
|
55954
|
+
chunks.push(chunk);
|
|
55955
|
+
if (chunks.reduce((n, c) => n + c.length, 0) > 256 * 1024)
|
|
55956
|
+
break;
|
|
55957
|
+
}
|
|
55958
|
+
const raw = Buffer.concat(chunks).toString("utf-8").trim();
|
|
55959
|
+
if (!raw)
|
|
55960
|
+
return {};
|
|
55961
|
+
return JSON.parse(raw);
|
|
55962
|
+
} catch {
|
|
55963
|
+
return {};
|
|
55964
|
+
}
|
|
55965
|
+
}
|
|
55966
|
+
async function postHook(brokerWsUrl, body) {
|
|
55967
|
+
const base = wsToHttp2(brokerWsUrl);
|
|
55968
|
+
try {
|
|
55969
|
+
const controller = new AbortController;
|
|
55970
|
+
const t = setTimeout(() => controller.abort(), 1000);
|
|
55971
|
+
await fetch(`${base}/hook/set-status`, {
|
|
55972
|
+
method: "POST",
|
|
55973
|
+
headers: { "Content-Type": "application/json" },
|
|
55974
|
+
body: JSON.stringify(body),
|
|
55975
|
+
signal: controller.signal
|
|
55976
|
+
}).finally(() => clearTimeout(t));
|
|
55977
|
+
} catch (e) {
|
|
55978
|
+
debug(`post failed ${base}: ${e instanceof Error ? e.message : e}`);
|
|
55979
|
+
}
|
|
55980
|
+
}
|
|
55981
|
+
async function runHook(args) {
|
|
55982
|
+
const status = args[0];
|
|
55983
|
+
if (!status || !["idle", "working", "dnd"].includes(status)) {
|
|
55984
|
+
process.exit(0);
|
|
55985
|
+
}
|
|
55986
|
+
const stdinTimeout = new Promise((r) => setTimeout(() => r({}), 500));
|
|
55987
|
+
const payload = await Promise.race([readStdinJson(), stdinTimeout]);
|
|
55988
|
+
const cwd = typeof payload.cwd === "string" && payload.cwd || process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
55989
|
+
const sessionId = typeof payload.session_id === "string" && payload.session_id || "";
|
|
55990
|
+
let config2;
|
|
55991
|
+
try {
|
|
55992
|
+
config2 = loadConfig();
|
|
55993
|
+
} catch (e) {
|
|
55994
|
+
debug(`config load failed: ${e instanceof Error ? e.message : e}`);
|
|
55995
|
+
process.exit(0);
|
|
55996
|
+
}
|
|
55997
|
+
if (config2.meshes.length === 0) {
|
|
55998
|
+
debug("no joined meshes, nothing to do");
|
|
55999
|
+
process.exit(0);
|
|
56000
|
+
}
|
|
56001
|
+
const body = { cwd, pid: process.ppid, status, session_id: sessionId };
|
|
56002
|
+
debug(`status=${status} cwd=${cwd} meshes=${config2.meshes.length} session=${sessionId.slice(0, 8)}`);
|
|
56003
|
+
const brokerUrls = [...new Set(config2.meshes.map((m) => m.brokerUrl))];
|
|
56004
|
+
await Promise.all(brokerUrls.map((url2) => postHook(url2, body)));
|
|
56005
|
+
process.exit(0);
|
|
56006
|
+
}
|
|
56007
|
+
|
|
55797
56008
|
// src/index.ts
|
|
55798
56009
|
var HELP = `claudemesh — peer mesh for Claude Code sessions
|
|
55799
56010
|
|
|
@@ -55801,8 +56012,9 @@ Usage:
|
|
|
55801
56012
|
claudemesh <command> [args]
|
|
55802
56013
|
|
|
55803
56014
|
Commands:
|
|
55804
|
-
install Register
|
|
55805
|
-
|
|
56015
|
+
install Register MCP + Stop/UserPromptSubmit status hooks
|
|
56016
|
+
(add --no-hooks for bare MCP registration)
|
|
56017
|
+
uninstall Remove MCP server + hooks
|
|
55806
56018
|
join <url> Join a mesh via https://claudemesh.com/join/... URL
|
|
55807
56019
|
list Show all joined meshes
|
|
55808
56020
|
leave <slug> Leave a joined mesh
|
|
@@ -55823,11 +56035,14 @@ async function main() {
|
|
|
55823
56035
|
await startMcpServer();
|
|
55824
56036
|
return;
|
|
55825
56037
|
case "install":
|
|
55826
|
-
runInstall();
|
|
56038
|
+
runInstall(args);
|
|
55827
56039
|
return;
|
|
55828
56040
|
case "uninstall":
|
|
55829
56041
|
runUninstall();
|
|
55830
56042
|
return;
|
|
56043
|
+
case "hook":
|
|
56044
|
+
await runHook(args);
|
|
56045
|
+
return;
|
|
55831
56046
|
case "join":
|
|
55832
56047
|
await runJoin(args);
|
|
55833
56048
|
return;
|