3 Commits

Author SHA1 Message Date
Alejandro Gutiérrez
ab08be04a5 feat(cli): v0.5.8 — welcome notification on connect
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
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 18:07:08 +01:00
Alejandro Gutiérrez
ee585a8370 fix(cli): v0.5.7 — event loop keepalive for stdout flush
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
Node.js stdout to a pipe is buffered. Without periodic event loop
activity, WS callback → server.notification() → stdout.write() may
not flush until the next I/O event. A 1s setInterval (NOT unref'd)
keeps the event loop ticking so notifications flush immediately.

This is why claude-intercom worked: its 1s HTTP poll kept the event
loop active as a side effect. Claudemesh's passive WS listener let
the event loop settle, causing stdout to buffer indefinitely.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 17:48:41 +01:00
Alejandro Gutiérrez
1f078bf0c8 fix(web): --no-turbopack for prod build (payload CSS)
Some checks failed
CI / Typecheck (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Broker tests (Postgres) (push) Has been cancelled
CI / Docker build (linux/amd64) (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 17:32:24 +01:00
4 changed files with 50 additions and 9 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "claudemesh-cli", "name": "claudemesh-cli",
"version": "0.5.6", "version": "0.5.8",
"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

@@ -445,11 +445,14 @@ Your message mode is "${messageMode}".
if (!existsSync(filePath)) return text(`share_file: file not found: ${filePath}`, true); if (!existsSync(filePath)) return text(`share_file: file not found: ${filePath}`, true);
const client = allClients()[0]; const client = allClients()[0];
if (!client) return text("share_file: not connected", true); if (!client) return text("share_file: not connected", true);
const fileId = await client.uploadFile(filePath, client.meshId, client.meshSlug, { try {
name: fileName, tags, persistent: true, const fileId = await client.uploadFile(filePath, client.meshId, client.meshSlug, {
}); name: fileName, tags, persistent: true,
if (!fileId) return text("share_file: upload failed", true); });
return text(`Shared: ${fileName ?? filePath} (${fileId})`); return text(`Shared: ${fileName ?? filePath} (${fileId})`);
} catch (e) {
return text(`share_file: upload failed — ${e instanceof Error ? e.message : String(e)}`, true);
}
} }
case "get_file": { case "get_file": {
@@ -856,7 +859,42 @@ Your message mode is "${messageMode}".
}); });
} }
// Welcome notification: give Claude immediate context on connect.
// Triggers Claude to call mesh_info/list_peers without user input.
setTimeout(async () => {
const client = allClients()[0];
if (!client || client.status !== "open") return;
try {
const peers = await client.listPeers();
const peerNames = peers
.filter(p => p.displayName !== myName)
.map(p => p.displayName)
.join(", ") || "none";
await server.notification({
method: "notifications/claude/channel",
params: {
content: `[system] Connected as ${myName} to mesh ${client.meshSlug}. ${peers.length} peer(s) online: ${peerNames}. Call mesh_info for full details or set_summary to announce yourself.`,
meta: { kind: "welcome", mesh_slug: client.meshSlug },
},
});
} catch { /* best effort */ }
}, 3_000); // 3s delay: let WS connect + hello_ack complete first
// Event loop keepalive: Node.js stdout to a pipe is buffered. Without
// periodic event loop activity, stdout.write() from WS callbacks may not
// flush until the next I/O event. This 1s interval keeps the event loop
// ticking so channel notifications flush promptly — same pattern that made
// claude-intercom's push delivery reliable (its 1s HTTP poll had this
// effect as a side effect). The interval does nothing except prevent the
// event loop from settling.
const keepalive = setInterval(() => {
// Intentionally empty — the interval itself keeps the event loop active.
// Do NOT call .unref() — that would defeat the purpose.
}, 1_000);
void keepalive; // suppress unused warning
const shutdown = (): void => { const shutdown = (): void => {
clearInterval(keepalive);
stopAll(); stopAll();
process.exit(0); process.exit(0);
}; };

View File

@@ -526,8 +526,11 @@ export class BrokerClient {
body: data, body: data,
signal: AbortSignal.timeout(30_000), signal: AbortSignal.timeout(30_000),
}); });
const body = await res.json() as { ok?: boolean; fileId?: string }; const body = await res.json() as { ok?: boolean; fileId?: string; error?: string };
return body.fileId ?? null; if (!res.ok || !body.fileId) {
throw new Error(body.error ?? `HTTP ${res.status}`);
}
return body.fileId;
} }
// --- Vectors --- // --- Vectors ---

View File

@@ -4,7 +4,7 @@
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
"build": "next build", "build": "next build --no-turbopack",
"clean": "git clean -xdf .cache .next .turbo node_modules", "clean": "git clean -xdf .cache .next .turbo node_modules",
"dev": "next dev", "dev": "next dev",
"format": "prettier --check . --ignore-path ../../.gitignore", "format": "prettier --check . --ignore-path ../../.gitignore",