feat(sdk+cli): bridge peer — forward a topic between two meshes
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

A bridge holds memberships in two meshes and relays messages on a
single topic between them. Federation-lite without a broker-to-broker
protocol.

SDK additions:
- Bridge class (start, stop, EventEmitter for forwarded/dropped/error)
- MeshClient.joinTopic / leaveTopic / createTopic methods
- Loop prevention: plaintext hop counter prefix __cmh<n>: with maxHops
  default 2; echo guard via senderPubkey == own session pubkey

CLI additions:
- claudemesh bridge run <config.yaml> long-lived process
- claudemesh bridge init prints config template
- Zero-dep YAML parser for the flat bridge config shape

The hop prefix is visible in message bodies — minor wart, fixed in
v0.3.0 by moving loop tracking into broker primitives.

SDK kept as devDependency since Bun bundles it into dist; no impact
on npm publish or runtime resolution.

Spec: .artifacts/specs/2026-05-02-v0.2.0-scope.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-05-02 13:41:50 +01:00
parent 9418d0ee30
commit 9dd1e401b0
7 changed files with 432 additions and 0 deletions

View File

@@ -107,6 +107,10 @@ API keys (REST + external WS auth, v0.2.0)
claudemesh apikey list show keys (status, last-used, scope)
claudemesh apikey revoke <id> revoke a key
Bridge (forward a topic between two meshes, v0.2.0)
claudemesh bridge init print config template
claudemesh bridge run <config> run bridge as a long-lived process
Topic (conversation scope, v0.2.0)
claudemesh topic create <name> create a topic [--description --visibility]
claudemesh topic list list topics in the mesh
@@ -514,6 +518,23 @@ async function main(): Promise<void> {
break;
}
// bridge — forward a topic between two meshes (v0.2.0)
case "bridge": {
const sub = positionals[0];
if (sub === "run") {
const { runBridge } = await import("~/commands/bridge.js");
process.exit(await runBridge(positionals[1] ?? ""));
} else if (sub === "init" || sub === "config") {
const { bridgeConfigTemplate } = await import("~/commands/bridge.js");
console.log(bridgeConfigTemplate());
process.exit(EXIT.SUCCESS);
} else {
console.error("Usage: claudemesh bridge <run <config.yaml> | init>");
process.exit(EXIT.INVALID_ARGS);
}
break;
}
// apikey — REST + external WS bearer tokens (v0.2.0)
case "apikey": case "api-key": {
const sub = positionals[0];