3 Commits

Author SHA1 Message Date
Alejandro Gutiérrez
a987e9e27b fix(cli): v0.1.14 — persist displayName in config file, not env var
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
Write displayName into tmpdir config.json so the MCP server reads
it directly. Env vars from claudemesh launch may not propagate to
MCP child processes spawned by Claude Code. Config file is reliable.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 12:18:08 +01:00
Alejandro Gutiérrez
ff86db615f style(cli): tighten autonomous mode confirmation copy
Some checks failed
CI / Docker build (linux/amd64) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Broker tests (Postgres) (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 11:54:55 +01:00
Alejandro Gutiérrez
4aa61b40e2 feat(cli): v0.1.13 — autonomous mode with user confirmation
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
claudemesh launch now passes --dangerously-skip-permissions to
claude so peers can chat without per-tool-call approval prompts.
Shows a clear explanation before launch; user confirms with Enter.
Skip with -y/--yes for CI or repeat launches.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 11:53:13 +01:00
5 changed files with 62 additions and 11 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "claudemesh-cli", "name": "claudemesh-cli",
"version": "0.1.12", "version": "0.1.14",
"description": "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.", "description": "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.",
"keywords": [ "keywords": [
"claude-code", "claude-code",

View File

@@ -25,6 +25,7 @@ interface LaunchArgs {
joinLink: string | null; joinLink: string | null;
meshSlug: string | null; meshSlug: string | null;
quiet: boolean; quiet: boolean;
skipPermConfirm: boolean;
claudeArgs: string[]; claudeArgs: string[];
} }
@@ -34,6 +35,7 @@ function parseArgs(argv: string[]): LaunchArgs {
joinLink: null, joinLink: null,
meshSlug: null, meshSlug: null,
quiet: false, quiet: false,
skipPermConfirm: false,
claudeArgs: [], claudeArgs: [],
}; };
@@ -54,6 +56,8 @@ function parseArgs(argv: string[]): LaunchArgs {
result.meshSlug = arg.slice("--mesh=".length); result.meshSlug = arg.slice("--mesh=".length);
} else if (arg === "--quiet") { } else if (arg === "--quiet") {
result.quiet = true; result.quiet = true;
} else if (arg === "-y" || arg === "--yes") {
result.skipPermConfirm = true;
} else if (arg === "--") { } else if (arg === "--") {
result.claudeArgs.push(...argv.slice(i + 1)); result.claudeArgs.push(...argv.slice(i + 1));
break; break;
@@ -91,6 +95,41 @@ async function pickMesh(meshes: JoinedMesh[]): Promise<JoinedMesh> {
}); });
} }
// --- Permission confirmation ---
async function confirmPermissions(): Promise<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 yellow = (s: string): string => (useColor ? `\x1b[33m${s}\x1b[39m` : s);
console.log(yellow(bold(" Autonomous mode")));
console.log("");
console.log(" Claude will send and receive peer messages without asking");
console.log(" you first. Peers exchange text only — no file access,");
console.log(" no tool calls, no code execution.");
console.log("");
console.log(dim(" Same as: claude --dangerously-skip-permissions"));
console.log(dim(" Skip this prompt: claudemesh launch -y"));
console.log("");
const rl = createInterface({ input: process.stdin, output: process.stdout });
return new Promise((resolve, reject) => {
rl.question(` ${bold("Continue?")} [Y/n] `, (answer) => {
rl.close();
const a = answer.trim().toLowerCase();
if (a === "" || a === "y" || a === "yes") {
resolve();
} else {
console.log("\n Aborted. Run without autonomous mode:");
console.log(" claude --dangerously-load-development-channels server:claudemesh\n");
process.exit(0);
}
});
});
}
// --- Banner --- // --- Banner ---
function printBanner(name: string, meshSlug: string): void { function printBanner(name: string, meshSlug: string): void {
@@ -181,6 +220,7 @@ export async function runLaunch(extraArgs: string[]): Promise<void> {
const sessionConfig: Config = { const sessionConfig: Config = {
version: 1, version: 1,
meshes: [mesh], meshes: [mesh],
displayName,
}; };
writeFileSync( writeFileSync(
join(tmpDir, "config.json"), join(tmpDir, "config.json"),
@@ -188,16 +228,22 @@ export async function runLaunch(extraArgs: string[]): Promise<void> {
"utf-8", "utf-8",
); );
// 5. Banner. // 5. Banner + permission confirmation.
if (!args.quiet) printBanner(displayName, mesh.slug); if (!args.quiet) {
printBanner(displayName, mesh.slug);
// Auto-permissions confirmation — needed for autonomous peer messaging.
if (!args.skipPermConfirm) {
await confirmPermissions();
}
}
// 6. Spawn claude with ephemeral config + dev channel + display name. // 6. Spawn claude with ephemeral config + dev channel + auto-permissions.
// Strip any user-supplied --dangerously-load-development-channels // Strip any user-supplied --dangerously flags to avoid duplicates.
// to avoid duplicates — we always inject our own.
const filtered: string[] = []; const filtered: string[] = [];
for (let i = 0; i < args.claudeArgs.length; i++) { for (let i = 0; i < args.claudeArgs.length; i++) {
if (args.claudeArgs[i] === "--dangerously-load-development-channels") { if (args.claudeArgs[i] === "--dangerously-load-development-channels"
i++; // skip the next arg (the channel value) too || args.claudeArgs[i] === "--dangerously-skip-permissions") {
if (args.claudeArgs[i] === "--dangerously-load-development-channels") i++;
continue; continue;
} }
filtered.push(args.claudeArgs[i]!); filtered.push(args.claudeArgs[i]!);
@@ -205,6 +251,7 @@ export async function runLaunch(extraArgs: string[]): Promise<void> {
const claudeArgs = [ const claudeArgs = [
"--dangerously-load-development-channels", "--dangerously-load-development-channels",
"server:claudemesh", "server:claudemesh",
"--dangerously-skip-permissions",
...filtered, ...filtered,
]; ];

View File

@@ -31,6 +31,7 @@ export interface JoinedMesh {
export interface Config { export interface Config {
version: 1; version: 1;
meshes: JoinedMesh[]; meshes: JoinedMesh[];
displayName?: string; // per-session override, written by `claudemesh launch --name`
} }
const CONFIG_DIR = env.CLAUDEMESH_CONFIG_DIR ?? join(homedir(), ".claudemesh"); const CONFIG_DIR = env.CLAUDEMESH_CONFIG_DIR ?? join(homedir(), ".claudemesh");
@@ -46,7 +47,7 @@ export function loadConfig(): Config {
if (!parsed || !Array.isArray(parsed.meshes)) { if (!parsed || !Array.isArray(parsed.meshes)) {
return { version: 1, meshes: [] }; return { version: 1, meshes: [] };
} }
return { version: 1, meshes: parsed.meshes }; return { version: 1, meshes: parsed.meshes, displayName: parsed.displayName };
} catch (e) { } catch (e) {
throw new Error( throw new Error(
`Failed to load ${CONFIG_PATH}: ${e instanceof Error ? e.message : String(e)}`, `Failed to load ${CONFIG_PATH}: ${e instanceof Error ? e.message : String(e)}`,

View File

@@ -86,6 +86,7 @@ export class BrokerClient {
private mesh: JoinedMesh, private mesh: JoinedMesh,
private opts: { private opts: {
onStatusChange?: (status: ConnStatus) => void; onStatusChange?: (status: ConnStatus) => void;
displayName?: string;
debug?: boolean; debug?: boolean;
} = {}, } = {},
) {} ) {}
@@ -132,7 +133,7 @@ export class BrokerClient {
memberId: this.mesh.memberId, memberId: this.mesh.memberId,
pubkey: this.mesh.pubkey, pubkey: this.mesh.pubkey,
sessionPubkey: this.sessionPubkey, sessionPubkey: this.sessionPubkey,
displayName: process.env.CLAUDEMESH_DISPLAY_NAME || undefined, displayName: process.env.CLAUDEMESH_DISPLAY_NAME || this.opts.displayName || undefined,
sessionId: `${process.pid}-${Date.now()}`, sessionId: `${process.pid}-${Date.now()}`,
pid: process.pid, pid: process.pid,
cwd: process.cwd(), cwd: process.cwd(),

View File

@@ -11,12 +11,13 @@ import type { Config, JoinedMesh } from "../state/config";
import { env } from "../env"; import { env } from "../env";
const clients = new Map<string, BrokerClient>(); const clients = new Map<string, BrokerClient>();
let configDisplayName: string | undefined;
/** Ensure a BrokerClient exists + is connecting/open for this mesh. */ /** Ensure a BrokerClient exists + is connecting/open for this mesh. */
export async function ensureClient(mesh: JoinedMesh): Promise<BrokerClient> { export async function ensureClient(mesh: JoinedMesh): Promise<BrokerClient> {
const existing = clients.get(mesh.meshId); const existing = clients.get(mesh.meshId);
if (existing) return existing; if (existing) return existing;
const client = new BrokerClient(mesh, { debug: env.CLAUDEMESH_DEBUG }); const client = new BrokerClient(mesh, { debug: env.CLAUDEMESH_DEBUG, displayName: configDisplayName });
clients.set(mesh.meshId, client); clients.set(mesh.meshId, client);
try { try {
await client.connect(); await client.connect();
@@ -29,6 +30,7 @@ export async function ensureClient(mesh: JoinedMesh): Promise<BrokerClient> {
/** Start clients for every joined mesh. Called once on MCP server start. */ /** Start clients for every joined mesh. Called once on MCP server start. */
export async function startClients(config: Config): Promise<void> { export async function startClients(config: Config): Promise<void> {
configDisplayName = config.displayName;
await Promise.allSettled(config.meshes.map(ensureClient)); await Promise.allSettled(config.meshes.map(ensureClient));
} }