claudemesh-cli 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -88,7 +88,7 @@ __export(exports_urls, {
88
88
  VERSION: () => VERSION,
89
89
  URLS: () => URLS
90
90
  });
91
- var URLS, VERSION = "1.5.0", env;
91
+ var URLS, VERSION = "1.6.0", env;
92
92
  var init_urls = __esm(() => {
93
93
  URLS = {
94
94
  BROKER: process.env.CLAUDEMESH_BROKER_URL ?? "wss://ic.claudemesh.com/ws",
@@ -1132,6 +1132,12 @@ class BrokerClient {
1132
1132
  grantFileAccessResolvers = new Map;
1133
1133
  peerFileResponseResolvers = new Map;
1134
1134
  peerDirResponseResolvers = new Map;
1135
+ topicCreatedResolvers = new Map;
1136
+ topicListResolvers = new Map;
1137
+ topicMembersResolvers = new Map;
1138
+ topicHistoryResolvers = new Map;
1139
+ apiKeyCreatedResolvers = new Map;
1140
+ apiKeyListResolvers = new Map;
1135
1141
  sharedDirs = [process.cwd()];
1136
1142
  _serviceCatalog = [];
1137
1143
  get serviceCatalog() {
@@ -1434,6 +1440,116 @@ class BrokerClient {
1434
1440
  return;
1435
1441
  this.ws.send(JSON.stringify({ type: "leave_group", name }));
1436
1442
  }
1443
+ async topicCreate(args) {
1444
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
1445
+ return null;
1446
+ return new Promise((resolve) => {
1447
+ const reqId = this.makeReqId();
1448
+ this.topicCreatedResolvers.set(reqId, {
1449
+ resolve,
1450
+ timer: setTimeout(() => {
1451
+ if (this.topicCreatedResolvers.delete(reqId))
1452
+ resolve(null);
1453
+ }, 5000)
1454
+ });
1455
+ this.ws.send(JSON.stringify({ type: "topic_create", _reqId: reqId, ...args }));
1456
+ });
1457
+ }
1458
+ async topicList() {
1459
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
1460
+ return [];
1461
+ return new Promise((resolve) => {
1462
+ const reqId = this.makeReqId();
1463
+ this.topicListResolvers.set(reqId, {
1464
+ resolve,
1465
+ timer: setTimeout(() => {
1466
+ if (this.topicListResolvers.delete(reqId))
1467
+ resolve([]);
1468
+ }, 5000)
1469
+ });
1470
+ this.ws.send(JSON.stringify({ type: "topic_list", _reqId: reqId }));
1471
+ });
1472
+ }
1473
+ async topicJoin(topic, role) {
1474
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
1475
+ return;
1476
+ this.ws.send(JSON.stringify({ type: "topic_join", topic, role }));
1477
+ }
1478
+ async topicLeave(topic) {
1479
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
1480
+ return;
1481
+ this.ws.send(JSON.stringify({ type: "topic_leave", topic }));
1482
+ }
1483
+ async topicMembers(topic) {
1484
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
1485
+ return [];
1486
+ return new Promise((resolve) => {
1487
+ const reqId = this.makeReqId();
1488
+ this.topicMembersResolvers.set(reqId, {
1489
+ resolve,
1490
+ timer: setTimeout(() => {
1491
+ if (this.topicMembersResolvers.delete(reqId))
1492
+ resolve([]);
1493
+ }, 5000)
1494
+ });
1495
+ this.ws.send(JSON.stringify({ type: "topic_members", _reqId: reqId, topic }));
1496
+ });
1497
+ }
1498
+ async topicHistory(args) {
1499
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
1500
+ return [];
1501
+ return new Promise((resolve) => {
1502
+ const reqId = this.makeReqId();
1503
+ this.topicHistoryResolvers.set(reqId, {
1504
+ resolve,
1505
+ timer: setTimeout(() => {
1506
+ if (this.topicHistoryResolvers.delete(reqId))
1507
+ resolve([]);
1508
+ }, 5000)
1509
+ });
1510
+ this.ws.send(JSON.stringify({ type: "topic_history", _reqId: reqId, ...args }));
1511
+ });
1512
+ }
1513
+ async topicMarkRead(topic) {
1514
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
1515
+ return;
1516
+ this.ws.send(JSON.stringify({ type: "topic_mark_read", topic }));
1517
+ }
1518
+ async apiKeyCreate(args) {
1519
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
1520
+ return null;
1521
+ return new Promise((resolve) => {
1522
+ const reqId = this.makeReqId();
1523
+ this.apiKeyCreatedResolvers.set(reqId, {
1524
+ resolve,
1525
+ timer: setTimeout(() => {
1526
+ if (this.apiKeyCreatedResolvers.delete(reqId))
1527
+ resolve(null);
1528
+ }, 5000)
1529
+ });
1530
+ this.ws.send(JSON.stringify({ type: "apikey_create", _reqId: reqId, ...args }));
1531
+ });
1532
+ }
1533
+ async apiKeyList() {
1534
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
1535
+ return [];
1536
+ return new Promise((resolve) => {
1537
+ const reqId = this.makeReqId();
1538
+ this.apiKeyListResolvers.set(reqId, {
1539
+ resolve,
1540
+ timer: setTimeout(() => {
1541
+ if (this.apiKeyListResolvers.delete(reqId))
1542
+ resolve([]);
1543
+ }, 5000)
1544
+ });
1545
+ this.ws.send(JSON.stringify({ type: "apikey_list", _reqId: reqId }));
1546
+ });
1547
+ }
1548
+ async apiKeyRevoke(id) {
1549
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
1550
+ return;
1551
+ this.ws.send(JSON.stringify({ type: "apikey_revoke", id }));
1552
+ }
1437
1553
  async setState(key, value) {
1438
1554
  if (!this.ws || this.ws.readyState !== this.ws.OPEN)
1439
1555
  return;
@@ -2514,6 +2630,43 @@ class BrokerClient {
2514
2630
  this.resolveFromMap(this.listPeersResolvers, msgReqId, peers);
2515
2631
  return;
2516
2632
  }
2633
+ if (msg.type === "topic_created") {
2634
+ const r = msg.topic ?? {};
2635
+ this.resolveFromMap(this.topicCreatedResolvers, msgReqId, {
2636
+ id: r.id,
2637
+ name: r.name,
2638
+ created: !!msg.created
2639
+ });
2640
+ return;
2641
+ }
2642
+ if (msg.type === "topic_list_response") {
2643
+ this.resolveFromMap(this.topicListResolvers, msgReqId, msg.topics ?? []);
2644
+ return;
2645
+ }
2646
+ if (msg.type === "topic_members_response") {
2647
+ this.resolveFromMap(this.topicMembersResolvers, msgReqId, msg.members ?? []);
2648
+ return;
2649
+ }
2650
+ if (msg.type === "topic_history_response") {
2651
+ this.resolveFromMap(this.topicHistoryResolvers, msgReqId, msg.messages ?? []);
2652
+ return;
2653
+ }
2654
+ if (msg.type === "apikey_created") {
2655
+ this.resolveFromMap(this.apiKeyCreatedResolvers, msgReqId, {
2656
+ id: String(msg.id ?? ""),
2657
+ secret: String(msg.secret ?? ""),
2658
+ label: String(msg.label ?? ""),
2659
+ prefix: String(msg.prefix ?? ""),
2660
+ capabilities: msg.capabilities ?? [],
2661
+ topicScopes: msg.topicScopes ?? null,
2662
+ createdAt: String(msg.createdAt ?? "")
2663
+ });
2664
+ return;
2665
+ }
2666
+ if (msg.type === "apikey_list_response") {
2667
+ this.resolveFromMap(this.apiKeyListResolvers, msgReqId, msg.keys ?? []);
2668
+ return;
2669
+ }
2517
2670
  if (msg.type === "push") {
2518
2671
  this._statsCounters.messagesIn++;
2519
2672
  const nonce = String(msg.nonce ?? "");
@@ -6598,7 +6751,17 @@ async function runSend(flags, to, message) {
6598
6751
  }
6599
6752
  await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
6600
6753
  let targetSpec = to;
6601
- if (!to.startsWith("@") && to !== "*" && !/^[0-9a-f]{64}$/i.test(to)) {
6754
+ if (to.startsWith("#") && !/^#[0-9a-z_-]{20,}$/i.test(to)) {
6755
+ const name = to.slice(1);
6756
+ const topics = await client.topicList();
6757
+ const match = topics.find((t) => t.name === name);
6758
+ if (!match) {
6759
+ const names = topics.map((t) => "#" + t.name).join(", ");
6760
+ render.err(`Topic "${to}" not found.`, `topics: ${names || "(none)"}`);
6761
+ process.exit(1);
6762
+ }
6763
+ targetSpec = "#" + match.id;
6764
+ } else if (!to.startsWith("@") && !to.startsWith("#") && to !== "*" && !/^[0-9a-f]{64}$/i.test(to)) {
6602
6765
  const peers = await client.listPeers();
6603
6766
  const match = peers.find((p) => p.displayName.toLowerCase() === to.toLowerCase());
6604
6767
  if (!match) {
@@ -10378,6 +10541,1037 @@ var init_platform_actions = __esm(() => {
10378
10541
  init_exit_codes();
10379
10542
  });
10380
10543
 
10544
+ // ../../packages/sdk/dist/crypto.js
10545
+ var require_crypto = __commonJS((exports) => {
10546
+ var __importDefault = exports && exports.__importDefault || function(mod) {
10547
+ return mod && mod.__esModule ? mod : { default: mod };
10548
+ };
10549
+ Object.defineProperty(exports, "__esModule", { value: true });
10550
+ exports.generateKeyPair = generateKeyPair;
10551
+ exports.signHello = signHello2;
10552
+ exports.isDirectTarget = isDirectTarget2;
10553
+ exports.encryptDirect = encryptDirect2;
10554
+ exports.decryptDirect = decryptDirect2;
10555
+ var libsodium_wrappers_1 = __importDefault(__require("libsodium-wrappers"));
10556
+ var ready2 = false;
10557
+ async function ensureSodium3() {
10558
+ if (!ready2) {
10559
+ await libsodium_wrappers_1.default.ready;
10560
+ ready2 = true;
10561
+ }
10562
+ return libsodium_wrappers_1.default;
10563
+ }
10564
+ async function generateKeyPair() {
10565
+ const s = await ensureSodium3();
10566
+ const kp = s.crypto_sign_keypair();
10567
+ return {
10568
+ publicKey: s.to_hex(kp.publicKey),
10569
+ secretKey: s.to_hex(kp.privateKey)
10570
+ };
10571
+ }
10572
+ async function signHello2(meshId, memberId, pubkey, secretKeyHex) {
10573
+ const s = await ensureSodium3();
10574
+ const timestamp2 = Date.now();
10575
+ const canonical = `${meshId}|${memberId}|${pubkey}|${timestamp2}`;
10576
+ const sig = s.crypto_sign_detached(s.from_string(canonical), s.from_hex(secretKeyHex));
10577
+ return { timestamp: timestamp2, signature: s.to_hex(sig) };
10578
+ }
10579
+ var HEX_PUBKEY2 = /^[0-9a-f]{64}$/;
10580
+ function isDirectTarget2(targetSpec) {
10581
+ return HEX_PUBKEY2.test(targetSpec);
10582
+ }
10583
+ async function encryptDirect2(message, recipientPubkeyHex, senderSecretKeyHex) {
10584
+ const s = await ensureSodium3();
10585
+ const recipientPub = s.crypto_sign_ed25519_pk_to_curve25519(s.from_hex(recipientPubkeyHex));
10586
+ const senderSec = s.crypto_sign_ed25519_sk_to_curve25519(s.from_hex(senderSecretKeyHex));
10587
+ const nonce = s.randombytes_buf(s.crypto_box_NONCEBYTES);
10588
+ const ciphertext = s.crypto_box_easy(s.from_string(message), nonce, recipientPub, senderSec);
10589
+ return {
10590
+ nonce: s.to_base64(nonce, s.base64_variants.ORIGINAL),
10591
+ ciphertext: s.to_base64(ciphertext, s.base64_variants.ORIGINAL)
10592
+ };
10593
+ }
10594
+ async function decryptDirect2(envelope, senderPubkeyHex, recipientSecretKeyHex) {
10595
+ const s = await ensureSodium3();
10596
+ try {
10597
+ const senderPub = s.crypto_sign_ed25519_pk_to_curve25519(s.from_hex(senderPubkeyHex));
10598
+ const recipientSec = s.crypto_sign_ed25519_sk_to_curve25519(s.from_hex(recipientSecretKeyHex));
10599
+ const nonce = s.from_base64(envelope.nonce, s.base64_variants.ORIGINAL);
10600
+ const ciphertext = s.from_base64(envelope.ciphertext, s.base64_variants.ORIGINAL);
10601
+ const plain = s.crypto_box_open_easy(ciphertext, nonce, senderPub, recipientSec);
10602
+ return s.to_string(plain);
10603
+ } catch {
10604
+ return null;
10605
+ }
10606
+ }
10607
+ });
10608
+
10609
+ // ../../packages/sdk/dist/client.js
10610
+ var require_client = __commonJS((exports) => {
10611
+ var __importDefault = exports && exports.__importDefault || function(mod) {
10612
+ return mod && mod.__esModule ? mod : { default: mod };
10613
+ };
10614
+ Object.defineProperty(exports, "__esModule", { value: true });
10615
+ exports.MeshClient = undefined;
10616
+ var node_events_1 = __require("node:events");
10617
+ var node_crypto_1 = __require("node:crypto");
10618
+ var ws_1 = __importDefault(__require("ws"));
10619
+ var crypto_js_1 = require_crypto();
10620
+ var MAX_QUEUED2 = 100;
10621
+ var HELLO_ACK_TIMEOUT_MS2 = 5000;
10622
+ var BACKOFF_CAPS2 = [1000, 2000, 4000, 8000, 16000, 30000];
10623
+
10624
+ class MeshClient extends node_events_1.EventEmitter {
10625
+ opts;
10626
+ ws = null;
10627
+ _status = "closed";
10628
+ pendingSends = new Map;
10629
+ outbound = [];
10630
+ closed = false;
10631
+ reconnectAttempt = 0;
10632
+ helloTimer = null;
10633
+ reconnectTimer = null;
10634
+ sessionPubkey = null;
10635
+ sessionSecretKey = null;
10636
+ listPeersResolvers = new Map;
10637
+ stateResolvers = new Map;
10638
+ constructor(opts) {
10639
+ super();
10640
+ this.opts = opts;
10641
+ }
10642
+ get status() {
10643
+ return this._status;
10644
+ }
10645
+ get pubkey() {
10646
+ return this.sessionPubkey;
10647
+ }
10648
+ async connect() {
10649
+ if (this.closed)
10650
+ throw new Error("client is closed");
10651
+ this._status = "connecting";
10652
+ const ws = new ws_1.default(this.opts.brokerUrl);
10653
+ this.ws = ws;
10654
+ return new Promise((resolve3, reject) => {
10655
+ const onOpen = async () => {
10656
+ this.debug("ws open -> generating session keypair + signing hello");
10657
+ try {
10658
+ if (!this.sessionPubkey) {
10659
+ const sessionKP = await (0, crypto_js_1.generateKeyPair)();
10660
+ this.sessionPubkey = sessionKP.publicKey;
10661
+ this.sessionSecretKey = sessionKP.secretKey;
10662
+ }
10663
+ const { timestamp: timestamp2, signature } = await (0, crypto_js_1.signHello)(this.opts.meshId, this.opts.memberId, this.opts.pubkey, this.opts.secretKey);
10664
+ ws.send(JSON.stringify({
10665
+ type: "hello",
10666
+ meshId: this.opts.meshId,
10667
+ memberId: this.opts.memberId,
10668
+ pubkey: this.opts.pubkey,
10669
+ sessionPubkey: this.sessionPubkey,
10670
+ displayName: this.opts.displayName,
10671
+ sessionId: `sdk-${process.pid}-${Date.now()}`,
10672
+ pid: process.pid,
10673
+ peerType: this.opts.peerType ?? "connector",
10674
+ channel: this.opts.channel ?? "sdk",
10675
+ timestamp: timestamp2,
10676
+ signature
10677
+ }));
10678
+ } catch (e) {
10679
+ reject(new Error(`hello sign failed: ${e instanceof Error ? e.message : e}`));
10680
+ return;
10681
+ }
10682
+ this.helloTimer = setTimeout(() => {
10683
+ this.debug("hello_ack timeout");
10684
+ ws.close();
10685
+ reject(new Error("hello_ack timeout"));
10686
+ }, HELLO_ACK_TIMEOUT_MS2);
10687
+ };
10688
+ const onMessage = (raw) => {
10689
+ let msg;
10690
+ try {
10691
+ msg = JSON.parse(raw.toString());
10692
+ } catch {
10693
+ return;
10694
+ }
10695
+ if (msg.type === "hello_ack") {
10696
+ if (this.helloTimer)
10697
+ clearTimeout(this.helloTimer);
10698
+ this.helloTimer = null;
10699
+ this._status = "open";
10700
+ this.reconnectAttempt = 0;
10701
+ this.flushOutbound();
10702
+ this.emit("connected");
10703
+ resolve3();
10704
+ return;
10705
+ }
10706
+ this.handleServerMessage(msg);
10707
+ };
10708
+ const onClose = () => {
10709
+ if (this.helloTimer)
10710
+ clearTimeout(this.helloTimer);
10711
+ this.helloTimer = null;
10712
+ const wasOpen = this._status === "open" || this._status === "reconnecting";
10713
+ this.ws = null;
10714
+ if (!wasOpen && this._status === "connecting") {
10715
+ reject(new Error("ws closed before hello_ack"));
10716
+ }
10717
+ if (!this.closed) {
10718
+ this.emit("disconnected");
10719
+ this.scheduleReconnect();
10720
+ } else {
10721
+ this._status = "closed";
10722
+ this.emit("disconnected");
10723
+ }
10724
+ };
10725
+ const onError = (err) => {
10726
+ this.debug(`ws error: ${err.message}`);
10727
+ };
10728
+ ws.on("open", onOpen);
10729
+ ws.on("message", onMessage);
10730
+ ws.on("close", onClose);
10731
+ ws.on("error", onError);
10732
+ });
10733
+ }
10734
+ disconnect() {
10735
+ this.closed = true;
10736
+ if (this.helloTimer)
10737
+ clearTimeout(this.helloTimer);
10738
+ if (this.reconnectTimer)
10739
+ clearTimeout(this.reconnectTimer);
10740
+ if (this.ws) {
10741
+ try {
10742
+ this.ws.close();
10743
+ } catch {}
10744
+ }
10745
+ this._status = "closed";
10746
+ }
10747
+ async send(to, message, priority = "next") {
10748
+ let targetSpec = to;
10749
+ if (!(0, crypto_js_1.isDirectTarget)(to) && to !== "*" && !to.startsWith("@") && !to.startsWith("#")) {
10750
+ const peers = await this.listPeers();
10751
+ const match = peers.find((p) => p.displayName.toLowerCase() === to.toLowerCase());
10752
+ if (match) {
10753
+ targetSpec = match.pubkey;
10754
+ }
10755
+ }
10756
+ const id = (0, node_crypto_1.randomBytes)(8).toString("hex");
10757
+ let nonce;
10758
+ let ciphertext;
10759
+ if ((0, crypto_js_1.isDirectTarget)(targetSpec)) {
10760
+ const env2 = await (0, crypto_js_1.encryptDirect)(message, targetSpec, this.sessionSecretKey ?? this.opts.secretKey);
10761
+ nonce = env2.nonce;
10762
+ ciphertext = env2.ciphertext;
10763
+ } else {
10764
+ nonce = (0, node_crypto_1.randomBytes)(24).toString("base64");
10765
+ ciphertext = Buffer.from(message, "utf-8").toString("base64");
10766
+ }
10767
+ return new Promise((resolve3) => {
10768
+ if (this.pendingSends.size >= MAX_QUEUED2) {
10769
+ resolve3({ ok: false, error: "outbound queue full" });
10770
+ return;
10771
+ }
10772
+ this.pendingSends.set(id, {
10773
+ id,
10774
+ targetSpec,
10775
+ priority,
10776
+ nonce,
10777
+ ciphertext,
10778
+ resolve: resolve3
10779
+ });
10780
+ const dispatch = () => {
10781
+ if (!this.ws || this.ws.readyState !== ws_1.default.OPEN)
10782
+ return;
10783
+ this.ws.send(JSON.stringify({
10784
+ type: "send",
10785
+ id,
10786
+ targetSpec,
10787
+ priority,
10788
+ nonce,
10789
+ ciphertext
10790
+ }));
10791
+ };
10792
+ if (this._status === "open")
10793
+ dispatch();
10794
+ else {
10795
+ if (this.outbound.length >= MAX_QUEUED2) {
10796
+ this.pendingSends.delete(id);
10797
+ resolve3({ ok: false, error: "outbound queue full" });
10798
+ return;
10799
+ }
10800
+ this.outbound.push(dispatch);
10801
+ }
10802
+ setTimeout(() => {
10803
+ if (this.pendingSends.has(id)) {
10804
+ this.pendingSends.delete(id);
10805
+ resolve3({ ok: false, error: "ack timeout" });
10806
+ }
10807
+ }, 1e4);
10808
+ });
10809
+ }
10810
+ async broadcast(message, priority = "next") {
10811
+ return this.send("*", message, priority);
10812
+ }
10813
+ async listPeers() {
10814
+ if (!this.ws || this.ws.readyState !== ws_1.default.OPEN)
10815
+ return [];
10816
+ return new Promise((resolve3) => {
10817
+ const reqId = this.makeReqId();
10818
+ this.listPeersResolvers.set(reqId, {
10819
+ resolve: resolve3,
10820
+ timer: setTimeout(() => {
10821
+ if (this.listPeersResolvers.delete(reqId))
10822
+ resolve3([]);
10823
+ }, 5000)
10824
+ });
10825
+ this.ws.send(JSON.stringify({ type: "list_peers", _reqId: reqId }));
10826
+ });
10827
+ }
10828
+ async getState(key) {
10829
+ if (!this.ws || this.ws.readyState !== ws_1.default.OPEN)
10830
+ return null;
10831
+ return new Promise((resolve3) => {
10832
+ const reqId = this.makeReqId();
10833
+ this.stateResolvers.set(reqId, {
10834
+ resolve: (result) => resolve3(result ? String(result.value) : null),
10835
+ timer: setTimeout(() => {
10836
+ if (this.stateResolvers.delete(reqId))
10837
+ resolve3(null);
10838
+ }, 5000)
10839
+ });
10840
+ this.ws.send(JSON.stringify({ type: "get_state", key, _reqId: reqId }));
10841
+ });
10842
+ }
10843
+ async setState(key, value) {
10844
+ if (!this.ws || this.ws.readyState !== ws_1.default.OPEN)
10845
+ return;
10846
+ this.ws.send(JSON.stringify({ type: "set_state", key, value }));
10847
+ }
10848
+ async setSummary(summary) {
10849
+ if (!this.ws || this.ws.readyState !== ws_1.default.OPEN)
10850
+ return;
10851
+ this.ws.send(JSON.stringify({ type: "set_summary", summary }));
10852
+ }
10853
+ async setStatus(status) {
10854
+ if (!this.ws || this.ws.readyState !== ws_1.default.OPEN)
10855
+ return;
10856
+ this.ws.send(JSON.stringify({ type: "set_status", status }));
10857
+ }
10858
+ async createTopic(args) {
10859
+ if (!this.ws || this.ws.readyState !== ws_1.default.OPEN)
10860
+ return;
10861
+ this.ws.send(JSON.stringify({ type: "topic_create", ...args }));
10862
+ }
10863
+ async joinTopic(topic, role) {
10864
+ if (!this.ws || this.ws.readyState !== ws_1.default.OPEN)
10865
+ return;
10866
+ this.ws.send(JSON.stringify({ type: "topic_join", topic, role }));
10867
+ }
10868
+ async leaveTopic(topic) {
10869
+ if (!this.ws || this.ws.readyState !== ws_1.default.OPEN)
10870
+ return;
10871
+ this.ws.send(JSON.stringify({ type: "topic_leave", topic }));
10872
+ }
10873
+ makeReqId() {
10874
+ return Math.random().toString(36).slice(2) + Date.now().toString(36);
10875
+ }
10876
+ flushOutbound() {
10877
+ const queued = this.outbound.slice();
10878
+ this.outbound.length = 0;
10879
+ for (const send of queued)
10880
+ send();
10881
+ }
10882
+ scheduleReconnect() {
10883
+ this._status = "reconnecting";
10884
+ const delay = BACKOFF_CAPS2[Math.min(this.reconnectAttempt, BACKOFF_CAPS2.length - 1)];
10885
+ this.reconnectAttempt += 1;
10886
+ this.debug(`reconnect in ${delay}ms (attempt ${this.reconnectAttempt})`);
10887
+ this.reconnectTimer = setTimeout(() => {
10888
+ if (this.closed)
10889
+ return;
10890
+ this.connect().catch((e) => {
10891
+ this.debug(`reconnect failed: ${e instanceof Error ? e.message : e}`);
10892
+ });
10893
+ }, delay);
10894
+ }
10895
+ handleServerMessage(msg) {
10896
+ const reqId = msg._reqId;
10897
+ if (msg.type === "ack") {
10898
+ const pending = this.pendingSends.get(String(msg.id ?? ""));
10899
+ if (pending) {
10900
+ pending.resolve({
10901
+ ok: true,
10902
+ messageId: String(msg.messageId ?? "")
10903
+ });
10904
+ this.pendingSends.delete(pending.id);
10905
+ }
10906
+ return;
10907
+ }
10908
+ if (msg.type === "peers_list") {
10909
+ const peers = msg.peers ?? [];
10910
+ this.resolveFromMap(this.listPeersResolvers, reqId, peers);
10911
+ return;
10912
+ }
10913
+ if (msg.type === "push") {
10914
+ this.handlePush(msg);
10915
+ return;
10916
+ }
10917
+ if (msg.type === "state_result") {
10918
+ if (msg.key) {
10919
+ this.resolveFromMap(this.stateResolvers, reqId, {
10920
+ key: String(msg.key),
10921
+ value: msg.value,
10922
+ updatedBy: String(msg.updatedBy ?? ""),
10923
+ updatedAt: String(msg.updatedAt ?? "")
10924
+ });
10925
+ } else {
10926
+ this.resolveFromMap(this.stateResolvers, reqId, null);
10927
+ }
10928
+ return;
10929
+ }
10930
+ if (msg.type === "state_change") {
10931
+ this.emit("state_change", {
10932
+ key: String(msg.key ?? ""),
10933
+ value: msg.value,
10934
+ updatedBy: String(msg.updatedBy ?? "")
10935
+ });
10936
+ return;
10937
+ }
10938
+ if (msg.type === "error") {
10939
+ this.debug(`broker error: ${msg.code} ${msg.message}`);
10940
+ const id = msg.id ? String(msg.id) : null;
10941
+ if (id) {
10942
+ const pending = this.pendingSends.get(id);
10943
+ if (pending) {
10944
+ pending.resolve({
10945
+ ok: false,
10946
+ error: `${msg.code}: ${msg.message}`
10947
+ });
10948
+ this.pendingSends.delete(id);
10949
+ }
10950
+ }
10951
+ return;
10952
+ }
10953
+ }
10954
+ async handlePush(msg) {
10955
+ const nonce = String(msg.nonce ?? "");
10956
+ const ciphertext = String(msg.ciphertext ?? "");
10957
+ const senderPubkey = String(msg.senderPubkey ?? "");
10958
+ const kind = senderPubkey ? "direct" : "unknown";
10959
+ let plaintext = null;
10960
+ if (senderPubkey && nonce && ciphertext) {
10961
+ plaintext = await (0, crypto_js_1.decryptDirect)({ nonce, ciphertext }, senderPubkey, this.sessionSecretKey ?? this.opts.secretKey);
10962
+ }
10963
+ if (plaintext === null && ciphertext && !senderPubkey) {
10964
+ try {
10965
+ plaintext = Buffer.from(ciphertext, "base64").toString("utf-8");
10966
+ } catch {
10967
+ plaintext = null;
10968
+ }
10969
+ }
10970
+ if (plaintext === null && ciphertext) {
10971
+ try {
10972
+ const decoded = Buffer.from(ciphertext, "base64").toString("utf-8");
10973
+ if (/^[\x20-\x7E\s\u00A0-\uFFFF]*$/.test(decoded) && decoded.length > 0) {
10974
+ plaintext = decoded;
10975
+ }
10976
+ } catch {
10977
+ plaintext = null;
10978
+ }
10979
+ }
10980
+ const push = {
10981
+ messageId: String(msg.messageId ?? ""),
10982
+ meshId: String(msg.meshId ?? ""),
10983
+ senderPubkey,
10984
+ priority: msg.priority ?? "next",
10985
+ nonce,
10986
+ ciphertext,
10987
+ createdAt: String(msg.createdAt ?? ""),
10988
+ receivedAt: new Date().toISOString(),
10989
+ plaintext,
10990
+ kind,
10991
+ ...msg.subtype ? { subtype: msg.subtype } : {},
10992
+ ...msg.event ? { event: String(msg.event) } : {},
10993
+ ...msg.eventData ? { eventData: msg.eventData } : {}
10994
+ };
10995
+ this.emit("message", push);
10996
+ if (push.event === "peer_joined" && push.eventData) {
10997
+ this.emit("peer_joined", push.eventData);
10998
+ }
10999
+ if (push.event === "peer_left" && push.eventData) {
11000
+ this.emit("peer_left", push.eventData);
11001
+ }
11002
+ }
11003
+ resolveFromMap(map, reqId, value) {
11004
+ let entry = reqId ? map.get(reqId) : undefined;
11005
+ if (!entry) {
11006
+ const first = map.entries().next().value;
11007
+ if (first) {
11008
+ entry = first[1];
11009
+ map.delete(first[0]);
11010
+ }
11011
+ } else {
11012
+ map.delete(reqId);
11013
+ }
11014
+ if (entry) {
11015
+ clearTimeout(entry.timer);
11016
+ entry.resolve(value);
11017
+ return true;
11018
+ }
11019
+ return false;
11020
+ }
11021
+ debug(msg) {
11022
+ if (this.opts.debug)
11023
+ console.error(`[claudemesh-sdk] ${msg}`);
11024
+ }
11025
+ }
11026
+ exports.MeshClient = MeshClient;
11027
+ });
11028
+
11029
+ // ../../packages/sdk/dist/bridge.js
11030
+ var require_bridge = __commonJS((exports) => {
11031
+ Object.defineProperty(exports, "__esModule", { value: true });
11032
+ exports.Bridge = undefined;
11033
+ var node_events_1 = __require("node:events");
11034
+ var client_js_1 = require_client();
11035
+ var HOP_PREFIX_RE = /^__cmh(\d+):/;
11036
+ var MAX_HOPS_DEFAULT = 2;
11037
+
11038
+ class Bridge extends node_events_1.EventEmitter {
11039
+ clientA;
11040
+ clientB;
11041
+ maxHops;
11042
+ opts;
11043
+ started = false;
11044
+ constructor(opts) {
11045
+ super();
11046
+ this.opts = opts;
11047
+ this.maxHops = opts.maxHops ?? MAX_HOPS_DEFAULT;
11048
+ this.clientA = new client_js_1.MeshClient(opts.a.client);
11049
+ this.clientB = new client_js_1.MeshClient(opts.b.client);
11050
+ }
11051
+ async start() {
11052
+ if (this.started)
11053
+ return;
11054
+ this.started = true;
11055
+ await Promise.all([this.clientA.connect(), this.clientB.connect()]);
11056
+ await Promise.all([
11057
+ this.clientA.joinTopic(this.opts.a.topic, this.opts.a.role),
11058
+ this.clientB.joinTopic(this.opts.b.topic, this.opts.b.role)
11059
+ ]);
11060
+ this.clientA.on("message", (m) => this.handleIncoming("a", m).catch((e) => this.emit("error", e instanceof Error ? e : new Error(String(e)))));
11061
+ this.clientB.on("message", (m) => this.handleIncoming("b", m).catch((e) => this.emit("error", e instanceof Error ? e : new Error(String(e)))));
11062
+ }
11063
+ async stop() {
11064
+ if (!this.started)
11065
+ return;
11066
+ this.started = false;
11067
+ this.clientA.disconnect();
11068
+ this.clientB.disconnect();
11069
+ }
11070
+ async handleIncoming(fromSide, msg) {
11071
+ if (msg.subtype === "system")
11072
+ return;
11073
+ const text = msg.plaintext;
11074
+ if (!text)
11075
+ return;
11076
+ const ownA = this.clientA.pubkey;
11077
+ const ownB = this.clientB.pubkey;
11078
+ if (msg.senderPubkey === ownA || msg.senderPubkey === ownB) {
11079
+ this.emit("dropped", { from: fromSide, reason: "echo", hop: -1 });
11080
+ return;
11081
+ }
11082
+ if (this.opts.filter) {
11083
+ const ok = await this.opts.filter(msg, fromSide);
11084
+ if (!ok) {
11085
+ this.emit("dropped", { from: fromSide, reason: "filter", hop: -1 });
11086
+ return;
11087
+ }
11088
+ }
11089
+ const m = text.match(HOP_PREFIX_RE);
11090
+ const currentHop = m ? Number(m[1]) : 0;
11091
+ const nextHop = currentHop + 1;
11092
+ if (nextHop > this.maxHops) {
11093
+ this.emit("dropped", { from: fromSide, reason: "max_hops", hop: currentHop });
11094
+ return;
11095
+ }
11096
+ const stripped = m ? text.slice(m[0].length) : text;
11097
+ const forwarded = `__cmh${nextHop}:${stripped}`;
11098
+ const targetClient = fromSide === "a" ? this.clientB : this.clientA;
11099
+ const targetTopic = fromSide === "a" ? this.opts.b.topic : this.opts.a.topic;
11100
+ await targetClient.send(`#${targetTopic}`, forwarded, "next");
11101
+ this.emit("forwarded", {
11102
+ from: fromSide,
11103
+ to: fromSide === "a" ? "b" : "a",
11104
+ hop: nextHop,
11105
+ bytes: forwarded.length
11106
+ });
11107
+ }
11108
+ }
11109
+ exports.Bridge = Bridge;
11110
+ });
11111
+
11112
+ // ../../packages/sdk/dist/index.js
11113
+ var require_dist = __commonJS((exports) => {
11114
+ Object.defineProperty(exports, "__esModule", { value: true });
11115
+ exports.Bridge = exports.generateKeyPair = exports.MeshClient = undefined;
11116
+ var client_js_1 = require_client();
11117
+ Object.defineProperty(exports, "MeshClient", { enumerable: true, get: function() {
11118
+ return client_js_1.MeshClient;
11119
+ } });
11120
+ var crypto_js_1 = require_crypto();
11121
+ Object.defineProperty(exports, "generateKeyPair", { enumerable: true, get: function() {
11122
+ return crypto_js_1.generateKeyPair;
11123
+ } });
11124
+ var bridge_js_1 = require_bridge();
11125
+ Object.defineProperty(exports, "Bridge", { enumerable: true, get: function() {
11126
+ return bridge_js_1.Bridge;
11127
+ } });
11128
+ });
11129
+
11130
+ // src/commands/bridge.ts
11131
+ var exports_bridge = {};
11132
+ __export(exports_bridge, {
11133
+ runBridge: () => runBridge,
11134
+ bridgeConfigTemplate: () => bridgeConfigTemplate
11135
+ });
11136
+ import { readFileSync as readFileSync14, existsSync as existsSync20 } from "node:fs";
11137
+ function parseConfig(text) {
11138
+ const trimmed = text.trim();
11139
+ if (trimmed.startsWith("{"))
11140
+ return JSON.parse(trimmed);
11141
+ const root = {};
11142
+ let cursor = null;
11143
+ for (const raw of text.split(`
11144
+ `)) {
11145
+ const line = raw.replace(/#.*$/, "").trimEnd();
11146
+ if (!line.trim())
11147
+ continue;
11148
+ const top = line.match(/^(a|b)\s*:\s*$/);
11149
+ if (top) {
11150
+ cursor = {};
11151
+ root[top[1]] = cursor;
11152
+ continue;
11153
+ }
11154
+ const flat = line.match(/^(\w+)\s*:\s*(.+)$/);
11155
+ if (flat && /^\s/.test(line) && cursor) {
11156
+ cursor[flat[1]] = parseScalar(flat[2]);
11157
+ } else if (flat) {
11158
+ const v = parseScalar(flat[2]);
11159
+ if (typeof v === "number")
11160
+ root[flat[1]] = v;
11161
+ }
11162
+ }
11163
+ return root;
11164
+ }
11165
+ function parseScalar(raw) {
11166
+ const v = raw.trim().replace(/^["'](.*)["']$/, "$1");
11167
+ if (v === "true")
11168
+ return true;
11169
+ if (v === "false")
11170
+ return false;
11171
+ if (/^-?\d+(\.\d+)?$/.test(v))
11172
+ return Number(v);
11173
+ return v;
11174
+ }
11175
+ async function runBridge(configPath) {
11176
+ if (!configPath) {
11177
+ render.err("Usage: claudemesh bridge run <config.yaml>");
11178
+ return EXIT.INVALID_ARGS;
11179
+ }
11180
+ if (!existsSync20(configPath)) {
11181
+ render.err(`config file not found: ${configPath}`);
11182
+ return EXIT.NOT_FOUND;
11183
+ }
11184
+ let cfg;
11185
+ try {
11186
+ cfg = parseConfig(readFileSync14(configPath, "utf-8"));
11187
+ } catch (e) {
11188
+ render.err(`failed to parse ${configPath}: ${e instanceof Error ? e.message : String(e)}`);
11189
+ return EXIT.INVALID_ARGS;
11190
+ }
11191
+ if (!cfg.a || !cfg.b) {
11192
+ render.err("config must define 'a:' and 'b:' sections");
11193
+ return EXIT.INVALID_ARGS;
11194
+ }
11195
+ for (const [name, side] of [["a", cfg.a], ["b", cfg.b]]) {
11196
+ if (!side.broker_url || !side.mesh_id || !side.member_id || !side.pubkey || !side.secret_key || !side.topic) {
11197
+ render.err(`config side '${name}' missing required fields: broker_url, mesh_id, member_id, pubkey, secret_key, topic`);
11198
+ return EXIT.INVALID_ARGS;
11199
+ }
11200
+ }
11201
+ const { Bridge } = await Promise.resolve().then(() => __toESM(require_dist(), 1));
11202
+ const bridge = new Bridge({
11203
+ a: {
11204
+ client: {
11205
+ brokerUrl: cfg.a.broker_url,
11206
+ meshId: cfg.a.mesh_id,
11207
+ memberId: cfg.a.member_id,
11208
+ pubkey: cfg.a.pubkey,
11209
+ secretKey: cfg.a.secret_key,
11210
+ displayName: cfg.a.display_name ?? "bridge",
11211
+ peerType: "connector",
11212
+ channel: "bridge"
11213
+ },
11214
+ topic: cfg.a.topic,
11215
+ role: cfg.a.role
11216
+ },
11217
+ b: {
11218
+ client: {
11219
+ brokerUrl: cfg.b.broker_url,
11220
+ meshId: cfg.b.mesh_id,
11221
+ memberId: cfg.b.member_id,
11222
+ pubkey: cfg.b.pubkey,
11223
+ secretKey: cfg.b.secret_key,
11224
+ displayName: cfg.b.display_name ?? "bridge",
11225
+ peerType: "connector",
11226
+ channel: "bridge"
11227
+ },
11228
+ topic: cfg.b.topic,
11229
+ role: cfg.b.role
11230
+ },
11231
+ maxHops: cfg.max_hops
11232
+ });
11233
+ bridge.on("forwarded", (e) => {
11234
+ process.stdout.write(`${dim(new Date().toISOString())} ${green("→")} ${e.from}→${e.to} hop=${e.hop} ${dim(`${e.bytes}b`)}
11235
+ `);
11236
+ });
11237
+ bridge.on("dropped", (e) => {
11238
+ process.stdout.write(`${dim(new Date().toISOString())} ${yellow("·")} drop from=${e.from} reason=${e.reason}${e.hop >= 0 ? ` hop=${e.hop}` : ""}
11239
+ `);
11240
+ });
11241
+ bridge.on("error", (e) => {
11242
+ process.stderr.write(`${red("✘")} ${e.message}
11243
+ `);
11244
+ });
11245
+ try {
11246
+ await bridge.start();
11247
+ } catch (e) {
11248
+ render.err(`bridge failed to start: ${e instanceof Error ? e.message : String(e)}`);
11249
+ return EXIT.NETWORK_ERROR;
11250
+ }
11251
+ render.ok("bridge running", `${clay("#" + cfg.a.topic)} ${dim("⟷")} ${clay("#" + cfg.b.topic)}`);
11252
+ process.stderr.write(`${dim(` meshes: ${cfg.a.mesh_id.slice(0, 8)} ⟷ ${cfg.b.mesh_id.slice(0, 8)} max_hops: ${cfg.max_hops ?? 2}`)}
11253
+ `);
11254
+ process.stderr.write(`${dim(" Ctrl-C to stop.")}
11255
+
11256
+ `);
11257
+ await new Promise((resolve3) => {
11258
+ const stop = async () => {
11259
+ process.stderr.write(`
11260
+ ${dim("stopping bridge...")}
11261
+ `);
11262
+ await bridge.stop();
11263
+ resolve3();
11264
+ };
11265
+ process.on("SIGINT", stop);
11266
+ process.on("SIGTERM", stop);
11267
+ });
11268
+ return EXIT.SUCCESS;
11269
+ }
11270
+ function bridgeConfigTemplate() {
11271
+ return `# claudemesh bridge config
11272
+ # Spec: .artifacts/specs/2026-05-02-v0.2.0-scope.md
11273
+ #
11274
+ # A bridge holds memberships in two meshes and forwards messages on a
11275
+ # single topic between them. Loop prevention via plaintext hop counter
11276
+ # (visible in message body — minor wart, fixed in v0.3.0).
11277
+ #
11278
+ # Tip: \`claudemesh peer verify\` shows the keys/ids you need below.
11279
+
11280
+ max_hops: 2
11281
+
11282
+ a:
11283
+ broker_url: wss://ic.claudemesh.com/ws
11284
+ mesh_id: <mesh A id>
11285
+ member_id: <bridge member id in mesh A>
11286
+ pubkey: <ed25519 public key hex, 32 bytes>
11287
+ secret_key: <ed25519 secret key hex, 64 bytes>
11288
+ topic: incidents
11289
+ display_name: bridge
11290
+ role: member
11291
+
11292
+ b:
11293
+ broker_url: wss://ic.claudemesh.com/ws
11294
+ mesh_id: <mesh B id>
11295
+ member_id: <bridge member id in mesh B>
11296
+ pubkey: <ed25519 public key hex>
11297
+ secret_key: <ed25519 secret key hex>
11298
+ topic: incidents
11299
+ `;
11300
+ }
11301
+ var init_bridge = __esm(() => {
11302
+ init_render();
11303
+ init_styles();
11304
+ init_exit_codes();
11305
+ });
11306
+
11307
+ // src/commands/apikey.ts
11308
+ var exports_apikey = {};
11309
+ __export(exports_apikey, {
11310
+ runApiKeyRevoke: () => runApiKeyRevoke,
11311
+ runApiKeyList: () => runApiKeyList,
11312
+ runApiKeyCreate: () => runApiKeyCreate
11313
+ });
11314
+ function parseCapabilities(raw) {
11315
+ if (!raw)
11316
+ return ["send", "read"];
11317
+ const parts = raw.split(",").map((s) => s.trim()).filter(Boolean);
11318
+ const valid = new Set(["send", "read", "state_write", "admin"]);
11319
+ return parts.filter((p) => valid.has(p));
11320
+ }
11321
+ async function runApiKeyCreate(label, flags) {
11322
+ if (!label) {
11323
+ render.err("Usage: claudemesh apikey create <label> [--cap send,read] [--topic deploys]");
11324
+ return EXIT.INVALID_ARGS;
11325
+ }
11326
+ const caps = parseCapabilities(flags.cap);
11327
+ if (caps.length === 0) {
11328
+ render.err("at least one capability required: --cap send,read,state_write,admin");
11329
+ return EXIT.INVALID_ARGS;
11330
+ }
11331
+ const topicScopes = flags.topic ? flags.topic.split(",").map((s) => s.trim()).filter(Boolean) : undefined;
11332
+ return await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
11333
+ const result = await client.apiKeyCreate({
11334
+ label,
11335
+ capabilities: caps,
11336
+ topicScopes,
11337
+ expiresAt: flags.expires
11338
+ });
11339
+ if (!result) {
11340
+ render.err("apikey create failed");
11341
+ return EXIT.INTERNAL_ERROR;
11342
+ }
11343
+ if (flags.json) {
11344
+ console.log(JSON.stringify(result, null, 2));
11345
+ return EXIT.SUCCESS;
11346
+ }
11347
+ render.ok("created", `${bold(result.label)} ${dim(result.id.slice(0, 8))}`);
11348
+ process.stdout.write(`
11349
+ ${yellow("⚠ secret shown once — copy it now:")}
11350
+
11351
+ `);
11352
+ process.stdout.write(` ${green(result.secret)}
11353
+
11354
+ `);
11355
+ process.stdout.write(` ${dim(`capabilities: ${result.capabilities.join(", ")}`)}
11356
+ `);
11357
+ if (result.topicScopes?.length) {
11358
+ process.stdout.write(` ${dim(`topics: ${result.topicScopes.map((t) => "#" + t).join(", ")}`)}
11359
+ `);
11360
+ } else {
11361
+ process.stdout.write(` ${dim("topics: all (no scope)")}
11362
+ `);
11363
+ }
11364
+ return EXIT.SUCCESS;
11365
+ });
11366
+ }
11367
+ async function runApiKeyList(flags) {
11368
+ return await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
11369
+ const keys = await client.apiKeyList();
11370
+ if (flags.json) {
11371
+ console.log(JSON.stringify(keys, null, 2));
11372
+ return EXIT.SUCCESS;
11373
+ }
11374
+ if (keys.length === 0) {
11375
+ render.info(dim("no api keys in this mesh."));
11376
+ return EXIT.SUCCESS;
11377
+ }
11378
+ render.section(`api keys (${keys.length})`);
11379
+ for (const k of keys) {
11380
+ const status = k.revokedAt ? red("revoked") : k.expiresAt && new Date(k.expiresAt) < new Date ? yellow("expired") : green("active");
11381
+ const lastUsed = k.lastUsedAt ? new Date(k.lastUsedAt).toLocaleDateString() : "never";
11382
+ const scope = k.topicScopes?.length ? k.topicScopes.map((t) => "#" + t).join(",") : "all topics";
11383
+ process.stdout.write(` ${bold(k.label)} ${status} ${dim(k.id.slice(0, 8))}
11384
+ `);
11385
+ process.stdout.write(` ${dim(`${k.prefix}… caps: ${k.capabilities.join(",")} scope: ${scope} last_used: ${lastUsed}`)}
11386
+ `);
11387
+ }
11388
+ return EXIT.SUCCESS;
11389
+ });
11390
+ }
11391
+ async function runApiKeyRevoke(id, flags) {
11392
+ if (!id) {
11393
+ render.err("Usage: claudemesh apikey revoke <id>");
11394
+ return EXIT.INVALID_ARGS;
11395
+ }
11396
+ return await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
11397
+ await client.apiKeyRevoke(id);
11398
+ if (flags.json)
11399
+ console.log(JSON.stringify({ revoked: id }));
11400
+ else
11401
+ render.ok("revoked", clay(id.slice(0, 8)));
11402
+ return EXIT.SUCCESS;
11403
+ });
11404
+ }
11405
+ var init_apikey = __esm(() => {
11406
+ init_connect();
11407
+ init_render();
11408
+ init_styles();
11409
+ init_exit_codes();
11410
+ });
11411
+
11412
+ // src/commands/topic.ts
11413
+ var exports_topic = {};
11414
+ __export(exports_topic, {
11415
+ runTopicMembers: () => runTopicMembers,
11416
+ runTopicMarkRead: () => runTopicMarkRead,
11417
+ runTopicList: () => runTopicList,
11418
+ runTopicLeave: () => runTopicLeave,
11419
+ runTopicJoin: () => runTopicJoin,
11420
+ runTopicHistory: () => runTopicHistory,
11421
+ runTopicCreate: () => runTopicCreate
11422
+ });
11423
+ async function runTopicCreate(name, flags) {
11424
+ if (!name) {
11425
+ render.err("Usage: claudemesh topic create <name> [--description X] [--visibility V]");
11426
+ return EXIT.INVALID_ARGS;
11427
+ }
11428
+ return await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
11429
+ const result = await client.topicCreate({
11430
+ name,
11431
+ description: flags.description,
11432
+ visibility: flags.visibility
11433
+ });
11434
+ if (!result) {
11435
+ render.err("topic create failed");
11436
+ return EXIT.INTERNAL_ERROR;
11437
+ }
11438
+ if (flags.json) {
11439
+ console.log(JSON.stringify(result));
11440
+ return EXIT.SUCCESS;
11441
+ }
11442
+ if (result.created) {
11443
+ render.ok("created", `${clay("#" + name)} ${dim(result.id.slice(0, 8))}`);
11444
+ } else {
11445
+ render.info(dim(`already exists: #${name} ${result.id.slice(0, 8)}`));
11446
+ }
11447
+ return EXIT.SUCCESS;
11448
+ });
11449
+ }
11450
+ async function runTopicList(flags) {
11451
+ return await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
11452
+ const topics = await client.topicList();
11453
+ if (flags.json) {
11454
+ console.log(JSON.stringify(topics, null, 2));
11455
+ return EXIT.SUCCESS;
11456
+ }
11457
+ if (topics.length === 0) {
11458
+ render.info(dim("no topics in this mesh."));
11459
+ return EXIT.SUCCESS;
11460
+ }
11461
+ render.section(`topics (${topics.length})`);
11462
+ for (const t of topics) {
11463
+ const vis = t.visibility === "public" ? green(t.visibility) : dim(t.visibility);
11464
+ process.stdout.write(` ${clay("#" + t.name)} ${vis} ${dim(`${t.memberCount} member${t.memberCount === 1 ? "" : "s"}`)}
11465
+ `);
11466
+ if (t.description)
11467
+ process.stdout.write(` ${dim(t.description)}
11468
+ `);
11469
+ }
11470
+ return EXIT.SUCCESS;
11471
+ });
11472
+ }
11473
+ async function runTopicJoin(topic, flags) {
11474
+ if (!topic) {
11475
+ render.err("Usage: claudemesh topic join <topic> [--role lead|member|observer]");
11476
+ return EXIT.INVALID_ARGS;
11477
+ }
11478
+ return await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
11479
+ await client.topicJoin(topic, flags.role);
11480
+ if (flags.json)
11481
+ console.log(JSON.stringify({ joined: topic }));
11482
+ else
11483
+ render.ok("joined", clay("#" + topic));
11484
+ return EXIT.SUCCESS;
11485
+ });
11486
+ }
11487
+ async function runTopicLeave(topic, flags) {
11488
+ if (!topic) {
11489
+ render.err("Usage: claudemesh topic leave <topic>");
11490
+ return EXIT.INVALID_ARGS;
11491
+ }
11492
+ return await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
11493
+ await client.topicLeave(topic);
11494
+ if (flags.json)
11495
+ console.log(JSON.stringify({ left: topic }));
11496
+ else
11497
+ render.ok("left", clay("#" + topic));
11498
+ return EXIT.SUCCESS;
11499
+ });
11500
+ }
11501
+ async function runTopicMembers(topic, flags) {
11502
+ if (!topic) {
11503
+ render.err("Usage: claudemesh topic members <topic>");
11504
+ return EXIT.INVALID_ARGS;
11505
+ }
11506
+ return await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
11507
+ const members = await client.topicMembers(topic);
11508
+ if (flags.json) {
11509
+ console.log(JSON.stringify(members, null, 2));
11510
+ return EXIT.SUCCESS;
11511
+ }
11512
+ if (members.length === 0) {
11513
+ render.info(dim(`no members in ${clay("#" + topic)}.`));
11514
+ return EXIT.SUCCESS;
11515
+ }
11516
+ render.section(`${clay("#" + topic)} members (${members.length})`);
11517
+ for (const m of members) {
11518
+ process.stdout.write(` ${bold(m.displayName)} ${dim(m.role)} ${dim(m.pubkey.slice(0, 8))}
11519
+ `);
11520
+ }
11521
+ return EXIT.SUCCESS;
11522
+ });
11523
+ }
11524
+ async function runTopicHistory(topic, flags) {
11525
+ if (!topic) {
11526
+ render.err("Usage: claudemesh topic history <topic> [--limit N] [--before <id>]");
11527
+ return EXIT.INVALID_ARGS;
11528
+ }
11529
+ return await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
11530
+ const limit = flags.limit ? Number(flags.limit) : undefined;
11531
+ const messages = await client.topicHistory({
11532
+ topic,
11533
+ limit,
11534
+ beforeId: flags.before
11535
+ });
11536
+ if (flags.json) {
11537
+ console.log(JSON.stringify(messages, null, 2));
11538
+ return EXIT.SUCCESS;
11539
+ }
11540
+ if (messages.length === 0) {
11541
+ render.info(dim(`no messages in ${clay("#" + topic)}.`));
11542
+ return EXIT.SUCCESS;
11543
+ }
11544
+ const ordered = [...messages].reverse();
11545
+ render.section(`${clay("#" + topic)} history (${ordered.length})`);
11546
+ for (const m of ordered) {
11547
+ const t = new Date(m.createdAt).toLocaleString();
11548
+ process.stdout.write(` ${dim(t)} ${bold(m.senderPubkey.slice(0, 8))} ${dim("(encrypted, " + m.ciphertext.length + "b)")}
11549
+ `);
11550
+ }
11551
+ return EXIT.SUCCESS;
11552
+ });
11553
+ }
11554
+ async function runTopicMarkRead(topic, flags) {
11555
+ if (!topic) {
11556
+ render.err("Usage: claudemesh topic read <topic>");
11557
+ return EXIT.INVALID_ARGS;
11558
+ }
11559
+ return await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
11560
+ await client.topicMarkRead(topic);
11561
+ if (flags.json)
11562
+ console.log(JSON.stringify({ read: topic }));
11563
+ else
11564
+ render.ok("marked read", clay("#" + topic));
11565
+ return EXIT.SUCCESS;
11566
+ });
11567
+ }
11568
+ var init_topic = __esm(() => {
11569
+ init_connect();
11570
+ init_render();
11571
+ init_styles();
11572
+ init_exit_codes();
11573
+ });
11574
+
10381
11575
  // src/mcp/tools/definitions.ts
