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

@@ -0,0 +1,331 @@
/**
* Small broker-side action verbs that previously lived only as MCP tools.
*
* These are the CLI replacements for the soft-deprecated tools
* (set_status / set_summary / set_visible / set_profile / join_group /
* leave_group / forget / message_status / mesh_clock / mesh_stats /
* ping_mesh / claim_task / complete_task).
*
* Each verb runs against ONE mesh — pick with --mesh <slug>, or let the
* picker prompt when multiple meshes are joined. This is the deliberate
* difference from the MCP tools' fan-out-across-all-meshes behavior:
* the CLI invocation model binds one connection per call.
*
* Spec: .artifacts/specs/2026-05-01-mcp-tool-surface-trim.md
*/
import { withMesh } from "./connect.js";
import { readConfig } from "~/services/config/facade.js";
import { tryBridge } from "~/services/bridge/client.js";
import { render } from "~/ui/render.js";
import { bold, clay, dim } from "~/ui/styles.js";
import { EXIT } from "~/constants/exit-codes.js";
type StateFlags = { mesh?: string; json?: boolean };
type PeerStatus = "idle" | "working" | "dnd";
/** Resolve unambiguous mesh slug for warm-path bridging. Returns null if
* the user has multiple joined meshes and didn't pick one. */
function unambiguousMesh(opts: StateFlags): string | null {
if (opts.mesh) return opts.mesh;
const config = readConfig();
return config.meshes.length === 1 ? config.meshes[0]!.slug : null;
}
// --- status ---
export async function runStatusSet(state: string, opts: StateFlags): Promise<number> {
const valid: PeerStatus[] = ["idle", "working", "dnd"];
if (!valid.includes(state as PeerStatus)) {
render.err(`Invalid status: ${state}`, `must be one of: ${valid.join(", ")}`);
return EXIT.INVALID_ARGS;
}
// Warm path
const meshSlug = unambiguousMesh(opts);
if (meshSlug) {
const bridged = await tryBridge(meshSlug, "status_set", { status: state });
if (bridged !== null) {
if (bridged.ok) {
if (opts.json) console.log(JSON.stringify({ status: state }));
else render.ok(`status set to ${bold(state)}`);
return EXIT.SUCCESS;
}
render.err(bridged.error);
return EXIT.INTERNAL_ERROR;
}
}
await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
await client.setStatus(state as PeerStatus);
});
if (opts.json) console.log(JSON.stringify({ status: state }));
else render.ok(`status set to ${bold(state)}`);
return EXIT.SUCCESS;
}
// --- summary ---
export async function runSummary(text: string, opts: StateFlags): Promise<number> {
if (!text) {
render.err("Usage: claudemesh summary <text>");
return EXIT.INVALID_ARGS;
}
// Warm path
const meshSlug = unambiguousMesh(opts);
if (meshSlug) {
const bridged = await tryBridge(meshSlug, "summary", { summary: text });
if (bridged !== null) {
if (bridged.ok) {
if (opts.json) console.log(JSON.stringify({ summary: text }));
else render.ok("summary set", dim(text));
return EXIT.SUCCESS;
}
render.err(bridged.error);
return EXIT.INTERNAL_ERROR;
}
}
await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
await client.setSummary(text);
});
if (opts.json) console.log(JSON.stringify({ summary: text }));
else render.ok("summary set", dim(text));
return EXIT.SUCCESS;
}
// --- visible ---
export async function runVisible(value: string | undefined, opts: StateFlags): Promise<number> {
let visible: boolean;
if (value === "true" || value === "1" || value === "yes") visible = true;
else if (value === "false" || value === "0" || value === "no") visible = false;
else {
render.err("Usage: claudemesh visible <true|false>");
return EXIT.INVALID_ARGS;
}
// Warm path
const meshSlug = unambiguousMesh(opts);
if (meshSlug) {
const bridged = await tryBridge(meshSlug, "visible", { visible });
if (bridged !== null) {
if (bridged.ok) {
if (opts.json) console.log(JSON.stringify({ visible }));
else render.ok(visible ? "you are now visible to peers" : "you are now hidden", visible ? undefined : "direct messages still reach you");
return EXIT.SUCCESS;
}
render.err(bridged.error);
return EXIT.INTERNAL_ERROR;
}
}
await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
await client.setVisible(visible);
});
if (opts.json) console.log(JSON.stringify({ visible }));
else render.ok(visible ? "you are now visible to peers" : "you are now hidden", visible ? undefined : "direct messages still reach you");
return EXIT.SUCCESS;
}
// --- group ---
export async function runGroupJoin(name: string | undefined, opts: StateFlags & { role?: string }): Promise<number> {
if (!name) {
render.err("Usage: claudemesh group join @<name> [--role X]");
return EXIT.INVALID_ARGS;
}
const cleanName = name.startsWith("@") ? name.slice(1) : name;
await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
await client.joinGroup(cleanName, opts.role);
});
if (opts.json) {
console.log(JSON.stringify({ group: cleanName, role: opts.role ?? null }));
return EXIT.SUCCESS;
}
render.ok(`joined ${clay("@" + cleanName)}`, opts.role ? `as ${opts.role}` : undefined);
return EXIT.SUCCESS;
}
export async function runGroupLeave(name: string | undefined, opts: StateFlags): Promise<number> {
if (!name) {
render.err("Usage: claudemesh group leave @<name>");
return EXIT.INVALID_ARGS;
}
const cleanName = name.startsWith("@") ? name.slice(1) : name;
await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
await client.leaveGroup(cleanName);
});
if (opts.json) {
console.log(JSON.stringify({ group: cleanName, left: true }));
return EXIT.SUCCESS;
}
render.ok(`left ${clay("@" + cleanName)}`);
return EXIT.SUCCESS;
}
// --- forget ---
export async function runForget(id: string | undefined, opts: StateFlags): Promise<number> {
if (!id) {
render.err("Usage: claudemesh forget <memory-id>");
return EXIT.INVALID_ARGS;
}
await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
await client.forget(id);
});
if (opts.json) {
console.log(JSON.stringify({ id, forgotten: true }));
return EXIT.SUCCESS;
}
render.ok(`forgot ${dim(id.slice(0, 8))}`);
return EXIT.SUCCESS;
}
// --- msg-status ---
export async function runMsgStatus(id: string | undefined, opts: StateFlags): Promise<number> {
if (!id) {
render.err("Usage: claudemesh msg-status <message-id>");
return EXIT.INVALID_ARGS;
}
return await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
const result = await client.messageStatus(id);
if (!result) {
if (opts.json) console.log(JSON.stringify({ id, found: false }));
else render.err(`Message ${id} not found or timed out.`);
return EXIT.NOT_FOUND;
}
if (opts.json) {
console.log(JSON.stringify(result, null, 2));
return EXIT.SUCCESS;
}
render.section(`message ${id.slice(0, 12)}`);
render.kv([
["target", result.targetSpec],
["delivered", result.delivered ? "yes" : "no"],
["delivered_at", result.deliveredAt ?? dim("—")],
]);
if (result.recipients.length > 0) {
render.blank();
render.heading("recipients");
for (const r of result.recipients) {
process.stdout.write(` ${bold(r.name)} ${dim(r.pubkey.slice(0, 12) + "…")} ${dim("·")} ${r.status}\n`);
}
}
return EXIT.SUCCESS;
});
}
// --- clock ---
export async function runClock(opts: StateFlags): Promise<number> {
return await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
const result = await client.getClock();
if (!result) {
if (opts.json) console.log(JSON.stringify({ error: "timed out" }));
else render.err("Clock query timed out");
return EXIT.INTERNAL_ERROR;
}
if (opts.json) {
console.log(JSON.stringify(result, null, 2));
return EXIT.SUCCESS;
}
const statusLabel = result.speed === 0 ? "not started" : result.paused ? "paused" : "running";
render.section(`mesh clock — ${statusLabel}`);
render.kv([
["speed", `x${result.speed}`],
["tick", String(result.tick)],
["sim_time", result.simTime],
["started_at", result.startedAt],
]);
return EXIT.SUCCESS;
});
}
// --- stats ---
export async function runStats(opts: StateFlags): Promise<number> {
return await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
const peers = await client.listPeers();
if (opts.json) {
console.log(JSON.stringify({
mesh: client.meshSlug,
peers: peers.map((p) => ({ name: p.displayName, pubkey: p.pubkey, stats: p.stats ?? null })),
}, null, 2));
return EXIT.SUCCESS;
}
render.section(client.meshSlug);
for (const p of peers) {
const s = p.stats;
if (!s) {
process.stdout.write(` ${bold(p.displayName)} ${dim("(no stats)")}\n`);
continue;
}
const up = s.uptime != null ? `${Math.floor(s.uptime / 60)}m` : "—";
process.stdout.write(
` ${bold(p.displayName)} ${dim(`in:${s.messagesIn ?? 0} out:${s.messagesOut ?? 0} tools:${s.toolCalls ?? 0} up:${up} err:${s.errors ?? 0}`)}\n`,
);
}
return EXIT.SUCCESS;
});
}
// --- ping ---
export async function runPing(opts: StateFlags): Promise<number> {
return await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
const peers = await client.listPeers();
if (opts.json) {
console.log(JSON.stringify({
mesh: client.meshSlug,
ws_status: client.status,
peers_online: peers.length,
push_buffer: client.pushHistory.length,
}, null, 2));
return EXIT.SUCCESS;
}
render.section(`ping ${client.meshSlug}`);
render.kv([
["ws_status", client.status],
["peers_online", String(peers.length)],
["push_buffer", String(client.pushHistory.length)],
]);
return EXIT.SUCCESS;
});
}
// --- task ---
export async function runTaskClaim(id: string | undefined, opts: StateFlags): Promise<number> {
if (!id) {
render.err("Usage: claudemesh task claim <id>");
return EXIT.INVALID_ARGS;
}
await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
await client.claimTask(id);
});
if (opts.json) {
console.log(JSON.stringify({ id, claimed: true }));
return EXIT.SUCCESS;
}
render.ok(`claimed ${dim(id.slice(0, 8))}`);
return EXIT.SUCCESS;
}
export async function runTaskComplete(id: string | undefined, result: string | undefined, opts: StateFlags): Promise<number> {
if (!id) {
render.err("Usage: claudemesh task complete <id> [result]");
return EXIT.INVALID_ARGS;
}
await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
await client.completeTask(id, result);
});
if (opts.json) {
console.log(JSON.stringify({ id, completed: true, result: result ?? null }));
return EXIT.SUCCESS;
}
render.ok(`completed ${dim(id.slice(0, 8))}`, result);
return EXIT.SUCCESS;
}