From f698aaeac79089530a969d824e2f5ea75e476d2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Guti=C3=A9rrez?= <35082514+alezmad@users.noreply.github.com> Date: Sun, 5 Apr 2026 23:19:27 +0100 Subject: [PATCH] feat(cli): stateful welcome screen + v0.1.4 bump Running \`claudemesh\` with no args now detects install state and prints context-appropriate guidance: suggests \`install\` if MCP not registered, \`join\` if no meshes, \`launch\` if ready. Replaces the static HELP dump with a first-run wizard that meets users where they are. Static HELP still available via --help. Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/cli/package.json | 2 +- apps/cli/src/commands/welcome.ts | 111 +++++++++++++++++++++++++++++++ apps/cli/src/index.ts | 5 +- apps/cli/src/mcp/server.ts | 2 +- 4 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 apps/cli/src/commands/welcome.ts diff --git a/apps/cli/package.json b/apps/cli/package.json index 022c661..a4c5923 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,6 +1,6 @@ { "name": "claudemesh-cli", - "version": "0.1.3", + "version": "0.1.4", "description": "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.", "keywords": [ "claude-code", diff --git a/apps/cli/src/commands/welcome.ts b/apps/cli/src/commands/welcome.ts new file mode 100644 index 0000000..60be03c --- /dev/null +++ b/apps/cli/src/commands/welcome.ts @@ -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; + }; + 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 ")); + 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/`); + 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(""); +} diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts index e2c80f9..8954781 100644 --- a/apps/cli/src/index.ts +++ b/apps/cli/src/index.ts @@ -18,6 +18,7 @@ import { runHook } from "./commands/hook"; 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 v${VERSION} — peer mesh for Claude Code sessions @@ -94,9 +95,11 @@ async function main(): Promise { case "--help": case "-h": case "help": - case undefined: console.log(HELP); return; + case undefined: + runWelcome(); + return; default: console.error(`Unknown command: ${cmd}`); console.error("Run `claudemesh --help` for usage."); diff --git a/apps/cli/src/mcp/server.ts b/apps/cli/src/mcp/server.ts index 8eafa96..f16d5cb 100644 --- a/apps/cli/src/mcp/server.ts +++ b/apps/cli/src/mcp/server.ts @@ -87,7 +87,7 @@ export async function startMcpServer(): Promise { const config = loadConfig(); const server = new Server( - { name: "claudemesh", version: "0.1.3" }, + { name: "claudemesh", version: "0.1.4" }, { capabilities: { experimental: { "claude/channel": {} },