10382
11576
  var TOOLS;
10383
11577
  var init_definitions = __esm(() => {
@@ -10386,7 +11580,7 @@ var init_definitions = __esm(() => {
10386
11580
 
10387
11581
  // src/services/bridge/server.ts
10388
11582
  import { createServer as createServer2 } from "node:net";
10389
- import { mkdirSync as mkdirSync7, unlinkSync as unlinkSync2, existsSync as existsSync20, chmodSync as chmodSync5 } from "node:fs";
11583
+ import { mkdirSync as mkdirSync7, unlinkSync as unlinkSync2, existsSync as existsSync21, chmodSync as chmodSync5 } from "node:fs";
10390
11584
  async function resolveTarget2(client, to) {
10391
11585
  if (to.startsWith("@") || to === "*" || /^[0-9a-f]{64}$/i.test(to)) {
10392
11586
  return { ok: true, spec: to };
@@ -10500,10 +11694,10 @@ function handleConnection(socket, client) {
10500
11694
  function startBridgeServer(client) {
10501
11695
  const path = socketPath(client.meshSlug);
10502
11696
  const dir = socketDir();
10503
- if (!existsSync20(dir)) {
11697
+ if (!existsSync21(dir)) {
10504
11698
  mkdirSync7(dir, { recursive: true, mode: 448 });
10505
11699
  }
10506
- if (existsSync20(path)) {
11700
+ if (existsSync21(path)) {
10507
11701
  try {
10508
11702
  unlinkSync2(path);
10509
11703
  } catch {}
@@ -11553,6 +12747,21 @@ function classifyInvocation(command, positionals) {
11553
12747
  case "task": {
11554
12748
  return { resource: "task", verb: sub || "list", isWrite: isWrite(sub) };
11555
12749
  }
12750
+ case "topic": {
12751
+ const verb = sub || "list";
12752
+ const writeVerbs = new Set(["create", "join", "leave"]);
12753
+ return { resource: "topic", verb, isWrite: writeVerbs.has(verb) };
12754
+ }
12755
+ case "apikey":
12756
+ case "api-key": {
12757
+ const verb = sub || "list";
12758
+ const writeVerbs = new Set(["create", "revoke"]);
12759
+ return { resource: "apikey", verb, isWrite: writeVerbs.has(verb) };
12760
+ }
12761
+ case "bridge": {
12762
+ const verb = sub || "init";
12763
+ return { resource: "bridge", verb, isWrite: verb === "run" };
12764
+ }
11556
12765
  case "vector":
11557
12766
  case "graph":
11558
12767
  case "context":
@@ -11656,7 +12865,9 @@ var DEFAULT_POLICY = {
11656
12865
  { resource: "watch", verb: "remove", decision: "prompt", reason: "removes URL watcher" },
11657
12866
  { resource: "sql", verb: "execute", decision: "prompt", reason: "raw SQL write to mesh DB" },
11658
12867
  { resource: "graph", verb: "execute", decision: "prompt", reason: "graph mutation" },
11659
- { resource: "mesh", verb: "delete", decision: "prompt", reason: "deletes the mesh for everyone" }
12868
+ { resource: "mesh", verb: "delete", decision: "prompt", reason: "deletes the mesh for everyone" },
12869
+ { resource: "apikey", verb: "create", decision: "prompt", reason: "issues a long-lived credential" },
12870
+ { resource: "apikey", verb: "revoke", decision: "prompt", reason: "irreversibly disables a credential" }
11660
12871
  ]
11661
12872
  };
11662
12873
  var USER_POLICY_PATH = join(homedir(), ".claudemesh", "policy.yaml");
@@ -11915,6 +13126,25 @@ Profile / presence (resource form)
11915
13126
  claudemesh group join @<name> join a group (--role X)
11916
13127
  claudemesh group leave @<name> leave a group
11917
13128
 
13129
+ API keys (REST + external WS auth, v0.2.0)
13130
+ claudemesh apikey create <label> issue [--cap send,read] [--topic deploys]
13131
+ claudemesh apikey list show keys (status, last-used, scope)
13132
+ claudemesh apikey revoke <id> revoke a key
13133
+
13134
+ Bridge (forward a topic between two meshes, v0.2.0)
13135
+ claudemesh bridge init print config template
13136
+ claudemesh bridge run <config> run bridge as a long-lived process
13137
+
13138
+ Topic (conversation scope, v0.2.0)
13139
+ claudemesh topic create <name> create a topic [--description --visibility]
13140
+ claudemesh topic list list topics in the mesh
13141
+ claudemesh topic join <topic> subscribe (via name or id)
13142
+ claudemesh topic leave <topic> unsubscribe
13143
+ claudemesh topic members <t> list topic subscribers
13144
+ claudemesh topic history <t> fetch message history [--limit --before]
13145
+ claudemesh topic read <topic> mark all as read
13146
+ claudemesh send "#topic" "msg" send to a topic
13147
+
11918
13148
  Schedule (resource form)
11919
13149
  claudemesh schedule msg <m> one-shot or recurring (alias: remind)
11920
13150
  claudemesh schedule list list pending
@@ -12618,6 +13848,86 @@ async function main() {
12618
13848
  }
12619
13849
  break;
12620
13850
  }
13851
+ case "bridge": {
13852
+ const sub = positionals[0];
13853
+ if (sub === "run") {
13854
+ const { runBridge: runBridge2 } = await Promise.resolve().then(() => (init_bridge(), exports_bridge));
13855
+ process.exit(await runBridge2(positionals[1] ?? ""));
13856
+ } else if (sub === "init" || sub === "config") {
13857
+ const { bridgeConfigTemplate: bridgeConfigTemplate2 } = await Promise.resolve().then(() => (init_bridge(), exports_bridge));
13858
+ console.log(bridgeConfigTemplate2());
13859
+ process.exit(EXIT.SUCCESS);
13860
+ } else {
13861
+ console.error("Usage: claudemesh bridge <run <config.yaml> | init>");
13862
+ process.exit(EXIT.INVALID_ARGS);
13863
+ }
13864
+ break;
13865
+ }
13866
+ case "apikey":
13867
+ case "api-key": {
13868
+ const sub = positionals[0];
13869
+ const f = {
13870
+ mesh: flags.mesh,
13871
+ json: !!flags.json,
13872
+ cap: flags.cap,
13873
+ topic: flags.topic,
13874
+ expires: flags.expires
13875
+ };
13876
+ const arg = positionals[1] ?? "";
13877
+ if (sub === "create") {
13878
+ const { runApiKeyCreate: runApiKeyCreate2 } = await Promise.resolve().then(() => (init_apikey(), exports_apikey));
13879
+ process.exit(await runApiKeyCreate2(arg, f));
13880
+ } else if (sub === "list") {
13881
+ const { runApiKeyList: runApiKeyList2 } = await Promise.resolve().then(() => (init_apikey(), exports_apikey));
13882
+ process.exit(await runApiKeyList2(f));
13883
+ } else if (sub === "revoke") {
13884
+ const { runApiKeyRevoke: runApiKeyRevoke2 } = await Promise.resolve().then(() => (init_apikey(), exports_apikey));
13885
+ process.exit(await runApiKeyRevoke2(arg, f));
13886
+ } else {
13887
+ console.error("Usage: claudemesh apikey <create|list|revoke>");
13888
+ process.exit(EXIT.INVALID_ARGS);
13889
+ }
13890
+ break;
13891
+ }
13892
+ case "topic": {
13893
+ const sub = positionals[0];
13894
+ const f = {
13895
+ mesh: flags.mesh,
13896
+ json: !!flags.json,
13897
+ description: flags.description,
13898
+ visibility: flags.visibility,
13899
+ role: flags.role,
13900
+ limit: flags.limit,
13901
+ before: flags.before
13902
+ };
13903
+ const arg = positionals[1] ?? "";
13904
+ if (sub === "create") {
13905
+ const { runTopicCreate: runTopicCreate2 } = await Promise.resolve().then(() => (init_topic(), exports_topic));
13906
+ process.exit(await runTopicCreate2(arg, f));
13907
+ } else if (sub === "list") {
13908
+ const { runTopicList: runTopicList2 } = await Promise.resolve().then(() => (init_topic(), exports_topic));
13909
+ process.exit(await runTopicList2(f));
13910
+ } else if (sub === "join") {
13911
+ const { runTopicJoin: runTopicJoin2 } = await Promise.resolve().then(() => (init_topic(), exports_topic));
13912
+ process.exit(await runTopicJoin2(arg, f));
13913
+ } else if (sub === "leave") {
13914
+ const { runTopicLeave: runTopicLeave2 } = await Promise.resolve().then(() => (init_topic(), exports_topic));
13915
+ process.exit(await runTopicLeave2(arg, f));
13916
+ } else if (sub === "members") {
13917
+ const { runTopicMembers: runTopicMembers2 } = await Promise.resolve().then(() => (init_topic(), exports_topic));
13918
+ process.exit(await runTopicMembers2(arg, f));
13919
+ } else if (sub === "history") {
13920
+ const { runTopicHistory: runTopicHistory2 } = await Promise.resolve().then(() => (init_topic(), exports_topic));
13921
+ process.exit(await runTopicHistory2(arg, f));
13922
+ } else if (sub === "read") {
13923
+ const { runTopicMarkRead: runTopicMarkRead2 } = await Promise.resolve().then(() => (init_topic(), exports_topic));
13924
+ process.exit(await runTopicMarkRead2(arg, f));
13925
+ } else {
13926
+ console.error("Usage: claudemesh topic <create|list|join|leave|members|history|read>");
13927
+ process.exit(EXIT.INVALID_ARGS);
13928
+ }
13929
+ break;
13930
+ }
12621
13931
  case "task": {
12622
13932
  const sub = positionals[0];
12623
13933
  const f = { mesh: flags.mesh, json: !!flags.json };
@@ -12666,4 +13976,4 @@ main().catch((err) => {
12666
13976
  process.exit(EXIT.INTERNAL_ERROR);
12667
13977
  });
12668
13978
 
12669
- //# debugId=C75596237B999F1364756E2164756E21
13979
+ //# debugId=E78F8FEDBA22CD8C64756E2164756E21