claudemesh-cli 0.1.1 → 0.1.3
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/README.md +25 -1
- package/dist/index.js +434 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,6 +28,28 @@ Run the printed command, then restart Claude Code.
|
|
|
28
28
|
claudemesh join https://claudemesh.com/join/<token>
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
+
## Launch Claude Code
|
|
32
|
+
|
|
33
|
+
For real-time **push messages** from peers (messages injected mid-turn
|
|
34
|
+
as `<channel source="claudemesh">` system reminders), launch with:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
claudemesh launch
|
|
38
|
+
# or pass through any claude flags:
|
|
39
|
+
claudemesh launch --model opus
|
|
40
|
+
claudemesh launch --resume
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Under the hood this runs:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
claude --dangerously-load-development-channels server:claudemesh
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Plain `claude` still works — the MCP tools are available — but incoming
|
|
50
|
+
messages are **pull-only** via the `check_messages` tool instead of
|
|
51
|
+
being pushed to Claude immediately.
|
|
52
|
+
|
|
31
53
|
The invite link is generated by whoever runs the mesh. It bundles the
|
|
32
54
|
mesh id, expiry, signing key, and role. Your CLI verifies it,
|
|
33
55
|
generates a fresh keypair, enrolls you with the broker, and persists
|
|
@@ -36,7 +58,9 @@ the result to `~/.claudemesh/config.json`.
|
|
|
36
58
|
## Commands
|
|
37
59
|
|
|
38
60
|
```sh
|
|
39
|
-
claudemesh install #
|
|
61
|
+
claudemesh install # register MCP + status hooks
|
|
62
|
+
claudemesh uninstall # remove MCP + status hooks
|
|
63
|
+
claudemesh launch [args] # launch Claude Code with push messages enabled
|
|
40
64
|
claudemesh join <url> # join a mesh via invite URL
|
|
41
65
|
claudemesh list # show joined meshes + identities
|
|
42
66
|
claudemesh leave <slug> # leave a mesh
|
package/dist/index.js
CHANGED
|
@@ -55187,7 +55187,7 @@ class BrokerClient {
|
|
|
55187
55187
|
if (senderPubkey && nonce && ciphertext) {
|
|
55188
55188
|
plaintext = await decryptDirect({ nonce, ciphertext }, senderPubkey, this.mesh.secretKey);
|
|
55189
55189
|
}
|
|
55190
|
-
if (plaintext === null && ciphertext) {
|
|
55190
|
+
if (plaintext === null && ciphertext && !senderPubkey) {
|
|
55191
55191
|
try {
|
|
55192
55192
|
plaintext = Buffer.from(ciphertext, "base64").toString("utf-8");
|
|
55193
55193
|
} catch {
|
|
@@ -55333,14 +55333,18 @@ function resolveClient(to) {
|
|
|
55333
55333
|
error: `multiple meshes joined; prefix target with "<mesh-slug>:" (joined: ${clients2.map((c) => c.meshSlug).join(", ")})`
|
|
55334
55334
|
};
|
|
55335
55335
|
}
|
|
55336
|
+
function decryptFailedWarning(senderPubkey) {
|
|
55337
|
+
const who = senderPubkey ? senderPubkey.slice(0, 12) + "…" : "unknown sender";
|
|
55338
|
+
return `⚠ message from ${who} failed to decrypt (tampered or wrong keypair)`;
|
|
55339
|
+
}
|
|
55336
55340
|
function formatPush(p, meshSlug) {
|
|
55337
|
-
const body = p.plaintext ??
|
|
55341
|
+
const body = p.plaintext ?? decryptFailedWarning(p.senderPubkey);
|
|
55338
55342
|
return `[${meshSlug}] from ${p.senderPubkey.slice(0, 12)}… (${p.priority}, ${p.createdAt}):
|
|
55339
55343
|
${body}`;
|
|
55340
55344
|
}
|
|
55341
55345
|
async function startMcpServer() {
|
|
55342
55346
|
const config2 = loadConfig();
|
|
55343
|
-
const server = new Server({ name: "claudemesh", version: "0.1.
|
|
55347
|
+
const server = new Server({ name: "claudemesh", version: "0.1.3" }, {
|
|
55344
55348
|
capabilities: {
|
|
55345
55349
|
experimental: { "claude/channel": {} },
|
|
55346
55350
|
tools: {}
|
|
@@ -55441,7 +55445,7 @@ ${drained.join(`
|
|
|
55441
55445
|
client.onPush(async (msg) => {
|
|
55442
55446
|
const fromPubkey = msg.senderPubkey || "";
|
|
55443
55447
|
const fromName = fromPubkey ? `peer-${fromPubkey.slice(0, 8)}` : "unknown";
|
|
55444
|
-
const content = msg.plaintext ??
|
|
55448
|
+
const content = msg.plaintext ?? decryptFailedWarning(fromPubkey);
|
|
55445
55449
|
try {
|
|
55446
55450
|
await server.notification({
|
|
55447
55451
|
method: "notifications/claude/channel",
|
|
@@ -55662,6 +55666,10 @@ function runInstall(args = []) {
|
|
|
55662
55666
|
console.log(yellow(bold("⚠ RESTART CLAUDE CODE")) + yellow(" for MCP tools to appear."));
|
|
55663
55667
|
console.log("");
|
|
55664
55668
|
console.log(`Next: ${bold("claudemesh join https://claudemesh.com/join/<token>")}`);
|
|
55669
|
+
console.log("");
|
|
55670
|
+
console.log(yellow("⚠ For real-time push messages from peers, launch with:"));
|
|
55671
|
+
console.log(` ${bold("claudemesh launch")}` + dim(" (or: claude --dangerously-load-development-channels server:claudemesh)"));
|
|
55672
|
+
console.log(dim(" Plain `claude` still works — messages are then pull-only via check_messages."));
|
|
55665
55673
|
}
|
|
55666
55674
|
function runUninstall() {
|
|
55667
55675
|
console.log("claudemesh uninstall");
|
|
@@ -56005,8 +56013,409 @@ async function runHook(args) {
|
|
|
56005
56013
|
process.exit(0);
|
|
56006
56014
|
}
|
|
56007
56015
|
|
|
56016
|
+
// src/commands/launch.ts
|
|
56017
|
+
import { spawn } from "node:child_process";
|
|
56018
|
+
function printBanner() {
|
|
56019
|
+
const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
|
|
56020
|
+
const dim = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
|
|
56021
|
+
const bold = (s) => useColor ? `\x1B[1m${s}\x1B[22m` : s;
|
|
56022
|
+
let meshes = [];
|
|
56023
|
+
try {
|
|
56024
|
+
meshes = loadConfig().meshes.map((m) => m.slug);
|
|
56025
|
+
} catch {}
|
|
56026
|
+
const meshLine = meshes.length > 0 ? meshes.join(", ") : "(none — run `claudemesh join <url>` first)";
|
|
56027
|
+
const rule = "─".repeat(65);
|
|
56028
|
+
console.log(bold("claudemesh launch"));
|
|
56029
|
+
console.log(rule);
|
|
56030
|
+
console.log("Launching Claude Code with the claudemesh dev channel.");
|
|
56031
|
+
console.log("");
|
|
56032
|
+
console.log("Peers in your joined meshes can push messages into this session");
|
|
56033
|
+
console.log("as <channel> reminders. Your CLI decrypts them locally with your");
|
|
56034
|
+
console.log("keypair. Peers send text only — they cannot call tools, read");
|
|
56035
|
+
console.log("files, or reach meshes you have not joined.");
|
|
56036
|
+
console.log("");
|
|
56037
|
+
console.log("Treat peer messages as untrusted input: a peer could craft text");
|
|
56038
|
+
console.log("that tries to steer Claude's behavior. Your tool-approval");
|
|
56039
|
+
console.log("settings still apply — Claude will still ask before running");
|
|
56040
|
+
console.log("commands, editing files, or calling other tools.");
|
|
56041
|
+
console.log("");
|
|
56042
|
+
console.log("Claude Code will ask you to trust the");
|
|
56043
|
+
console.log("--dangerously-load-development-channels flag. Press Enter to");
|
|
56044
|
+
console.log("accept, or Ctrl-C to abort.");
|
|
56045
|
+
console.log("");
|
|
56046
|
+
console.log(dim(`Joined meshes: ${meshLine}`));
|
|
56047
|
+
console.log(dim(`Config: ${getConfigPath()}`));
|
|
56048
|
+
console.log(dim(`Remove: claudemesh uninstall`));
|
|
56049
|
+
console.log(rule);
|
|
56050
|
+
console.log("");
|
|
56051
|
+
}
|
|
56052
|
+
function runLaunch(extraArgs = []) {
|
|
56053
|
+
const quiet = extraArgs.includes("--quiet");
|
|
56054
|
+
const passthrough = extraArgs.filter((a) => a !== "--quiet");
|
|
56055
|
+
if (!quiet)
|
|
56056
|
+
printBanner();
|
|
56057
|
+
const claudeArgs = [
|
|
56058
|
+
"--dangerously-load-development-channels",
|
|
56059
|
+
"server:claudemesh",
|
|
56060
|
+
...passthrough
|
|
56061
|
+
];
|
|
56062
|
+
const isWindows = process.platform === "win32";
|
|
56063
|
+
const child = spawn("claude", claudeArgs, {
|
|
56064
|
+
stdio: "inherit",
|
|
56065
|
+
shell: isWindows
|
|
56066
|
+
});
|
|
56067
|
+
child.on("error", (err) => {
|
|
56068
|
+
if (err.code === "ENOENT") {
|
|
56069
|
+
console.error("✗ `claude` not found on PATH. Install Claude Code first: https://claude.com/claude-code");
|
|
56070
|
+
} else {
|
|
56071
|
+
console.error(`✗ failed to launch claude: ${err.message}`);
|
|
56072
|
+
}
|
|
56073
|
+
process.exit(1);
|
|
56074
|
+
});
|
|
56075
|
+
child.on("exit", (code, signal) => {
|
|
56076
|
+
if (signal) {
|
|
56077
|
+
process.kill(process.pid, signal);
|
|
56078
|
+
return;
|
|
56079
|
+
}
|
|
56080
|
+
process.exit(code ?? 0);
|
|
56081
|
+
});
|
|
56082
|
+
}
|
|
56083
|
+
|
|
56084
|
+
// src/commands/status.ts
|
|
56085
|
+
import { statSync, existsSync as existsSync3 } from "node:fs";
|
|
56086
|
+
// package.json
|
|
56087
|
+
var package_default = {
|
|
56088
|
+
name: "claudemesh-cli",
|
|
56089
|
+
version: "0.1.3",
|
|
56090
|
+
description: "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.",
|
|
56091
|
+
keywords: [
|
|
56092
|
+
"claude-code",
|
|
56093
|
+
"mcp",
|
|
56094
|
+
"model-context-protocol",
|
|
56095
|
+
"claudemesh",
|
|
56096
|
+
"peer-messaging",
|
|
56097
|
+
"multi-agent"
|
|
56098
|
+
],
|
|
56099
|
+
author: "Alejandro Gutiérrez",
|
|
56100
|
+
license: "MIT",
|
|
56101
|
+
homepage: "https://claudemesh.com",
|
|
56102
|
+
repository: {
|
|
56103
|
+
type: "git",
|
|
56104
|
+
url: "https://github.com/alezmad/claudemesh.git",
|
|
56105
|
+
directory: "apps/cli"
|
|
56106
|
+
},
|
|
56107
|
+
type: "module",
|
|
56108
|
+
bin: {
|
|
56109
|
+
claudemesh: "./dist/index.js"
|
|
56110
|
+
},
|
|
56111
|
+
files: [
|
|
56112
|
+
"dist",
|
|
56113
|
+
"README.md",
|
|
56114
|
+
"LICENSE"
|
|
56115
|
+
],
|
|
56116
|
+
publishConfig: {
|
|
56117
|
+
access: "public"
|
|
56118
|
+
},
|
|
56119
|
+
scripts: {
|
|
56120
|
+
build: 'bun build src/index.ts --target=node --outfile dist/index.js --banner "#!/usr/bin/env node" && chmod +x dist/index.js',
|
|
56121
|
+
clean: "git clean -xdf .cache .turbo dist node_modules",
|
|
56122
|
+
dev: "bun --hot src/index.ts",
|
|
56123
|
+
start: "bun src/index.ts",
|
|
56124
|
+
format: "prettier --check . --ignore-path ../../.gitignore",
|
|
56125
|
+
lint: "eslint",
|
|
56126
|
+
prepublishOnly: "bun run build",
|
|
56127
|
+
test: "vitest run",
|
|
56128
|
+
typecheck: "tsc --noEmit"
|
|
56129
|
+
},
|
|
56130
|
+
prettier: "@turbostarter/prettier-config",
|
|
56131
|
+
engines: {
|
|
56132
|
+
node: ">=20"
|
|
56133
|
+
},
|
|
56134
|
+
dependencies: {
|
|
56135
|
+
"@modelcontextprotocol/sdk": "1.27.1",
|
|
56136
|
+
"libsodium-wrappers": "0.7.15",
|
|
56137
|
+
ws: "8.20.0",
|
|
56138
|
+
zod: "4.1.13"
|
|
56139
|
+
},
|
|
56140
|
+
devDependencies: {
|
|
56141
|
+
"@turbostarter/eslint-config": "workspace:*",
|
|
56142
|
+
"@turbostarter/prettier-config": "workspace:*",
|
|
56143
|
+
"@turbostarter/tsconfig": "workspace:*",
|
|
56144
|
+
"@turbostarter/vitest-config": "workspace:*",
|
|
56145
|
+
"@types/libsodium-wrappers": "0.7.14",
|
|
56146
|
+
"@types/ws": "8.5.13",
|
|
56147
|
+
eslint: "catalog:",
|
|
56148
|
+
prettier: "catalog:",
|
|
56149
|
+
typescript: "catalog:",
|
|
56150
|
+
vitest: "catalog:"
|
|
56151
|
+
}
|
|
56152
|
+
};
|
|
56153
|
+
|
|
56154
|
+
// src/version.ts
|
|
56155
|
+
var VERSION = package_default.version;
|
|
56156
|
+
|
|
56157
|
+
// src/commands/status.ts
|
|
56158
|
+
async function probeBroker(url2, timeoutMs = 4000) {
|
|
56159
|
+
return new Promise((resolve2) => {
|
|
56160
|
+
const ws = new wrapper_default(url2);
|
|
56161
|
+
const timer = setTimeout(() => {
|
|
56162
|
+
try {
|
|
56163
|
+
ws.terminate();
|
|
56164
|
+
} catch {}
|
|
56165
|
+
resolve2({ ok: false, error: "timeout" });
|
|
56166
|
+
}, timeoutMs);
|
|
56167
|
+
ws.on("open", () => {
|
|
56168
|
+
clearTimeout(timer);
|
|
56169
|
+
try {
|
|
56170
|
+
ws.close();
|
|
56171
|
+
} catch {}
|
|
56172
|
+
resolve2({ ok: true });
|
|
56173
|
+
});
|
|
56174
|
+
ws.on("error", (err) => {
|
|
56175
|
+
clearTimeout(timer);
|
|
56176
|
+
resolve2({ ok: false, error: err.message });
|
|
56177
|
+
});
|
|
56178
|
+
});
|
|
56179
|
+
}
|
|
56180
|
+
async function runStatus() {
|
|
56181
|
+
const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
|
|
56182
|
+
const dim = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
|
|
56183
|
+
const green = (s) => useColor ? `\x1B[32m${s}\x1B[39m` : s;
|
|
56184
|
+
const red = (s) => useColor ? `\x1B[31m${s}\x1B[39m` : s;
|
|
56185
|
+
console.log(`claudemesh status (v${VERSION})`);
|
|
56186
|
+
console.log("─".repeat(60));
|
|
56187
|
+
const configPath = getConfigPath();
|
|
56188
|
+
let configPerms = "missing";
|
|
56189
|
+
if (existsSync3(configPath)) {
|
|
56190
|
+
const st = statSync(configPath);
|
|
56191
|
+
const mode = (st.mode & 511).toString(8).padStart(4, "0");
|
|
56192
|
+
configPerms = mode === "0600" ? `${mode} ✓` : `${mode} ⚠ (expected 0600)`;
|
|
56193
|
+
}
|
|
56194
|
+
console.log(`Config: ${configPath} (${configPerms})`);
|
|
56195
|
+
const config2 = loadConfig();
|
|
56196
|
+
if (config2.meshes.length === 0) {
|
|
56197
|
+
console.log("");
|
|
56198
|
+
console.log(dim("No meshes joined. Run `claudemesh join <invite-url>` to get started."));
|
|
56199
|
+
process.exit(0);
|
|
56200
|
+
}
|
|
56201
|
+
console.log("");
|
|
56202
|
+
console.log(`Meshes (${config2.meshes.length}):`);
|
|
56203
|
+
const results = [];
|
|
56204
|
+
for (const m of config2.meshes) {
|
|
56205
|
+
process.stdout.write(` ${m.slug.padEnd(20)} probing ${m.brokerUrl}… `);
|
|
56206
|
+
const probe = await probeBroker(m.brokerUrl);
|
|
56207
|
+
results.push({
|
|
56208
|
+
slug: m.slug,
|
|
56209
|
+
brokerUrl: m.brokerUrl,
|
|
56210
|
+
pubkey: m.pubkey,
|
|
56211
|
+
reachable: probe.ok,
|
|
56212
|
+
error: probe.error
|
|
56213
|
+
});
|
|
56214
|
+
if (probe.ok) {
|
|
56215
|
+
console.log(green("reachable"));
|
|
56216
|
+
} else {
|
|
56217
|
+
console.log(red(`unreachable (${probe.error})`));
|
|
56218
|
+
}
|
|
56219
|
+
}
|
|
56220
|
+
console.log("");
|
|
56221
|
+
for (const r of results) {
|
|
56222
|
+
console.log(dim(` ${r.slug}: pubkey ${r.pubkey.slice(0, 16)}…`));
|
|
56223
|
+
}
|
|
56224
|
+
const allOk = results.every((r) => r.reachable);
|
|
56225
|
+
console.log("");
|
|
56226
|
+
if (allOk) {
|
|
56227
|
+
console.log(green("All meshes reachable."));
|
|
56228
|
+
process.exit(0);
|
|
56229
|
+
} else {
|
|
56230
|
+
const broken = results.filter((r) => !r.reachable).length;
|
|
56231
|
+
console.log(red(`${broken} of ${results.length} mesh(es) unreachable.`));
|
|
56232
|
+
process.exit(1);
|
|
56233
|
+
}
|
|
56234
|
+
}
|
|
56235
|
+
|
|
56236
|
+
// src/commands/doctor.ts
|
|
56237
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, statSync as statSync2 } from "node:fs";
|
|
56238
|
+
import { homedir as homedir3, platform as platform2 } from "node:os";
|
|
56239
|
+
import { join as join3 } from "node:path";
|
|
56240
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
56241
|
+
function checkNode() {
|
|
56242
|
+
const major = Number(process.versions.node.split(".")[0]);
|
|
56243
|
+
return {
|
|
56244
|
+
name: "Node.js >= 20",
|
|
56245
|
+
pass: major >= 20,
|
|
56246
|
+
detail: `v${process.versions.node}`,
|
|
56247
|
+
fix: "Install Node 20 or newer (https://nodejs.org)"
|
|
56248
|
+
};
|
|
56249
|
+
}
|
|
56250
|
+
function checkClaudeOnPath() {
|
|
56251
|
+
const res = platform2() === "win32" ? spawnSync2("where", ["claude"]) : spawnSync2("sh", ["-c", "command -v claude"]);
|
|
56252
|
+
const onPath = res.status === 0;
|
|
56253
|
+
const location = onPath ? res.stdout.toString().trim().split(`
|
|
56254
|
+
`)[0] : undefined;
|
|
56255
|
+
return {
|
|
56256
|
+
name: "claude binary on PATH",
|
|
56257
|
+
pass: onPath,
|
|
56258
|
+
detail: location,
|
|
56259
|
+
fix: "Install Claude Code (https://claude.com/claude-code)"
|
|
56260
|
+
};
|
|
56261
|
+
}
|
|
56262
|
+
function checkMcpRegistered() {
|
|
56263
|
+
const claudeConfig = join3(homedir3(), ".claude.json");
|
|
56264
|
+
if (!existsSync4(claudeConfig)) {
|
|
56265
|
+
return {
|
|
56266
|
+
name: "claudemesh MCP registered in ~/.claude.json",
|
|
56267
|
+
pass: false,
|
|
56268
|
+
fix: "Run `claudemesh install`"
|
|
56269
|
+
};
|
|
56270
|
+
}
|
|
56271
|
+
try {
|
|
56272
|
+
const cfg = JSON.parse(readFileSync3(claudeConfig, "utf-8"));
|
|
56273
|
+
const registered = Boolean(cfg.mcpServers?.["claudemesh"]);
|
|
56274
|
+
return {
|
|
56275
|
+
name: "claudemesh MCP registered in ~/.claude.json",
|
|
56276
|
+
pass: registered,
|
|
56277
|
+
fix: registered ? undefined : "Run `claudemesh install`"
|
|
56278
|
+
};
|
|
56279
|
+
} catch (e) {
|
|
56280
|
+
return {
|
|
56281
|
+
name: "claudemesh MCP registered in ~/.claude.json",
|
|
56282
|
+
pass: false,
|
|
56283
|
+
detail: e instanceof Error ? e.message : String(e),
|
|
56284
|
+
fix: "Check ~/.claude.json for JSON parse errors"
|
|
56285
|
+
};
|
|
56286
|
+
}
|
|
56287
|
+
}
|
|
56288
|
+
function checkHooksRegistered() {
|
|
56289
|
+
const settings = join3(homedir3(), ".claude", "settings.json");
|
|
56290
|
+
if (!existsSync4(settings)) {
|
|
56291
|
+
return {
|
|
56292
|
+
name: "Status hooks registered in ~/.claude/settings.json",
|
|
56293
|
+
pass: false,
|
|
56294
|
+
fix: "Run `claudemesh install` (remove --no-hooks)"
|
|
56295
|
+
};
|
|
56296
|
+
}
|
|
56297
|
+
try {
|
|
56298
|
+
const raw = readFileSync3(settings, "utf-8");
|
|
56299
|
+
const has = raw.includes("claudemesh hook ");
|
|
56300
|
+
return {
|
|
56301
|
+
name: "Status hooks registered in ~/.claude/settings.json",
|
|
56302
|
+
pass: has,
|
|
56303
|
+
fix: has ? undefined : "Run `claudemesh install` (remove --no-hooks)"
|
|
56304
|
+
};
|
|
56305
|
+
} catch (e) {
|
|
56306
|
+
return {
|
|
56307
|
+
name: "Status hooks registered in ~/.claude/settings.json",
|
|
56308
|
+
pass: false,
|
|
56309
|
+
detail: e instanceof Error ? e.message : String(e)
|
|
56310
|
+
};
|
|
56311
|
+
}
|
|
56312
|
+
}
|
|
56313
|
+
function checkConfigFile() {
|
|
56314
|
+
const path = getConfigPath();
|
|
56315
|
+
if (!existsSync4(path)) {
|
|
56316
|
+
return {
|
|
56317
|
+
name: "~/.claudemesh/config.json exists and parses",
|
|
56318
|
+
pass: true,
|
|
56319
|
+
detail: "not created yet (fine — no meshes joined)"
|
|
56320
|
+
};
|
|
56321
|
+
}
|
|
56322
|
+
try {
|
|
56323
|
+
loadConfig();
|
|
56324
|
+
const st = statSync2(path);
|
|
56325
|
+
const mode = (st.mode & 511).toString(8);
|
|
56326
|
+
const secure = platform2() === "win32" || mode === "600";
|
|
56327
|
+
return {
|
|
56328
|
+
name: "~/.claudemesh/config.json parses + chmod 0600",
|
|
56329
|
+
pass: secure,
|
|
56330
|
+
detail: platform2() === "win32" ? "chmod skipped on Windows" : `0${mode}`,
|
|
56331
|
+
fix: secure ? undefined : `chmod 600 ${path}`
|
|
56332
|
+
};
|
|
56333
|
+
} catch (e) {
|
|
56334
|
+
return {
|
|
56335
|
+
name: "~/.claudemesh/config.json exists and parses",
|
|
56336
|
+
pass: false,
|
|
56337
|
+
detail: e instanceof Error ? e.message : String(e),
|
|
56338
|
+
fix: "Inspect or delete ~/.claudemesh/config.json and re-join"
|
|
56339
|
+
};
|
|
56340
|
+
}
|
|
56341
|
+
}
|
|
56342
|
+
function checkKeypairs() {
|
|
56343
|
+
try {
|
|
56344
|
+
const cfg = loadConfig();
|
|
56345
|
+
if (cfg.meshes.length === 0) {
|
|
56346
|
+
return {
|
|
56347
|
+
name: "Mesh keypairs valid",
|
|
56348
|
+
pass: true,
|
|
56349
|
+
detail: "no meshes joined"
|
|
56350
|
+
};
|
|
56351
|
+
}
|
|
56352
|
+
for (const m of cfg.meshes) {
|
|
56353
|
+
if (m.pubkey.length !== 64 || !/^[0-9a-f]+$/.test(m.pubkey)) {
|
|
56354
|
+
return {
|
|
56355
|
+
name: "Mesh keypairs valid",
|
|
56356
|
+
pass: false,
|
|
56357
|
+
detail: `${m.slug}: pubkey malformed`,
|
|
56358
|
+
fix: `Leave + re-join the mesh: claudemesh leave ${m.slug}`
|
|
56359
|
+
};
|
|
56360
|
+
}
|
|
56361
|
+
if (m.secretKey.length !== 128 || !/^[0-9a-f]+$/.test(m.secretKey)) {
|
|
56362
|
+
return {
|
|
56363
|
+
name: "Mesh keypairs valid",
|
|
56364
|
+
pass: false,
|
|
56365
|
+
detail: `${m.slug}: secret key malformed`,
|
|
56366
|
+
fix: `Leave + re-join the mesh: claudemesh leave ${m.slug}`
|
|
56367
|
+
};
|
|
56368
|
+
}
|
|
56369
|
+
}
|
|
56370
|
+
return {
|
|
56371
|
+
name: "Mesh keypairs valid",
|
|
56372
|
+
pass: true,
|
|
56373
|
+
detail: `${cfg.meshes.length} mesh(es)`
|
|
56374
|
+
};
|
|
56375
|
+
} catch (e) {
|
|
56376
|
+
return {
|
|
56377
|
+
name: "Mesh keypairs valid",
|
|
56378
|
+
pass: false,
|
|
56379
|
+
detail: e instanceof Error ? e.message : String(e)
|
|
56380
|
+
};
|
|
56381
|
+
}
|
|
56382
|
+
}
|
|
56383
|
+
async function runDoctor() {
|
|
56384
|
+
const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
|
|
56385
|
+
const dim = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
|
|
56386
|
+
const green = (s) => useColor ? `\x1B[32m${s}\x1B[39m` : s;
|
|
56387
|
+
const red = (s) => useColor ? `\x1B[31m${s}\x1B[39m` : s;
|
|
56388
|
+
console.log(`claudemesh doctor (v${VERSION})`);
|
|
56389
|
+
console.log("─".repeat(60));
|
|
56390
|
+
const checks3 = [
|
|
56391
|
+
checkNode(),
|
|
56392
|
+
checkClaudeOnPath(),
|
|
56393
|
+
checkMcpRegistered(),
|
|
56394
|
+
checkHooksRegistered(),
|
|
56395
|
+
checkConfigFile(),
|
|
56396
|
+
checkKeypairs()
|
|
56397
|
+
];
|
|
56398
|
+
for (const c of checks3) {
|
|
56399
|
+
const mark = c.pass ? green("✓") : red("✗");
|
|
56400
|
+
const detail = c.detail ? dim(` (${c.detail})`) : "";
|
|
56401
|
+
console.log(`${mark} ${c.name}${detail}`);
|
|
56402
|
+
if (!c.pass && c.fix) {
|
|
56403
|
+
console.log(dim(` → ${c.fix}`));
|
|
56404
|
+
}
|
|
56405
|
+
}
|
|
56406
|
+
const failing = checks3.filter((c) => !c.pass);
|
|
56407
|
+
console.log("");
|
|
56408
|
+
if (failing.length === 0) {
|
|
56409
|
+
console.log(green("All checks passed."));
|
|
56410
|
+
process.exit(0);
|
|
56411
|
+
} else {
|
|
56412
|
+
console.log(red(`${failing.length} check(s) failed.`));
|
|
56413
|
+
process.exit(1);
|
|
56414
|
+
}
|
|
56415
|
+
}
|
|
56416
|
+
|
|
56008
56417
|
// src/index.ts
|
|
56009
|
-
var HELP = `claudemesh — peer mesh for Claude Code sessions
|
|
56418
|
+
var HELP = `claudemesh v${VERSION} — peer mesh for Claude Code sessions
|
|
56010
56419
|
|
|
56011
56420
|
Usage:
|
|
56012
56421
|
claudemesh <command> [args]
|
|
@@ -56015,12 +56424,18 @@ Commands:
|
|
|
56015
56424
|
install Register MCP + Stop/UserPromptSubmit status hooks
|
|
56016
56425
|
(add --no-hooks for bare MCP registration)
|
|
56017
56426
|
uninstall Remove MCP server + hooks
|
|
56427
|
+
launch [args] Launch Claude Code with real-time push messages enabled
|
|
56428
|
+
(add --quiet to skip the info banner; passes through
|
|
56429
|
+
extra flags, e.g. --model, --resume)
|
|
56018
56430
|
join <url> Join a mesh via https://claudemesh.com/join/... URL
|
|
56019
56431
|
list Show all joined meshes
|
|
56020
56432
|
leave <slug> Leave a joined mesh
|
|
56433
|
+
status Health report: broker reachability per joined mesh
|
|
56434
|
+
doctor Diagnostic checks (install, config, keypairs, PATH)
|
|
56021
56435
|
seed-test-mesh Dev-only: inject a mesh into config (skips invite flow)
|
|
56022
56436
|
mcp Start MCP server (stdio) — invoked by Claude Code
|
|
56023
56437
|
--help, -h Show this help
|
|
56438
|
+
--version, -v Show the CLI version
|
|
56024
56439
|
|
|
56025
56440
|
Environment:
|
|
56026
56441
|
CLAUDEMESH_BROKER_URL Override broker URL (default: wss://ic.claudemesh.com/ws)
|
|
@@ -56043,6 +56458,9 @@ async function main() {
|
|
|
56043
56458
|
case "hook":
|
|
56044
56459
|
await runHook(args);
|
|
56045
56460
|
return;
|
|
56461
|
+
case "launch":
|
|
56462
|
+
runLaunch(args);
|
|
56463
|
+
return;
|
|
56046
56464
|
case "join":
|
|
56047
56465
|
await runJoin(args);
|
|
56048
56466
|
return;
|
|
@@ -56052,9 +56470,20 @@ async function main() {
|
|
|
56052
56470
|
case "leave":
|
|
56053
56471
|
runLeave(args);
|
|
56054
56472
|
return;
|
|
56473
|
+
case "status":
|
|
56474
|
+
await runStatus();
|
|
56475
|
+
return;
|
|
56476
|
+
case "doctor":
|
|
56477
|
+
await runDoctor();
|
|
56478
|
+
return;
|
|
56055
56479
|
case "seed-test-mesh":
|
|
56056
56480
|
runSeedTestMesh(args);
|
|
56057
56481
|
return;
|
|
56482
|
+
case "--version":
|
|
56483
|
+
case "-v":
|
|
56484
|
+
case "version":
|
|
56485
|
+
console.log(VERSION);
|
|
56486
|
+
return;
|
|
56058
56487
|
case "--help":
|
|
56059
56488
|
case "-h":
|
|
56060
56489
|
case "help":
|