Files
claudemesh/apps/cli/src/commands/new.ts
Alejandro Gutiérrez b4f457fceb
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
feat(cli): 1.5.0 — CLI-first architecture, tool-less MCP, policy engine
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>
2026-05-02 01:18:19 +01:00

57 lines
1.6 KiB
TypeScript

import { create as createMesh } from "~/services/mesh/facade.js";
import { getStoredToken } from "~/services/auth/facade.js";
import { render } from "~/ui/render.js";
import { bold, clay, dim } from "~/ui/styles.js";
import { EXIT } from "~/constants/exit-codes.js";
export async function newMesh(
name: string,
opts: { template?: string; description?: string; json?: boolean },
): Promise<number> {
if (!name) {
render.err("Usage: claudemesh create <name>");
return EXIT.INVALID_ARGS;
}
if (!getStoredToken()) {
render.info(dim("not signed in — starting login…"));
render.blank();
const { login } = await import("./login.js");
const loginResult = await login();
if (loginResult !== EXIT.SUCCESS) return loginResult;
render.blank();
}
try {
const result = await createMesh(name, {
template: opts.template,
description: opts.description,
});
if (opts.json) {
console.log(JSON.stringify({ schema_version: "1.0", ...result }, null, 2));
return EXIT.SUCCESS;
}
render.section(`created ${bold(result.slug)}`);
render.kv([
["id", dim(result.id)],
["role", clay("owner")],
["local", "joined"],
]);
render.blank();
render.hint(`share with: ${bold("claudemesh share")}`);
render.blank();
return EXIT.SUCCESS;
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
if (msg.includes("409") || msg.includes("already exists")) {
render.err("A mesh with this name already exists.", "Try a different name.");
} else {
render.err(`Failed: ${msg}`);
}
return EXIT.INTERNAL_ERROR;
}
}