Files
claudemesh/apps/cli/src/state/config.ts
Alejandro Gutiérrez 02b1e5695f
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
Release / Publish multi-arch images (push) Has been cancelled
feat: v0.2.0 — Groups (@group routing, roles, wizard)
Phase A of the claudemesh spec. Peers can now join named groups
with roles, and messages route to @group targets.

Broker:
- @group routing in fan-out (matches peer group membership)
- @all alias for broadcast
- join_group/leave_group WS messages + DB persistence
- list_peers returns group metadata
- drainForMember matches @group targetSpecs in SQL

CLI:
- join_group/leave_group MCP tools
- send_message supports @group targets
- list_peers shows group membership
- PeerInfo includes groups array
- Peer name cache for push notifications

Launch:
- --role flag (optional peer role)
- --groups flag (comma-separated, e.g. "frontend:lead,reviewers")
- Interactive wizard for role + groups when flags omitted
- Groups written to session config for broker hello

Spec: SPEC.md added with full v0.2 vision (groups, state, memory)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 13:06:16 +01:00

78 lines
2.0 KiB
TypeScript

/**
* Local persistent config — ~/.claudemesh/config.json
*
* Stores: joined meshes, per-mesh identity keys (ed25519 keypairs),
* last-seen broker URL. Loaded on CLI start, on MCP server start,
* and on every join/leave.
*/
import {
readFileSync,
writeFileSync,
existsSync,
mkdirSync,
chmodSync,
} from "node:fs";
import { homedir } from "node:os";
import { join, dirname } from "node:path";
import { env } from "../env";
export interface JoinedMesh {
meshId: string;
memberId: string;
slug: string;
name: string;
pubkey: string; // ed25519 hex (32 bytes = 64 chars)
secretKey: string; // ed25519 hex (64 bytes = 128 chars)
brokerUrl: string;
joinedAt: string;
}
export interface GroupEntry {
name: string;
role?: string;
}
export interface Config {
version: 1;
meshes: JoinedMesh[];
displayName?: string; // per-session override, written by `claudemesh launch --name`
groups?: GroupEntry[];
}
const CONFIG_DIR = env.CLAUDEMESH_CONFIG_DIR ?? join(homedir(), ".claudemesh");
const CONFIG_PATH = join(CONFIG_DIR, "config.json");
export function loadConfig(): Config {
if (!existsSync(CONFIG_PATH)) {
return { version: 1, meshes: [] };
}
try {
const raw = readFileSync(CONFIG_PATH, "utf-8");
const parsed = JSON.parse(raw);
if (!parsed || !Array.isArray(parsed.meshes)) {
return { version: 1, meshes: [] };
}
return { version: 1, meshes: parsed.meshes, displayName: parsed.displayName, groups: parsed.groups };
} catch (e) {
throw new Error(
`Failed to load ${CONFIG_PATH}: ${e instanceof Error ? e.message : String(e)}`,
);
}
}
export function saveConfig(config: Config): void {
mkdirSync(dirname(CONFIG_PATH), { recursive: true });
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
// Config holds ed25519 secret keys — restrict to owner read/write.
try {
chmodSync(CONFIG_PATH, 0o600);
} catch {
// Windows filesystems ignore chmod; that's fine.
}
}
export function getConfigPath(): string {
return CONFIG_PATH;
}