feat(cli): 1.5.0 — CLI-first architecture, tool-less MCP, policy engine
Some checks failed
CI / Lint (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Broker tests (Postgres) (push) Has been cancelled
CI / Docker build (linux/amd64) (push) Has been cancelled

CLI becomes the API; MCP becomes a tool-less push-pipe. Bundle -42%
(250 KB → 146 KB) after stripping ~1700 lines of dead tool handlers.

- Tool-less MCP: tools/list returns []. Inbound peer messages still
  arrive as experimental.claude/channel notifications mid-turn.
- Resource-noun-verb CLI: peer list, message send, memory recall, etc.
  Legacy flat verbs (peers, send, remember) remain as aliases.
- Bundled claudemesh skill auto-installed by `claudemesh install` —
  sole CLI-discoverability surface for Claude.
- Unix-socket bridge: CLI invocations dial the push-pipe's warm WS
  (~220 ms warm vs ~600 ms cold).
- --mesh <slug> flag: connect a session to multiple meshes.
- Policy engine: every broker-touching verb runs through a YAML gate
  at ~/.claudemesh/policy.yaml (auto-created). Destructive verbs
  prompt; non-TTY auto-denies. Audit log at ~/.claudemesh/audit.log.
- --approval-mode plan|read-only|write|yolo + --policy <path>.

Spec: .artifacts/specs/2026-05-02-architecture-north-star.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-05-02 01:18:19 +01:00
parent ff551ccf3d
commit b4f457fceb
36 changed files with 3636 additions and 2833 deletions

View File

@@ -1,12 +1,25 @@
import { readFileSync, writeFileSync, existsSync } from "node:fs";
import { readFileSync, writeFileSync, existsSync, rmSync, readdirSync } from "node:fs";
import { join, dirname } from "node:path";
import { homedir } from "node:os";
import { fileURLToPath } from "node:url";
import { PATHS } from "~/constants/paths.js";
import { green, icons } from "~/ui/styles.js";
import { render } from "~/ui/render.js";
import { dim } from "~/ui/styles.js";
import { EXIT } from "~/constants/exit-codes.js";
const CLAUDE_SKILLS_ROOT = join(homedir(), ".claude", "skills");
/** Locate the bundled `skills/` directory shipped with this package. */
function bundledSkillsDir(): string | null {
const here = fileURLToPath(import.meta.url);
const pkgRoot = join(dirname(here), "..", "..");
const skillsDir = join(pkgRoot, "skills");
return existsSync(skillsDir) ? skillsDir : null;
}
export async function uninstall(): Promise<number> {
let removed = 0;
// Remove MCP server from ~/.claude.json
if (existsSync(PATHS.CLAUDE_JSON)) {
try {
const raw = readFileSync(PATHS.CLAUDE_JSON, "utf-8");
@@ -15,13 +28,12 @@ export async function uninstall(): Promise<number> {
if (servers && "claudemesh" in servers) {
delete servers.claudemesh;
writeFileSync(PATHS.CLAUDE_JSON, JSON.stringify(config, null, 2) + "\n", "utf-8");
console.log(` ${green(icons.check)} Removed MCP server from ~/.claude.json`);
render.ok("removed MCP server", dim("~/.claude.json"));
removed++;
}
} catch {}
}
// Remove only claudemesh hooks from ~/.claude/settings.json
if (existsSync(PATHS.CLAUDE_SETTINGS)) {
try {
const raw = readFileSync(PATHS.CLAUDE_SETTINGS, "utf-8");
@@ -43,15 +55,40 @@ export async function uninstall(): Promise<number> {
}
if (removedHooks > 0) {
writeFileSync(PATHS.CLAUDE_SETTINGS, JSON.stringify(config, null, 2) + "\n", "utf-8");
console.log(` ${green(icons.check)} Removed ${removedHooks} claudemesh hook(s) from settings.json`);
render.ok(`removed ${removedHooks} claudemesh hook${removedHooks === 1 ? "" : "s"}`, dim("settings.json"));
removed++;
}
}
} catch {}
}
// Skills shipped by claudemesh install — remove from ~/.claude/skills/.
const src = bundledSkillsDir();
if (src) {
const removedSkills: string[] = [];
try {
for (const entry of readdirSync(src, { withFileTypes: true })) {
if (!entry.isDirectory()) continue;
const dst = join(CLAUDE_SKILLS_ROOT, entry.name);
if (existsSync(dst)) {
try {
rmSync(dst, { recursive: true, force: true });
removedSkills.push(entry.name);
} catch { /* best effort */ }
}
}
if (removedSkills.length > 0) {
render.ok(
`removed Claude skill${removedSkills.length === 1 ? "" : "s"}`,
removedSkills.join(", "),
);
removed++;
}
} catch { /* best effort */ }
}
if (removed === 0) {
console.log(" Nothing to remove — claudemesh was not installed.");
render.info(dim("Nothing to remove — claudemesh was not installed."));
}
return EXIT.SUCCESS;