claudemesh-cli 0.1.2 → 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.
Files changed (2) hide show
  1. package/dist/index.js +349 -2
  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.3" }, {
55348
55348
  capabilities: {
55349
55349
  experimental: { "claude/channel": {} },
55350
55350
  tools: {}
@@ -56081,8 +56081,341 @@ 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.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
+
56084
56417
  // src/index.ts
56085
- var HELP = `claudemesh — peer mesh for Claude Code sessions
56418
+ var HELP = `claudemesh v${VERSION} — peer mesh for Claude Code sessions
56086
56419
 
56087
56420
  Usage:
56088
56421
  claudemesh <command> [args]
@@ -56097,9 +56430,12 @@ Commands:
56097
56430
  join <url> Join a mesh via https://claudemesh.com/join/... URL
56098
56431
  list Show all joined meshes
56099
56432
  leave <slug> Leave a joined mesh
56433
+ status Health report: broker reachability per joined mesh
56434
+ doctor Diagnostic checks (install, config, keypairs, PATH)
56100
56435
  seed-test-mesh Dev-only: inject a mesh into config (skips invite flow)
56101
56436
  mcp Start MCP server (stdio) — invoked by Claude Code
56102
56437
  --help, -h Show this help
56438
+ --version, -v Show the CLI version
56103
56439
 
56104
56440
  Environment:
56105
56441
  CLAUDEMESH_BROKER_URL Override broker URL (default: wss://ic.claudemesh.com/ws)
@@ -56134,9 +56470,20 @@ async function main() {
56134
56470
  case "leave":
56135
56471
  runLeave(args);
56136
56472
  return;
56473
+ case "status":
56474
+ await runStatus();
56475
+ return;
56476
+ case "doctor":
56477
+ await runDoctor();
56478
+ return;
56137
56479
  case "seed-test-mesh":
56138
56480
  runSeedTestMesh(args);
56139
56481
  return;
56482
+ case "--version":
56483
+ case "-v":
56484
+ case "version":
56485
+ console.log(VERSION);
56486
+ return;
56140
56487
  case "--help":
56141
56488
  case "-h":
56142
56489
  case "help":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudemesh-cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.",
5
5
  "keywords": [
6
6
  "claude-code",