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.
- package/README.md +10 -2
- package/dist/entrypoints/cli.js +1317 -7
- package/dist/entrypoints/cli.js.map +15 -8
- package/dist/entrypoints/mcp.js +155 -2
- package/dist/entrypoints/mcp.js.map +3 -3
- package/package.json +2 -1
- package/skills/claudemesh/SKILL.md +25 -0
package/dist/entrypoints/cli.js
CHANGED
|
@@ -88,7 +88,7 @@ __export(exports_urls, {
|
|
|
88
88
|
VERSION: () => VERSION,
|
|
89
89
|
URLS: () => URLS
|
|
90
90
|
});
|
|
91
|
-
var URLS, VERSION = "1.
|
|
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 (
|
|
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
|
|
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 (!
|
|
11697
|
+
if (!existsSync21(dir)) {
|
|
10504
11698
|
mkdirSync7(dir, { recursive: true, mode: 448 });
|
|
10505
11699
|
}
|
|
10506
|
-
if (
|
|
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=
|
|
13979
|
+
//# debugId=E78F8FEDBA22CD8C64756E2164756E21
|