claudemesh-cli 0.1.2 → 0.1.4

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.
Files changed (2) hide show
  1. package/dist/index.js +429 -3
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -55344,7 +55344,7 @@ ${body}`;
55344
55344
  }
55345
55345
  async function startMcpServer() {
55346
55346
  const config2 = loadConfig();
55347
- const server = new Server({ name: "claudemesh", version: "0.1.2" }, {
55347
+ const server = new Server({ name: "claudemesh", version: "0.1.4" }, {
55348
55348
  capabilities: {
55349
55349
  experimental: { "claude/channel": {} },
55350
55350
  tools: {}
@@ -56081,8 +56081,418 @@ function runLaunch(extraArgs = []) {
56081
56081
  });
56082
56082
  }
56083
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.4",
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
+
56417
+ // src/commands/welcome.ts
56418
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
56419
+ import { homedir as homedir4 } from "node:os";
56420
+ import { join as join4 } from "node:path";
56421
+ function detectState() {
56422
+ const claudeConfig = join4(homedir4(), ".claude.json");
56423
+ let mcpRegistered = false;
56424
+ if (existsSync5(claudeConfig)) {
56425
+ try {
56426
+ const cfg = JSON.parse(readFileSync4(claudeConfig, "utf-8"));
56427
+ mcpRegistered = Boolean(cfg.mcpServers?.["claudemesh"]);
56428
+ } catch {}
56429
+ }
56430
+ if (!mcpRegistered)
56431
+ return "no-install";
56432
+ try {
56433
+ const cfg = loadConfig();
56434
+ return cfg.meshes.length === 0 ? "no-meshes" : "ready";
56435
+ } catch {
56436
+ return "broken-config";
56437
+ }
56438
+ }
56439
+ function runWelcome() {
56440
+ const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
56441
+ const bold = (s) => useColor ? `\x1B[1m${s}\x1B[22m` : s;
56442
+ const dim = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
56443
+ const green = (s) => useColor ? `\x1B[32m${s}\x1B[39m` : s;
56444
+ const yellow = (s) => useColor ? `\x1B[33m${s}\x1B[39m` : s;
56445
+ console.log(bold(`claudemesh v${VERSION}`) + dim(" — peer mesh for Claude Code"));
56446
+ console.log("─".repeat(60));
56447
+ const state = detectState();
56448
+ switch (state) {
56449
+ case "no-install":
56450
+ console.log("Welcome. Let's get you set up.");
56451
+ console.log("");
56452
+ console.log(bold("Step 1:") + " register the MCP server + status hooks");
56453
+ console.log(` ${green("$")} claudemesh install`);
56454
+ console.log("");
56455
+ console.log(dim("Step 2 (after restart): claudemesh join <invite-url>"));
56456
+ console.log(dim("Step 3: claudemesh launch"));
56457
+ break;
56458
+ case "no-meshes":
56459
+ console.log(green("✓") + " MCP registered. Now join a mesh.");
56460
+ console.log("");
56461
+ console.log(bold("Step 2:") + " join a mesh");
56462
+ console.log(` ${green("$")} claudemesh join https://claudemesh.com/join/<token>`);
56463
+ console.log("");
56464
+ console.log(dim(" Don't have an invite? Create one at ") + bold("https://claudemesh.com") + dim(" or ask a mesh owner."));
56465
+ console.log("");
56466
+ console.log(dim("Step 3 (after joining): claudemesh launch"));
56467
+ break;
56468
+ case "ready": {
56469
+ const cfg = loadConfig();
56470
+ const meshNames = cfg.meshes.map((m) => m.slug).join(", ");
56471
+ console.log(green("✓") + " MCP registered.");
56472
+ console.log(green("✓") + ` ${cfg.meshes.length} mesh(es) joined: ${meshNames}`);
56473
+ console.log("");
56474
+ console.log(bold("You're ready.") + " Launch Claude Code with real-time peer messages:");
56475
+ console.log(` ${green("$")} claudemesh launch`);
56476
+ console.log("");
56477
+ console.log(dim(" (Plain `claude` works too — messages pull-only via check_messages.)"));
56478
+ console.log("");
56479
+ console.log(dim("Health check: claudemesh status"));
56480
+ console.log(dim("Diagnostics: claudemesh doctor"));
56481
+ console.log(dim("All commands: claudemesh --help"));
56482
+ break;
56483
+ }
56484
+ case "broken-config":
56485
+ console.log(yellow("⚠") + " Your ~/.claudemesh/config.json is unreadable.");
56486
+ console.log("");
56487
+ console.log("Run diagnostics to see what's wrong:");
56488
+ console.log(` ${green("$")} claudemesh doctor`);
56489
+ break;
56490
+ }
56491
+ console.log("");
56492
+ }
56493
+
56084
56494
  // src/index.ts
56085
- var HELP = `claudemesh — peer mesh for Claude Code sessions
56495
+ var HELP = `claudemesh v${VERSION} — peer mesh for Claude Code sessions
56086
56496
 
56087
56497
  Usage:
56088
56498
  claudemesh <command> [args]
@@ -56097,9 +56507,12 @@ Commands:
56097
56507
  join <url> Join a mesh via https://claudemesh.com/join/... URL
56098
56508
  list Show all joined meshes
56099
56509
  leave <slug> Leave a joined mesh
56510
+ status Health report: broker reachability per joined mesh
56511
+ doctor Diagnostic checks (install, config, keypairs, PATH)
56100
56512
  seed-test-mesh Dev-only: inject a mesh into config (skips invite flow)
56101
56513
  mcp Start MCP server (stdio) — invoked by Claude Code
56102
56514
  --help, -h Show this help
56515
+ --version, -v Show the CLI version
56103
56516
 
56104
56517
  Environment:
56105
56518
  CLAUDEMESH_BROKER_URL Override broker URL (default: wss://ic.claudemesh.com/ws)
@@ -56134,15 +56547,28 @@ async function main() {
56134
56547
  case "leave":
56135
56548
  runLeave(args);
56136
56549
  return;
56550
+ case "status":
56551
+ await runStatus();
56552
+ return;
56553
+ case "doctor":
56554
+ await runDoctor();
56555
+ return;
56137
56556
  case "seed-test-mesh":
56138
56557
  runSeedTestMesh(args);
56139
56558
  return;
56559
+ case "--version":
56560
+ case "-v":
56561
+ case "version":
56562
+ console.log(VERSION);
56563
+ return;
56140
56564
  case "--help":
56141
56565
  case "-h":
56142
56566
  case "help":
56143
- case undefined:
56144
56567
  console.log(HELP);
56145
56568
  return;
56569
+ case undefined:
56570
+ runWelcome();
56571
+ return;
56146
56572
  default:
56147
56573
  console.error(`Unknown command: ${cmd}`);
56148
56574
  console.error("Run `claudemesh --help` for usage.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudemesh-cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.",
5
5
  "keywords": [
6
6
  "claude-code",