Compare commits
3 Commits
cli-v0.1.2
...
cli-v0.1.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f698aaeac7 | ||
|
|
8810aa1e9e | ||
|
|
fa234fae25 |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "claudemesh-cli",
|
"name": "claudemesh-cli",
|
||||||
"version": "0.1.2",
|
"version": "0.1.4",
|
||||||
"description": "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.",
|
"description": "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"claude-code",
|
"claude-code",
|
||||||
|
|||||||
212
apps/cli/src/commands/doctor.ts
Normal file
212
apps/cli/src/commands/doctor.ts
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
/**
|
||||||
|
* `claudemesh doctor` — diagnostic checks.
|
||||||
|
*
|
||||||
|
* Walks through the install + runtime preconditions and prints each
|
||||||
|
* as pass/fail with a fix hint on failure. Exit 0 if everything
|
||||||
|
* passes, 1 otherwise.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
||||||
|
import { homedir, platform } from "node:os";
|
||||||
|
import { join } from "node:path";
|
||||||
|
import { spawnSync } from "node:child_process";
|
||||||
|
import { loadConfig, getConfigPath } from "../state/config";
|
||||||
|
import { VERSION } from "../version";
|
||||||
|
|
||||||
|
interface Check {
|
||||||
|
name: string;
|
||||||
|
pass: boolean;
|
||||||
|
detail?: string;
|
||||||
|
fix?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkNode(): Check {
|
||||||
|
const major = Number(process.versions.node.split(".")[0]);
|
||||||
|
return {
|
||||||
|
name: "Node.js >= 20",
|
||||||
|
pass: major >= 20,
|
||||||
|
detail: `v${process.versions.node}`,
|
||||||
|
fix: "Install Node 20 or newer (https://nodejs.org)",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkClaudeOnPath(): Check {
|
||||||
|
const res =
|
||||||
|
platform() === "win32"
|
||||||
|
? spawnSync("where", ["claude"])
|
||||||
|
: spawnSync("sh", ["-c", "command -v claude"]);
|
||||||
|
const onPath = res.status === 0;
|
||||||
|
const location = onPath ? res.stdout.toString().trim().split("\n")[0] : undefined;
|
||||||
|
return {
|
||||||
|
name: "claude binary on PATH",
|
||||||
|
pass: onPath,
|
||||||
|
detail: location,
|
||||||
|
fix: "Install Claude Code (https://claude.com/claude-code)",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkMcpRegistered(): Check {
|
||||||
|
const claudeConfig = join(homedir(), ".claude.json");
|
||||||
|
if (!existsSync(claudeConfig)) {
|
||||||
|
return {
|
||||||
|
name: "claudemesh MCP registered in ~/.claude.json",
|
||||||
|
pass: false,
|
||||||
|
fix: "Run `claudemesh install`",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const cfg = JSON.parse(readFileSync(claudeConfig, "utf-8")) as {
|
||||||
|
mcpServers?: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
const registered = Boolean(cfg.mcpServers?.["claudemesh"]);
|
||||||
|
return {
|
||||||
|
name: "claudemesh MCP registered in ~/.claude.json",
|
||||||
|
pass: registered,
|
||||||
|
fix: registered ? undefined : "Run `claudemesh install`",
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
return {
|
||||||
|
name: "claudemesh MCP registered in ~/.claude.json",
|
||||||
|
pass: false,
|
||||||
|
detail: e instanceof Error ? e.message : String(e),
|
||||||
|
fix: "Check ~/.claude.json for JSON parse errors",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkHooksRegistered(): Check {
|
||||||
|
const settings = join(homedir(), ".claude", "settings.json");
|
||||||
|
if (!existsSync(settings)) {
|
||||||
|
return {
|
||||||
|
name: "Status hooks registered in ~/.claude/settings.json",
|
||||||
|
pass: false,
|
||||||
|
fix: "Run `claudemesh install` (remove --no-hooks)",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const raw = readFileSync(settings, "utf-8");
|
||||||
|
const has = raw.includes("claudemesh hook ");
|
||||||
|
return {
|
||||||
|
name: "Status hooks registered in ~/.claude/settings.json",
|
||||||
|
pass: has,
|
||||||
|
fix: has ? undefined : "Run `claudemesh install` (remove --no-hooks)",
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
return {
|
||||||
|
name: "Status hooks registered in ~/.claude/settings.json",
|
||||||
|
pass: false,
|
||||||
|
detail: e instanceof Error ? e.message : String(e),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkConfigFile(): Check {
|
||||||
|
const path = getConfigPath();
|
||||||
|
if (!existsSync(path)) {
|
||||||
|
return {
|
||||||
|
name: "~/.claudemesh/config.json exists and parses",
|
||||||
|
pass: true,
|
||||||
|
detail: "not created yet (fine — no meshes joined)",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
loadConfig();
|
||||||
|
const st = statSync(path);
|
||||||
|
const mode = (st.mode & 0o777).toString(8);
|
||||||
|
const secure = platform() === "win32" || mode === "600";
|
||||||
|
return {
|
||||||
|
name: "~/.claudemesh/config.json parses + chmod 0600",
|
||||||
|
pass: secure,
|
||||||
|
detail: platform() === "win32" ? "chmod skipped on Windows" : `0${mode}`,
|
||||||
|
fix: secure ? undefined : `chmod 600 ${path}`,
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
return {
|
||||||
|
name: "~/.claudemesh/config.json exists and parses",
|
||||||
|
pass: false,
|
||||||
|
detail: e instanceof Error ? e.message : String(e),
|
||||||
|
fix: "Inspect or delete ~/.claudemesh/config.json and re-join",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkKeypairs(): Check {
|
||||||
|
try {
|
||||||
|
const cfg = loadConfig();
|
||||||
|
if (cfg.meshes.length === 0) {
|
||||||
|
return {
|
||||||
|
name: "Mesh keypairs valid",
|
||||||
|
pass: true,
|
||||||
|
detail: "no meshes joined",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
for (const m of cfg.meshes) {
|
||||||
|
if (m.pubkey.length !== 64 || !/^[0-9a-f]+$/.test(m.pubkey)) {
|
||||||
|
return {
|
||||||
|
name: "Mesh keypairs valid",
|
||||||
|
pass: false,
|
||||||
|
detail: `${m.slug}: pubkey malformed`,
|
||||||
|
fix: `Leave + re-join the mesh: claudemesh leave ${m.slug}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (m.secretKey.length !== 128 || !/^[0-9a-f]+$/.test(m.secretKey)) {
|
||||||
|
return {
|
||||||
|
name: "Mesh keypairs valid",
|
||||||
|
pass: false,
|
||||||
|
detail: `${m.slug}: secret key malformed`,
|
||||||
|
fix: `Leave + re-join the mesh: claudemesh leave ${m.slug}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
name: "Mesh keypairs valid",
|
||||||
|
pass: true,
|
||||||
|
detail: `${cfg.meshes.length} mesh(es)`,
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
return {
|
||||||
|
name: "Mesh keypairs valid",
|
||||||
|
pass: false,
|
||||||
|
detail: e instanceof Error ? e.message : String(e),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function runDoctor(): Promise<void> {
|
||||||
|
const useColor =
|
||||||
|
!process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
|
||||||
|
const dim = (s: string): string => (useColor ? `\x1b[2m${s}\x1b[22m` : s);
|
||||||
|
const green = (s: string): string => (useColor ? `\x1b[32m${s}\x1b[39m` : s);
|
||||||
|
const red = (s: string): string => (useColor ? `\x1b[31m${s}\x1b[39m` : s);
|
||||||
|
|
||||||
|
console.log(`claudemesh doctor (v${VERSION})`);
|
||||||
|
console.log("─".repeat(60));
|
||||||
|
|
||||||
|
const checks: Check[] = [
|
||||||
|
checkNode(),
|
||||||
|
checkClaudeOnPath(),
|
||||||
|
checkMcpRegistered(),
|
||||||
|
checkHooksRegistered(),
|
||||||
|
checkConfigFile(),
|
||||||
|
checkKeypairs(),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const c of checks) {
|
||||||
|
const mark = c.pass ? green("✓") : red("✗");
|
||||||
|
const detail = c.detail ? dim(` (${c.detail})`) : "";
|
||||||
|
console.log(`${mark} ${c.name}${detail}`);
|
||||||
|
if (!c.pass && c.fix) {
|
||||||
|
console.log(dim(` → ${c.fix}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const failing = checks.filter((c) => !c.pass);
|
||||||
|
console.log("");
|
||||||
|
if (failing.length === 0) {
|
||||||
|
console.log(green("All checks passed."));
|
||||||
|
process.exit(0);
|
||||||
|
} else {
|
||||||
|
console.log(red(`${failing.length} check(s) failed.`));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
103
apps/cli/src/commands/status.ts
Normal file
103
apps/cli/src/commands/status.ts
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
/**
|
||||||
|
* `claudemesh status` — one-shot health report.
|
||||||
|
*
|
||||||
|
* Reports CLI version, config path + permissions, each joined mesh
|
||||||
|
* with broker reachability (WS handshake probe). Exit 0 if every
|
||||||
|
* mesh's broker is reachable, 1 otherwise.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { statSync, existsSync } from "node:fs";
|
||||||
|
import WebSocket from "ws";
|
||||||
|
import { loadConfig, getConfigPath } from "../state/config";
|
||||||
|
import { VERSION } from "../version";
|
||||||
|
|
||||||
|
interface MeshStatus {
|
||||||
|
slug: string;
|
||||||
|
brokerUrl: string;
|
||||||
|
pubkey: string;
|
||||||
|
reachable: boolean;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function probeBroker(url: string, timeoutMs = 4000): Promise<{ ok: boolean; error?: string }> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const ws = new WebSocket(url);
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
try { ws.terminate(); } catch { /* noop */ }
|
||||||
|
resolve({ ok: false, error: "timeout" });
|
||||||
|
}, timeoutMs);
|
||||||
|
ws.on("open", () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
try { ws.close(); } catch { /* noop */ }
|
||||||
|
resolve({ ok: true });
|
||||||
|
});
|
||||||
|
ws.on("error", (err) => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
resolve({ ok: false, error: err.message });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function runStatus(): Promise<void> {
|
||||||
|
const useColor =
|
||||||
|
!process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
|
||||||
|
const dim = (s: string): string => (useColor ? `\x1b[2m${s}\x1b[22m` : s);
|
||||||
|
const green = (s: string): string => (useColor ? `\x1b[32m${s}\x1b[39m` : s);
|
||||||
|
const red = (s: string): string => (useColor ? `\x1b[31m${s}\x1b[39m` : s);
|
||||||
|
|
||||||
|
console.log(`claudemesh status (v${VERSION})`);
|
||||||
|
console.log("─".repeat(60));
|
||||||
|
|
||||||
|
const configPath = getConfigPath();
|
||||||
|
let configPerms = "missing";
|
||||||
|
if (existsSync(configPath)) {
|
||||||
|
const st = statSync(configPath);
|
||||||
|
const mode = (st.mode & 0o777).toString(8).padStart(4, "0");
|
||||||
|
configPerms = mode === "0600" ? `${mode} ✓` : `${mode} ⚠ (expected 0600)`;
|
||||||
|
}
|
||||||
|
console.log(`Config: ${configPath} (${configPerms})`);
|
||||||
|
|
||||||
|
const config = loadConfig();
|
||||||
|
if (config.meshes.length === 0) {
|
||||||
|
console.log("");
|
||||||
|
console.log(dim("No meshes joined. Run `claudemesh join <invite-url>` to get started."));
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("");
|
||||||
|
console.log(`Meshes (${config.meshes.length}):`);
|
||||||
|
|
||||||
|
const results: MeshStatus[] = [];
|
||||||
|
for (const m of config.meshes) {
|
||||||
|
process.stdout.write(` ${m.slug.padEnd(20)} probing ${m.brokerUrl}… `);
|
||||||
|
const probe = await probeBroker(m.brokerUrl);
|
||||||
|
results.push({
|
||||||
|
slug: m.slug,
|
||||||
|
brokerUrl: m.brokerUrl,
|
||||||
|
pubkey: m.pubkey,
|
||||||
|
reachable: probe.ok,
|
||||||
|
error: probe.error,
|
||||||
|
});
|
||||||
|
if (probe.ok) {
|
||||||
|
console.log(green("reachable"));
|
||||||
|
} else {
|
||||||
|
console.log(red(`unreachable (${probe.error})`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("");
|
||||||
|
for (const r of results) {
|
||||||
|
console.log(dim(` ${r.slug}: pubkey ${r.pubkey.slice(0, 16)}…`));
|
||||||
|
}
|
||||||
|
|
||||||
|
const allOk = results.every((r) => r.reachable);
|
||||||
|
console.log("");
|
||||||
|
if (allOk) {
|
||||||
|
console.log(green("All meshes reachable."));
|
||||||
|
process.exit(0);
|
||||||
|
} else {
|
||||||
|
const broken = results.filter((r) => !r.reachable).length;
|
||||||
|
console.log(red(`${broken} of ${results.length} mesh(es) unreachable.`));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
111
apps/cli/src/commands/welcome.ts
Normal file
111
apps/cli/src/commands/welcome.ts
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
/**
|
||||||
|
* Stateful welcome screen — shown when the user runs `claudemesh`
|
||||||
|
* with no arguments. Detects install state + joined meshes + prints
|
||||||
|
* the next action they should take.
|
||||||
|
*
|
||||||
|
* States, in priority order:
|
||||||
|
* 1. MCP not registered in ~/.claude.json → run install
|
||||||
|
* 2. Config dir exists but no meshes joined → run join
|
||||||
|
* 3. Meshes joined, all reachable → run launch
|
||||||
|
* 4. Meshes joined, broker unreachable → run status / doctor
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { existsSync, readFileSync } from "node:fs";
|
||||||
|
import { homedir } from "node:os";
|
||||||
|
import { join } from "node:path";
|
||||||
|
import { loadConfig } from "../state/config";
|
||||||
|
import { VERSION } from "../version";
|
||||||
|
|
||||||
|
type State = "no-install" | "no-meshes" | "ready" | "broken-config";
|
||||||
|
|
||||||
|
function detectState(): State {
|
||||||
|
// 1. MCP registered?
|
||||||
|
const claudeConfig = join(homedir(), ".claude.json");
|
||||||
|
let mcpRegistered = false;
|
||||||
|
if (existsSync(claudeConfig)) {
|
||||||
|
try {
|
||||||
|
const cfg = JSON.parse(readFileSync(claudeConfig, "utf-8")) as {
|
||||||
|
mcpServers?: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
mcpRegistered = Boolean(cfg.mcpServers?.["claudemesh"]);
|
||||||
|
} catch {
|
||||||
|
/* treat parse errors as not-registered */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!mcpRegistered) return "no-install";
|
||||||
|
|
||||||
|
// 2. Config parseable + has meshes?
|
||||||
|
try {
|
||||||
|
const cfg = loadConfig();
|
||||||
|
return cfg.meshes.length === 0 ? "no-meshes" : "ready";
|
||||||
|
} catch {
|
||||||
|
return "broken-config";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function runWelcome(): void {
|
||||||
|
const useColor =
|
||||||
|
!process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
|
||||||
|
const bold = (s: string): string => (useColor ? `\x1b[1m${s}\x1b[22m` : s);
|
||||||
|
const dim = (s: string): string => (useColor ? `\x1b[2m${s}\x1b[22m` : s);
|
||||||
|
const green = (s: string): string => (useColor ? `\x1b[32m${s}\x1b[39m` : s);
|
||||||
|
const yellow = (s: string): string => (useColor ? `\x1b[33m${s}\x1b[39m` : s);
|
||||||
|
|
||||||
|
console.log(bold(`claudemesh v${VERSION}`) + dim(" — peer mesh for Claude Code"));
|
||||||
|
console.log("─".repeat(60));
|
||||||
|
|
||||||
|
const state = detectState();
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
case "no-install":
|
||||||
|
console.log("Welcome. Let's get you set up.");
|
||||||
|
console.log("");
|
||||||
|
console.log(bold("Step 1:") + " register the MCP server + status hooks");
|
||||||
|
console.log(` ${green("$")} claudemesh install`);
|
||||||
|
console.log("");
|
||||||
|
console.log(dim("Step 2 (after restart): claudemesh join <invite-url>"));
|
||||||
|
console.log(dim("Step 3: claudemesh launch"));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "no-meshes":
|
||||||
|
console.log(green("✓") + " MCP registered. Now join a mesh.");
|
||||||
|
console.log("");
|
||||||
|
console.log(bold("Step 2:") + " join a mesh");
|
||||||
|
console.log(` ${green("$")} claudemesh join https://claudemesh.com/join/<token>`);
|
||||||
|
console.log("");
|
||||||
|
console.log(
|
||||||
|
dim(" Don't have an invite? Create one at ") +
|
||||||
|
bold("https://claudemesh.com") +
|
||||||
|
dim(" or ask a mesh owner."),
|
||||||
|
);
|
||||||
|
console.log("");
|
||||||
|
console.log(dim("Step 3 (after joining): claudemesh launch"));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "ready": {
|
||||||
|
const cfg = loadConfig();
|
||||||
|
const meshNames = cfg.meshes.map((m) => m.slug).join(", ");
|
||||||
|
console.log(green("✓") + " MCP registered.");
|
||||||
|
console.log(green("✓") + ` ${cfg.meshes.length} mesh(es) joined: ${meshNames}`);
|
||||||
|
console.log("");
|
||||||
|
console.log(bold("You're ready.") + " Launch Claude Code with real-time peer messages:");
|
||||||
|
console.log(` ${green("$")} claudemesh launch`);
|
||||||
|
console.log("");
|
||||||
|
console.log(dim(" (Plain `claude` works too — messages pull-only via check_messages.)"));
|
||||||
|
console.log("");
|
||||||
|
console.log(dim("Health check: claudemesh status"));
|
||||||
|
console.log(dim("Diagnostics: claudemesh doctor"));
|
||||||
|
console.log(dim("All commands: claudemesh --help"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "broken-config":
|
||||||
|
console.log(yellow("⚠") + " Your ~/.claudemesh/config.json is unreadable.");
|
||||||
|
console.log("");
|
||||||
|
console.log("Run diagnostics to see what's wrong:");
|
||||||
|
console.log(` ${green("$")} claudemesh doctor`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("");
|
||||||
|
}
|
||||||
@@ -16,8 +16,12 @@ import { runLeave } from "./commands/leave";
|
|||||||
import { runSeedTestMesh } from "./commands/seed-test-mesh";
|
import { runSeedTestMesh } from "./commands/seed-test-mesh";
|
||||||
import { runHook } from "./commands/hook";
|
import { runHook } from "./commands/hook";
|
||||||
import { runLaunch } from "./commands/launch";
|
import { runLaunch } from "./commands/launch";
|
||||||
|
import { runStatus } from "./commands/status";
|
||||||
|
import { runDoctor } from "./commands/doctor";
|
||||||
|
import { runWelcome } from "./commands/welcome";
|
||||||
|
import { VERSION } from "./version";
|
||||||
|
|
||||||
const HELP = `claudemesh — peer mesh for Claude Code sessions
|
const HELP = `claudemesh v${VERSION} — peer mesh for Claude Code sessions
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
claudemesh <command> [args]
|
claudemesh <command> [args]
|
||||||
@@ -32,9 +36,12 @@ Commands:
|
|||||||
join <url> Join a mesh via https://claudemesh.com/join/... URL
|
join <url> Join a mesh via https://claudemesh.com/join/... URL
|
||||||
list Show all joined meshes
|
list Show all joined meshes
|
||||||
leave <slug> Leave a joined mesh
|
leave <slug> Leave a joined mesh
|
||||||
|
status Health report: broker reachability per joined mesh
|
||||||
|
doctor Diagnostic checks (install, config, keypairs, PATH)
|
||||||
seed-test-mesh Dev-only: inject a mesh into config (skips invite flow)
|
seed-test-mesh Dev-only: inject a mesh into config (skips invite flow)
|
||||||
mcp Start MCP server (stdio) — invoked by Claude Code
|
mcp Start MCP server (stdio) — invoked by Claude Code
|
||||||
--help, -h Show this help
|
--help, -h Show this help
|
||||||
|
--version, -v Show the CLI version
|
||||||
|
|
||||||
Environment:
|
Environment:
|
||||||
CLAUDEMESH_BROKER_URL Override broker URL (default: wss://ic.claudemesh.com/ws)
|
CLAUDEMESH_BROKER_URL Override broker URL (default: wss://ic.claudemesh.com/ws)
|
||||||
@@ -71,15 +78,28 @@ async function main(): Promise<void> {
|
|||||||
case "leave":
|
case "leave":
|
||||||
runLeave(args);
|
runLeave(args);
|
||||||
return;
|
return;
|
||||||
|
case "status":
|
||||||
|
await runStatus();
|
||||||
|
return;
|
||||||
|
case "doctor":
|
||||||
|
await runDoctor();
|
||||||
|
return;
|
||||||
case "seed-test-mesh":
|
case "seed-test-mesh":
|
||||||
runSeedTestMesh(args);
|
runSeedTestMesh(args);
|
||||||
return;
|
return;
|
||||||
|
case "--version":
|
||||||
|
case "-v":
|
||||||
|
case "version":
|
||||||
|
console.log(VERSION);
|
||||||
|
return;
|
||||||
case "--help":
|
case "--help":
|
||||||
case "-h":
|
case "-h":
|
||||||
case "help":
|
case "help":
|
||||||
case undefined:
|
|
||||||
console.log(HELP);
|
console.log(HELP);
|
||||||
return;
|
return;
|
||||||
|
case undefined:
|
||||||
|
runWelcome();
|
||||||
|
return;
|
||||||
default:
|
default:
|
||||||
console.error(`Unknown command: ${cmd}`);
|
console.error(`Unknown command: ${cmd}`);
|
||||||
console.error("Run `claudemesh --help` for usage.");
|
console.error("Run `claudemesh --help` for usage.");
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export async function startMcpServer(): Promise<void> {
|
|||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
|
|
||||||
const server = new Server(
|
const server = new Server(
|
||||||
{ name: "claudemesh", version: "0.1.2" },
|
{ name: "claudemesh", version: "0.1.4" },
|
||||||
{
|
{
|
||||||
capabilities: {
|
capabilities: {
|
||||||
experimental: { "claude/channel": {} },
|
experimental: { "claude/channel": {} },
|
||||||
|
|||||||
8
apps/cli/src/version.ts
Normal file
8
apps/cli/src/version.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Bundled version string. Bun inlines the package.json JSON at build
|
||||||
|
* time, so the shipped binary carries the exact version that was
|
||||||
|
* published.
|
||||||
|
*/
|
||||||
|
import pkg from "../package.json" with { type: "json" };
|
||||||
|
|
||||||
|
export const VERSION: string = pkg.version;
|
||||||
@@ -3,6 +3,12 @@ import { useState } from "react";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
const NEWS = [
|
const NEWS = [
|
||||||
|
{
|
||||||
|
tag: "New",
|
||||||
|
title: "claudemesh launch (v0.1.2)",
|
||||||
|
body: "Real-time peer messages pushed into Claude Code mid-turn. One command. Source open at github.com/alezmad/claudemesh-cli.",
|
||||||
|
href: "https://github.com/alezmad/claudemesh-cli",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
tag: "Beta",
|
tag: "Beta",
|
||||||
title: "Mesh Dashboard",
|
title: "Mesh Dashboard",
|
||||||
|
|||||||
Reference in New Issue
Block a user