claudemesh-cli 1.5.0 → 1.6.1
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 +1404 -18
- package/dist/entrypoints/cli.js.map +17 -10
- package/dist/entrypoints/mcp.js +211 -5
- package/dist/entrypoints/mcp.js.map +4 -4
- 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.1", env;
|
|
92
92
|
var init_urls = __esm(() => {
|
|
93
93
|
URLS = {
|
|
94
94
|
BROKER: process.env.CLAUDEMESH_BROKER_URL ?? "wss://ic.claudemesh.com/ws",
|
|
@@ -751,10 +751,25 @@ function requireToken() {
|
|
|
751
751
|
throw new NotSignedIn;
|
|
752
752
|
return auth.session_token;
|
|
753
753
|
}
|
|
754
|
+
function localView() {
|
|
755
|
+
const cfg = readConfig();
|
|
756
|
+
if (cfg.meshes.length === 0)
|
|
757
|
+
return;
|
|
758
|
+
return {
|
|
759
|
+
config_path: PATHS.CONFIG_FILE,
|
|
760
|
+
meshes: cfg.meshes.map((m) => ({
|
|
761
|
+
slug: m.slug,
|
|
762
|
+
mesh_id: m.meshId,
|
|
763
|
+
member_id: m.memberId,
|
|
764
|
+
pubkey_prefix: m.pubkey.slice(0, 12)
|
|
765
|
+
}))
|
|
766
|
+
};
|
|
767
|
+
}
|
|
754
768
|
async function whoAmI() {
|
|
755
769
|
const auth = getStoredToken();
|
|
770
|
+
const local = localView();
|
|
756
771
|
if (!auth)
|
|
757
|
-
return { signed_in: false };
|
|
772
|
+
return { signed_in: false, local };
|
|
758
773
|
try {
|
|
759
774
|
const profile = await exports_my.getProfile(auth.session_token);
|
|
760
775
|
const meshes = await exports_my.getMeshes(auth.session_token);
|
|
@@ -763,12 +778,13 @@ async function whoAmI() {
|
|
|
763
778
|
signed_in: true,
|
|
764
779
|
user: profile,
|
|
765
780
|
token_source: auth.token_source,
|
|
766
|
-
meshes: { owned, guest: meshes.length - owned }
|
|
781
|
+
meshes: { owned, guest: meshes.length - owned },
|
|
782
|
+
local
|
|
767
783
|
};
|
|
768
784
|
} catch (err) {
|
|
769
785
|
if (err instanceof ApiError && err.isUnauthorized) {
|
|
770
786
|
clearToken();
|
|
771
|
-
return { signed_in: false };
|
|
787
|
+
return { signed_in: false, local };
|
|
772
788
|
}
|
|
773
789
|
throw err;
|
|
774
790
|
}
|
|
@@ -791,6 +807,8 @@ async function register(callbackPort) {
|
|
|
791
807
|
var init_client2 = __esm(() => {
|
|
792
808
|
init_facade3();
|
|
793
809
|
init_facade3();
|
|
810
|
+
init_facade();
|
|
811
|
+
init_paths();
|
|
794
812
|
init_token_store();
|
|
795
813
|
init_errors2();
|
|
796
814
|
});
|
|
@@ -1132,6 +1150,13 @@ class BrokerClient {
|
|
|
1132
1150
|
grantFileAccessResolvers = new Map;
|
|
1133
1151
|
peerFileResponseResolvers = new Map;
|
|
1134
1152
|
peerDirResponseResolvers = new Map;
|
|
1153
|
+
topicCreatedResolvers = new Map;
|
|
1154
|
+
topicListResolvers = new Map;
|
|
1155
|
+
topicMembersResolvers = new Map;
|
|
1156
|
+
topicHistoryResolvers = new Map;
|
|
1157
|
+
apiKeyCreatedResolvers = new Map;
|
|
1158
|
+
apiKeyListResolvers = new Map;
|
|
1159
|
+
apiKeyRevokeResolvers = new Map;
|
|
1135
1160
|
sharedDirs = [process.cwd()];
|
|
1136
1161
|
_serviceCatalog = [];
|
|
1137
1162
|
get serviceCatalog() {
|
|
@@ -1434,6 +1459,128 @@ class BrokerClient {
|
|
|
1434
1459
|
return;
|
|
1435
1460
|
this.ws.send(JSON.stringify({ type: "leave_group", name }));
|
|
1436
1461
|
}
|
|
1462
|
+
async topicCreate(args) {
|
|
1463
|
+
if (!this.ws || this.ws.readyState !== this.ws.OPEN)
|
|
1464
|
+
return null;
|
|
1465
|
+
return new Promise((resolve) => {
|
|
1466
|
+
const reqId = this.makeReqId();
|
|
1467
|
+
this.topicCreatedResolvers.set(reqId, {
|
|
1468
|
+
resolve,
|
|
1469
|
+
timer: setTimeout(() => {
|
|
1470
|
+
if (this.topicCreatedResolvers.delete(reqId))
|
|
1471
|
+
resolve(null);
|
|
1472
|
+
}, 5000)
|
|
1473
|
+
});
|
|
1474
|
+
this.ws.send(JSON.stringify({ type: "topic_create", _reqId: reqId, ...args }));
|
|
1475
|
+
});
|
|
1476
|
+
}
|
|
1477
|
+
async topicList() {
|
|
1478
|
+
if (!this.ws || this.ws.readyState !== this.ws.OPEN)
|
|
1479
|
+
return [];
|
|
1480
|
+
return new Promise((resolve) => {
|
|
1481
|
+
const reqId = this.makeReqId();
|
|
1482
|
+
this.topicListResolvers.set(reqId, {
|
|
1483
|
+
resolve,
|
|
1484
|
+
timer: setTimeout(() => {
|
|
1485
|
+
if (this.topicListResolvers.delete(reqId))
|
|
1486
|
+
resolve([]);
|
|
1487
|
+
}, 5000)
|
|
1488
|
+
});
|
|
1489
|
+
this.ws.send(JSON.stringify({ type: "topic_list", _reqId: reqId }));
|
|
1490
|
+
});
|
|
1491
|
+
}
|
|
1492
|
+
async topicJoin(topic, role) {
|
|
1493
|
+
if (!this.ws || this.ws.readyState !== this.ws.OPEN)
|
|
1494
|
+
return;
|
|
1495
|
+
this.ws.send(JSON.stringify({ type: "topic_join", topic, role }));
|
|
1496
|
+
}
|
|
1497
|
+
async topicLeave(topic) {
|
|
1498
|
+
if (!this.ws || this.ws.readyState !== this.ws.OPEN)
|
|
1499
|
+
return;
|
|
1500
|
+
this.ws.send(JSON.stringify({ type: "topic_leave", topic }));
|
|
1501
|
+
}
|
|
1502
|
+
async topicMembers(topic) {
|
|
1503
|
+
if (!this.ws || this.ws.readyState !== this.ws.OPEN)
|
|
1504
|
+
return [];
|
|
1505
|
+
return new Promise((resolve) => {
|
|
1506
|
+
const reqId = this.makeReqId();
|
|
1507
|
+
this.topicMembersResolvers.set(reqId, {
|
|
1508
|
+
resolve,
|
|
1509
|
+
timer: setTimeout(() => {
|
|
1510
|
+
if (this.topicMembersResolvers.delete(reqId))
|
|
1511
|
+
resolve([]);
|
|
1512
|
+
}, 5000)
|
|
1513
|
+
});
|
|
1514
|
+
this.ws.send(JSON.stringify({ type: "topic_members", _reqId: reqId, topic }));
|
|
1515
|
+
});
|
|
1516
|
+
}
|
|
1517
|
+
async topicHistory(args) {
|
|
1518
|
+
if (!this.ws || this.ws.readyState !== this.ws.OPEN)
|
|
1519
|
+
return [];
|
|
1520
|
+
return new Promise((resolve) => {
|
|
1521
|
+
const reqId = this.makeReqId();
|
|
1522
|
+
this.topicHistoryResolvers.set(reqId, {
|
|
1523
|
+
resolve,
|
|
1524
|
+
timer: setTimeout(() => {
|
|
1525
|
+
if (this.topicHistoryResolvers.delete(reqId))
|
|
1526
|
+
resolve([]);
|
|
1527
|
+
}, 5000)
|
|
1528
|
+
});
|
|
1529
|
+
this.ws.send(JSON.stringify({ type: "topic_history", _reqId: reqId, ...args }));
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1532
|
+
async topicMarkRead(topic) {
|
|
1533
|
+
if (!this.ws || this.ws.readyState !== this.ws.OPEN)
|
|
1534
|
+
return;
|
|
1535
|
+
this.ws.send(JSON.stringify({ type: "topic_mark_read", topic }));
|
|
1536
|
+
}
|
|
1537
|
+
async apiKeyCreate(args) {
|
|
1538
|
+
if (!this.ws || this.ws.readyState !== this.ws.OPEN)
|
|
1539
|
+
return null;
|
|
1540
|
+
return new Promise((resolve) => {
|
|
1541
|
+
const reqId = this.makeReqId();
|
|
1542
|
+
this.apiKeyCreatedResolvers.set(reqId, {
|
|
1543
|
+
resolve,
|
|
1544
|
+
timer: setTimeout(() => {
|
|
1545
|
+
if (this.apiKeyCreatedResolvers.delete(reqId))
|
|
1546
|
+
resolve(null);
|
|
1547
|
+
}, 5000)
|
|
1548
|
+
});
|
|
1549
|
+
this.ws.send(JSON.stringify({ type: "apikey_create", _reqId: reqId, ...args }));
|
|
1550
|
+
});
|
|
1551
|
+
}
|
|
1552
|
+
async apiKeyList() {
|
|
1553
|
+
if (!this.ws || this.ws.readyState !== this.ws.OPEN)
|
|
1554
|
+
return [];
|
|
1555
|
+
return new Promise((resolve) => {
|
|
1556
|
+
const reqId = this.makeReqId();
|
|
1557
|
+
this.apiKeyListResolvers.set(reqId, {
|
|
1558
|
+
resolve,
|
|
1559
|
+
timer: setTimeout(() => {
|
|
1560
|
+
if (this.apiKeyListResolvers.delete(reqId))
|
|
1561
|
+
resolve([]);
|
|
1562
|
+
}, 5000)
|
|
1563
|
+
});
|
|
1564
|
+
this.ws.send(JSON.stringify({ type: "apikey_list", _reqId: reqId }));
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1567
|
+
async apiKeyRevoke(id) {
|
|
1568
|
+
if (!this.ws || this.ws.readyState !== this.ws.OPEN) {
|
|
1569
|
+
return { ok: false, code: "not_connected", message: "broker not connected" };
|
|
1570
|
+
}
|
|
1571
|
+
return new Promise((resolve) => {
|
|
1572
|
+
const reqId = this.makeReqId();
|
|
1573
|
+
this.apiKeyRevokeResolvers.set(reqId, {
|
|
1574
|
+
resolve,
|
|
1575
|
+
timer: setTimeout(() => {
|
|
1576
|
+
if (this.apiKeyRevokeResolvers.delete(reqId)) {
|
|
1577
|
+
resolve({ ok: false, code: "timeout", message: "broker did not respond within 5s" });
|
|
1578
|
+
}
|
|
1579
|
+
}, 5000)
|
|
1580
|
+
});
|
|
1581
|
+
this.ws.send(JSON.stringify({ type: "apikey_revoke", id, _reqId: reqId }));
|
|
1582
|
+
});
|
|
1583
|
+
}
|
|
1437
1584
|
async setState(key, value) {
|
|
1438
1585
|
if (!this.ws || this.ws.readyState !== this.ws.OPEN)
|
|
1439
1586
|
return;
|
|
@@ -2514,6 +2661,65 @@ class BrokerClient {
|
|
|
2514
2661
|
this.resolveFromMap(this.listPeersResolvers, msgReqId, peers);
|
|
2515
2662
|
return;
|
|
2516
2663
|
}
|
|
2664
|
+
if (msg.type === "topic_created") {
|
|
2665
|
+
const r = msg.topic ?? {};
|
|
2666
|
+
this.resolveFromMap(this.topicCreatedResolvers, msgReqId, {
|
|
2667
|
+
id: r.id,
|
|
2668
|
+
name: r.name,
|
|
2669
|
+
created: !!msg.created
|
|
2670
|
+
});
|
|
2671
|
+
return;
|
|
2672
|
+
}
|
|
2673
|
+
if (msg.type === "topic_list_response") {
|
|
2674
|
+
this.resolveFromMap(this.topicListResolvers, msgReqId, msg.topics ?? []);
|
|
2675
|
+
return;
|
|
2676
|
+
}
|
|
2677
|
+
if (msg.type === "topic_members_response") {
|
|
2678
|
+
this.resolveFromMap(this.topicMembersResolvers, msgReqId, msg.members ?? []);
|
|
2679
|
+
return;
|
|
2680
|
+
}
|
|
2681
|
+
if (msg.type === "topic_history_response") {
|
|
2682
|
+
this.resolveFromMap(this.topicHistoryResolvers, msgReqId, msg.messages ?? []);
|
|
2683
|
+
return;
|
|
2684
|
+
}
|
|
2685
|
+
if (msg.type === "apikey_created") {
|
|
2686
|
+
this.resolveFromMap(this.apiKeyCreatedResolvers, msgReqId, {
|
|
2687
|
+
id: String(msg.id ?? ""),
|
|
2688
|
+
secret: String(msg.secret ?? ""),
|
|
2689
|
+
label: String(msg.label ?? ""),
|
|
2690
|
+
prefix: String(msg.prefix ?? ""),
|
|
2691
|
+
capabilities: msg.capabilities ?? [],
|
|
2692
|
+
topicScopes: msg.topicScopes ?? null,
|
|
2693
|
+
createdAt: String(msg.createdAt ?? "")
|
|
2694
|
+
});
|
|
2695
|
+
return;
|
|
2696
|
+
}
|
|
2697
|
+
if (msg.type === "apikey_list_response") {
|
|
2698
|
+
this.resolveFromMap(this.apiKeyListResolvers, msgReqId, msg.keys ?? []);
|
|
2699
|
+
return;
|
|
2700
|
+
}
|
|
2701
|
+
if (msg.type === "apikey_revoke_response") {
|
|
2702
|
+
const status = String(msg.status ?? "");
|
|
2703
|
+
if (status === "revoked") {
|
|
2704
|
+
this.resolveFromMap(this.apiKeyRevokeResolvers, msgReqId, {
|
|
2705
|
+
ok: true,
|
|
2706
|
+
id: String(msg.id ?? "")
|
|
2707
|
+
});
|
|
2708
|
+
} else if (status === "not_found") {
|
|
2709
|
+
this.resolveFromMap(this.apiKeyRevokeResolvers, msgReqId, {
|
|
2710
|
+
ok: false,
|
|
2711
|
+
code: "not_found",
|
|
2712
|
+
message: "no api key matches that id in this mesh"
|
|
2713
|
+
});
|
|
2714
|
+
} else if (status === "not_unique") {
|
|
2715
|
+
this.resolveFromMap(this.apiKeyRevokeResolvers, msgReqId, {
|
|
2716
|
+
ok: false,
|
|
2717
|
+
code: "not_unique",
|
|
2718
|
+
message: `prefix matches ${Number(msg.matches ?? 0)} keys; use the full id`
|
|
2719
|
+
});
|
|
2720
|
+
}
|
|
2721
|
+
return;
|
|
2722
|
+
}
|
|
2517
2723
|
if (msg.type === "push") {
|
|
2518
2724
|
this._statsCounters.messagesIn++;
|
|
2519
2725
|
const nonce = String(msg.nonce ?? "");
|
|
@@ -6598,7 +6804,17 @@ async function runSend(flags, to, message) {
|
|
|
6598
6804
|
}
|
|
6599
6805
|
await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
|
|
6600
6806
|
let targetSpec = to;
|
|
6601
|
-
if (
|
|
6807
|
+
if (to.startsWith("#") && !/^#[0-9a-z_-]{20,}$/i.test(to)) {
|
|
6808
|
+
const name = to.slice(1);
|
|
6809
|
+
const topics = await client.topicList();
|
|
6810
|
+
const match = topics.find((t) => t.name === name);
|
|
6811
|
+
if (!match) {
|
|
6812
|
+
const names = topics.map((t) => "#" + t.name).join(", ");
|
|
6813
|
+
render.err(`Topic "${to}" not found.`, `topics: ${names || "(none)"}`);
|
|
6814
|
+
process.exit(1);
|
|
6815
|
+
}
|
|
6816
|
+
targetSpec = "#" + match.id;
|
|
6817
|
+
} else if (!to.startsWith("@") && !to.startsWith("#") && to !== "*" && !/^[0-9a-f]{64}$/i.test(to)) {
|
|
6602
6818
|
const peers = await client.listPeers();
|
|
6603
6819
|
const match = peers.find((p) => p.displayName.toLowerCase() === to.toLowerCase());
|
|
6604
6820
|
if (!match) {
|
|
@@ -7358,18 +7574,33 @@ async function whoami(opts) {
|
|
|
7358
7574
|
const result = await whoAmI();
|
|
7359
7575
|
if (opts.json) {
|
|
7360
7576
|
console.log(JSON.stringify({ schema_version: "1.0", ...result }, null, 2));
|
|
7361
|
-
return EXIT.SUCCESS;
|
|
7577
|
+
return result.signed_in || result.local ? EXIT.SUCCESS : EXIT.AUTH_FAILED;
|
|
7362
7578
|
}
|
|
7363
|
-
if (!result.signed_in) {
|
|
7364
|
-
render.err("Not signed in", "Run `claudemesh login` to sign in.");
|
|
7579
|
+
if (!result.signed_in && !result.local) {
|
|
7580
|
+
render.err("Not signed in", "Run `claudemesh login` to sign in or `claudemesh <invite>` to join.");
|
|
7365
7581
|
return EXIT.AUTH_FAILED;
|
|
7366
7582
|
}
|
|
7367
7583
|
render.section("whoami");
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
7372
|
-
|
|
7584
|
+
if (result.signed_in) {
|
|
7585
|
+
render.kv([
|
|
7586
|
+
["user", `${bold(result.user.display_name)} ${dim(`(${result.user.email})`)}`],
|
|
7587
|
+
["token", `${result.token_source} ${dim("(~/.claudemesh/auth.json)")}`],
|
|
7588
|
+
...result.meshes ? [["meshes", `${result.meshes.owned} owned · ${result.meshes.guest} guest`]] : []
|
|
7589
|
+
]);
|
|
7590
|
+
} else {
|
|
7591
|
+
render.kv([
|
|
7592
|
+
["web", dim("not signed in · run `claudemesh login` for account features")]
|
|
7593
|
+
]);
|
|
7594
|
+
}
|
|
7595
|
+
if (result.local) {
|
|
7596
|
+
render.blank();
|
|
7597
|
+
render.kv([
|
|
7598
|
+
["local", `${result.local.meshes.length} mesh${result.local.meshes.length === 1 ? "" : "es"} · ${dim(result.local.config_path)}`]
|
|
7599
|
+
]);
|
|
7600
|
+
for (const m of result.local.meshes) {
|
|
7601
|
+
console.log(` ${clay("●")} ${bold(m.slug)} ${dim(`member ${m.member_id.slice(0, 8)}… pk ${m.pubkey_prefix}…`)}`);
|
|
7602
|
+
}
|
|
7603
|
+
}
|
|
7373
7604
|
render.blank();
|
|
7374
7605
|
return EXIT.SUCCESS;
|
|
7375
7606
|
}
|
|
@@ -10378,6 +10609,1045 @@ var init_platform_actions = __esm(() => {
|
|
|
10378
10609
|
init_exit_codes();
|
|
10379
10610
|
});
|
|
10380
10611
|
|
|
10612
|
+
// ../../packages/sdk/dist/crypto.js
|
|
10613
|
+
var require_crypto = __commonJS((exports) => {
|
|
10614
|
+
var __importDefault = exports && exports.__importDefault || function(mod) {
|
|
10615
|
+
return mod && mod.__esModule ? mod : { default: mod };
|
|
10616
|
+
};
|
|
10617
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10618
|
+
exports.generateKeyPair = generateKeyPair;
|
|
10619
|
+
exports.signHello = signHello2;
|
|
10620
|
+
exports.isDirectTarget = isDirectTarget2;
|
|
10621
|
+
exports.encryptDirect = encryptDirect2;
|
|
10622
|
+
exports.decryptDirect = decryptDirect2;
|
|
10623
|
+
var libsodium_wrappers_1 = __importDefault(__require("libsodium-wrappers"));
|
|
10624
|
+
var ready2 = false;
|
|
10625
|
+
async function ensureSodium3() {
|
|
10626
|
+
if (!ready2) {
|
|
10627
|
+
await libsodium_wrappers_1.default.ready;
|
|
10628
|
+
ready2 = true;
|
|
10629
|
+
}
|
|
10630
|
+
return libsodium_wrappers_1.default;
|
|
10631
|
+
}
|
|
10632
|
+
async function generateKeyPair() {
|
|
10633
|
+
const s = await ensureSodium3();
|
|
10634
|
+
const kp = s.crypto_sign_keypair();
|
|
10635
|
+
return {
|
|
10636
|
+
publicKey: s.to_hex(kp.publicKey),
|
|
10637
|
+
secretKey: s.to_hex(kp.privateKey)
|
|
10638
|
+
};
|
|
10639
|
+
}
|
|
10640
|
+
async function signHello2(meshId, memberId, pubkey, secretKeyHex) {
|
|
10641
|
+
const s = await ensureSodium3();
|
|
10642
|
+
const timestamp2 = Date.now();
|
|
10643
|
+
const canonical = `${meshId}|${memberId}|${pubkey}|${timestamp2}`;
|
|
10644
|
+
const sig = s.crypto_sign_detached(s.from_string(canonical), s.from_hex(secretKeyHex));
|
|
10645
|
+
return { timestamp: timestamp2, signature: s.to_hex(sig) };
|
|
10646
|
+
}
|
|
10647
|
+
var HEX_PUBKEY2 = /^[0-9a-f]{64}$/;
|
|
10648
|
+
function isDirectTarget2(targetSpec) {
|
|
10649
|
+
return HEX_PUBKEY2.test(targetSpec);
|
|
10650
|
+
}
|
|
10651
|
+
async function encryptDirect2(message, recipientPubkeyHex, senderSecretKeyHex) {
|
|
10652
|
+
const s = await ensureSodium3();
|
|
10653
|
+
const recipientPub = s.crypto_sign_ed25519_pk_to_curve25519(s.from_hex(recipientPubkeyHex));
|
|
10654
|
+
const senderSec = s.crypto_sign_ed25519_sk_to_curve25519(s.from_hex(senderSecretKeyHex));
|
|
10655
|
+
const nonce = s.randombytes_buf(s.crypto_box_NONCEBYTES);
|
|
10656
|
+
const ciphertext = s.crypto_box_easy(s.from_string(message), nonce, recipientPub, senderSec);
|
|
10657
|
+
return {
|
|
10658
|
+
nonce: s.to_base64(nonce, s.base64_variants.ORIGINAL),
|
|
10659
|
+
ciphertext: s.to_base64(ciphertext, s.base64_variants.ORIGINAL)
|
|
10660
|
+
};
|
|
10661
|
+
}
|
|
10662
|
+
async function decryptDirect2(envelope, senderPubkeyHex, recipientSecretKeyHex) {
|
|
10663
|
+
const s = await ensureSodium3();
|
|
10664
|
+
try {
|
|
10665
|
+
const senderPub = s.crypto_sign_ed25519_pk_to_curve25519(s.from_hex(senderPubkeyHex));
|
|
10666
|
+
const recipientSec = s.crypto_sign_ed25519_sk_to_curve25519(s.from_hex(recipientSecretKeyHex));
|
|
10667
|
+
const nonce = s.from_base64(envelope.nonce, s.base64_variants.ORIGINAL);
|
|
10668
|
+
const ciphertext = s.from_base64(envelope.ciphertext, s.base64_variants.ORIGINAL);
|
|
10669
|
+
const plain = s.crypto_box_open_easy(ciphertext, nonce, senderPub, recipientSec);
|
|
10670
|
+
return s.to_string(plain);
|
|
10671
|
+
} catch {
|
|
10672
|
+
return null;
|
|
10673
|
+
}
|
|
10674
|
+
}
|
|
10675
|
+
});
|
|
10676
|
+
|
|
10677
|
+
// ../../packages/sdk/dist/client.js
|
|
10678
|
+
var require_client = __commonJS((exports) => {
|
|
10679
|
+
var __importDefault = exports && exports.__importDefault || function(mod) {
|
|
10680
|
+
return mod && mod.__esModule ? mod : { default: mod };
|
|
10681
|
+
};
|
|
10682
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10683
|
+
exports.MeshClient = undefined;
|
|
10684
|
+
var node_events_1 = __require("node:events");
|
|
10685
|
+
var node_crypto_1 = __require("node:crypto");
|
|
10686
|
+
var ws_1 = __importDefault(__require("ws"));
|
|
10687
|
+
var crypto_js_1 = require_crypto();
|
|
10688
|
+
var MAX_QUEUED2 = 100;
|
|
10689
|
+
var HELLO_ACK_TIMEOUT_MS2 = 5000;
|
|
10690
|
+
var BACKOFF_CAPS2 = [1000, 2000, 4000, 8000, 16000, 30000];
|
|
10691
|
+
|
|
10692
|
+
class MeshClient extends node_events_1.EventEmitter {
|
|
10693
|
+
opts;
|
|
10694
|
+
ws = null;
|
|
10695
|
+
_status = "closed";
|
|
10696
|
+
pendingSends = new Map;
|
|
10697
|
+
outbound = [];
|
|
10698
|
+
closed = false;
|
|
10699
|
+
reconnectAttempt = 0;
|
|
10700
|
+
helloTimer = null;
|
|
10701
|
+
reconnectTimer = null;
|
|
10702
|
+
sessionPubkey = null;
|
|
10703
|
+
sessionSecretKey = null;
|
|
10704
|
+
listPeersResolvers = new Map;
|
|
10705
|
+
stateResolvers = new Map;
|
|
10706
|
+
constructor(opts) {
|
|
10707
|
+
super();
|
|
10708
|
+
this.opts = opts;
|
|
10709
|
+
}
|
|
10710
|
+
get status() {
|
|
10711
|
+
return this._status;
|
|
10712
|
+
}
|
|
10713
|
+
get pubkey() {
|
|
10714
|
+
return this.sessionPubkey;
|
|
10715
|
+
}
|
|
10716
|
+
async connect() {
|
|
10717
|
+
if (this.closed)
|
|
10718
|
+
throw new Error("client is closed");
|
|
10719
|
+
this._status = "connecting";
|
|
10720
|
+
const ws = new ws_1.default(this.opts.brokerUrl);
|
|
10721
|
+
this.ws = ws;
|
|
10722
|
+
return new Promise((resolve3, reject) => {
|
|
10723
|
+
const onOpen = async () => {
|
|
10724
|
+
this.debug("ws open -> generating session keypair + signing hello");
|
|
10725
|
+
try {
|
|
10726
|
+
if (!this.sessionPubkey) {
|
|
10727
|
+
const sessionKP = await (0, crypto_js_1.generateKeyPair)();
|
|
10728
|
+
this.sessionPubkey = sessionKP.publicKey;
|
|
10729
|
+
this.sessionSecretKey = sessionKP.secretKey;
|
|
10730
|
+
}
|
|
10731
|
+
const { timestamp: timestamp2, signature } = await (0, crypto_js_1.signHello)(this.opts.meshId, this.opts.memberId, this.opts.pubkey, this.opts.secretKey);
|
|
10732
|
+
ws.send(JSON.stringify({
|
|
10733
|
+
type: "hello",
|
|
10734
|
+
meshId: this.opts.meshId,
|
|
10735
|
+
memberId: this.opts.memberId,
|
|
10736
|
+
pubkey: this.opts.pubkey,
|
|
10737
|
+
sessionPubkey: this.sessionPubkey,
|
|
10738
|
+
displayName: this.opts.displayName,
|
|
10739
|
+
sessionId: `sdk-${process.pid}-${Date.now()}`,
|
|
10740
|
+
pid: process.pid,
|
|
10741
|
+
peerType: this.opts.peerType ?? "connector",
|
|
10742
|
+
channel: this.opts.channel ?? "sdk",
|
|
10743
|
+
timestamp: timestamp2,
|
|
10744
|
+
signature
|
|
10745
|
+
}));
|
|
10746
|
+
} catch (e) {
|
|
10747
|
+
reject(new Error(`hello sign failed: ${e instanceof Error ? e.message : e}`));
|
|
10748
|
+
return;
|
|
10749
|
+
}
|
|
10750
|
+
this.helloTimer = setTimeout(() => {
|
|
10751
|
+
this.debug("hello_ack timeout");
|
|
10752
|
+
ws.close();
|
|
10753
|
+
reject(new Error("hello_ack timeout"));
|
|
10754
|
+
}, HELLO_ACK_TIMEOUT_MS2);
|
|
10755
|
+
};
|
|
10756
|
+
const onMessage = (raw) => {
|
|
10757
|
+
let msg;
|
|
10758
|
+
try {
|
|
10759
|
+
msg = JSON.parse(raw.toString());
|
|
10760
|
+
} catch {
|
|
10761
|
+
return;
|
|
10762
|
+
}
|
|
10763
|
+
if (msg.type === "hello_ack") {
|
|
10764
|
+
if (this.helloTimer)
|
|
10765
|
+
clearTimeout(this.helloTimer);
|
|
10766
|
+
this.helloTimer = null;
|
|
10767
|
+
this._status = "open";
|
|
10768
|
+
this.reconnectAttempt = 0;
|
|
10769
|
+
this.flushOutbound();
|
|
10770
|
+
this.emit("connected");
|
|
10771
|
+
resolve3();
|
|
10772
|
+
return;
|
|
10773
|
+
}
|
|
10774
|
+
this.handleServerMessage(msg);
|
|
10775
|
+
};
|
|
10776
|
+
const onClose = () => {
|
|
10777
|
+
if (this.helloTimer)
|
|
10778
|
+
clearTimeout(this.helloTimer);
|
|
10779
|
+
this.helloTimer = null;
|
|
10780
|
+
const wasOpen = this._status === "open" || this._status === "reconnecting";
|
|
10781
|
+
this.ws = null;
|
|
10782
|
+
if (!wasOpen && this._status === "connecting") {
|
|
10783
|
+
reject(new Error("ws closed before hello_ack"));
|
|
10784
|
+
}
|
|
10785
|
+
if (!this.closed) {
|
|
10786
|
+
this.emit("disconnected");
|
|
10787
|
+
this.scheduleReconnect();
|
|
10788
|
+
} else {
|
|
10789
|
+
this._status = "closed";
|
|
10790
|
+
this.emit("disconnected");
|
|
10791
|
+
}
|
|
10792
|
+
};
|
|
10793
|
+
const onError = (err) => {
|
|
10794
|
+
this.debug(`ws error: ${err.message}`);
|
|
10795
|
+
};
|
|
10796
|
+
ws.on("open", onOpen);
|
|
10797
|
+
ws.on("message", onMessage);
|
|
10798
|
+
ws.on("close", onClose);
|
|
10799
|
+
ws.on("error", onError);
|
|
10800
|
+
});
|
|
10801
|
+
}
|
|
10802
|
+
disconnect() {
|
|
10803
|
+
this.closed = true;
|
|
10804
|
+
if (this.helloTimer)
|
|
10805
|
+
clearTimeout(this.helloTimer);
|
|
10806
|
+
if (this.reconnectTimer)
|
|
10807
|
+
clearTimeout(this.reconnectTimer);
|
|
10808
|
+
if (this.ws) {
|
|
10809
|
+
try {
|
|
10810
|
+
this.ws.close();
|
|
10811
|
+
} catch {}
|
|
10812
|
+
}
|
|
10813
|
+
this._status = "closed";
|
|
10814
|
+
}
|
|
10815
|
+
async send(to, message, priority = "next") {
|
|
10816
|
+
let targetSpec = to;
|
|
10817
|
+
if (!(0, crypto_js_1.isDirectTarget)(to) && to !== "*" && !to.startsWith("@") && !to.startsWith("#")) {
|
|
10818
|
+
const peers = await this.listPeers();
|
|
10819
|
+
const match = peers.find((p) => p.displayName.toLowerCase() === to.toLowerCase());
|
|
10820
|
+
if (match) {
|
|
10821
|
+
targetSpec = match.pubkey;
|
|
10822
|
+
}
|
|
10823
|
+
}
|
|
10824
|
+
const id = (0, node_crypto_1.randomBytes)(8).toString("hex");
|
|
10825
|
+
let nonce;
|
|
10826
|
+
let ciphertext;
|
|
10827
|
+
if ((0, crypto_js_1.isDirectTarget)(targetSpec)) {
|
|
10828
|
+
const env2 = await (0, crypto_js_1.encryptDirect)(message, targetSpec, this.sessionSecretKey ?? this.opts.secretKey);
|
|
10829
|
+
nonce = env2.nonce;
|
|
10830
|
+
ciphertext = env2.ciphertext;
|
|
10831
|
+
} else {
|
|
10832
|
+
nonce = (0, node_crypto_1.randomBytes)(24).toString("base64");
|
|
10833
|
+
ciphertext = Buffer.from(message, "utf-8").toString("base64");
|
|
10834
|
+
}
|
|
10835
|
+
return new Promise((resolve3) => {
|
|
10836
|
+
if (this.pendingSends.size >= MAX_QUEUED2) {
|
|
10837
|
+
resolve3({ ok: false, error: "outbound queue full" });
|
|
10838
|
+
return;
|
|
10839
|
+
}
|
|
10840
|
+
this.pendingSends.set(id, {
|
|
10841
|
+
id,
|
|
10842
|
+
targetSpec,
|
|
10843
|
+
priority,
|
|
10844
|
+
nonce,
|
|
10845
|
+
ciphertext,
|
|
10846
|
+
resolve: resolve3
|
|
10847
|
+
});
|
|
10848
|
+
const dispatch = () => {
|
|
10849
|
+
if (!this.ws || this.ws.readyState !== ws_1.default.OPEN)
|
|
10850
|
+
return;
|
|
10851
|
+
this.ws.send(JSON.stringify({
|
|
10852
|
+
type: "send",
|
|
10853
|
+
id,
|
|
10854
|
+
targetSpec,
|
|
10855
|
+
priority,
|
|
10856
|
+
nonce,
|
|
10857
|
+
ciphertext
|
|
10858
|
+
}));
|
|
10859
|
+
};
|
|
10860
|
+
if (this._status === "open")
|
|
10861
|
+
dispatch();
|
|
10862
|
+
else {
|
|
10863
|
+
if (this.outbound.length >= MAX_QUEUED2) {
|
|
10864
|
+
this.pendingSends.delete(id);
|
|
10865
|
+
resolve3({ ok: false, error: "outbound queue full" });
|
|
10866
|
+
return;
|
|
10867
|
+
}
|
|
10868
|
+
this.outbound.push(dispatch);
|
|
10869
|
+
}
|
|
10870
|
+
setTimeout(() => {
|
|
10871
|
+
if (this.pendingSends.has(id)) {
|
|
10872
|
+
this.pendingSends.delete(id);
|
|
10873
|
+
resolve3({ ok: false, error: "ack timeout" });
|
|
10874
|
+
}
|
|
10875
|
+
}, 1e4);
|
|
10876
|
+
});
|
|
10877
|
+
}
|
|
10878
|
+
async broadcast(message, priority = "next") {
|
|
10879
|
+
return this.send("*", message, priority);
|
|
10880
|
+
}
|
|
10881
|
+
async listPeers() {
|
|
10882
|
+
if (!this.ws || this.ws.readyState !== ws_1.default.OPEN)
|
|
10883
|
+
return [];
|
|
10884
|
+
return new Promise((resolve3) => {
|
|
10885
|
+
const reqId = this.makeReqId();
|
|
10886
|
+
this.listPeersResolvers.set(reqId, {
|
|
10887
|
+
resolve: resolve3,
|
|
10888
|
+
timer: setTimeout(() => {
|
|
10889
|
+
if (this.listPeersResolvers.delete(reqId))
|
|
10890
|
+
resolve3([]);
|
|
10891
|
+
}, 5000)
|
|
10892
|
+
});
|
|
10893
|
+
this.ws.send(JSON.stringify({ type: "list_peers", _reqId: reqId }));
|
|
10894
|
+
});
|
|
10895
|
+
}
|
|
10896
|
+
async getState(key) {
|
|
10897
|
+
if (!this.ws || this.ws.readyState !== ws_1.default.OPEN)
|
|
10898
|
+
return null;
|
|
10899
|
+
return new Promise((resolve3) => {
|
|
10900
|
+
const reqId = this.makeReqId();
|
|
10901
|
+
this.stateResolvers.set(reqId, {
|
|
10902
|
+
resolve: (result) => resolve3(result ? String(result.value) : null),
|
|
10903
|
+
timer: setTimeout(() => {
|
|
10904
|
+
if (this.stateResolvers.delete(reqId))
|
|
10905
|
+
resolve3(null);
|
|
10906
|
+
}, 5000)
|
|
10907
|
+
});
|
|
10908
|
+
this.ws.send(JSON.stringify({ type: "get_state", key, _reqId: reqId }));
|
|
10909
|
+
});
|
|
10910
|
+
}
|
|
10911
|
+
async setState(key, value) {
|
|
10912
|
+
if (!this.ws || this.ws.readyState !== ws_1.default.OPEN)
|
|
10913
|
+
return;
|
|
10914
|
+
this.ws.send(JSON.stringify({ type: "set_state", key, value }));
|
|
10915
|
+
}
|
|
10916
|
+
async setSummary(summary) {
|
|
10917
|
+
if (!this.ws || this.ws.readyState !== ws_1.default.OPEN)
|
|
10918
|
+
return;
|
|
10919
|
+
this.ws.send(JSON.stringify({ type: "set_summary", summary }));
|
|
10920
|
+
}
|
|
10921
|
+
async setStatus(status) {
|
|
10922
|
+
if (!this.ws || this.ws.readyState !== ws_1.default.OPEN)
|
|
10923
|
+
return;
|
|
10924
|
+
this.ws.send(JSON.stringify({ type: "set_status", status }));
|
|
10925
|
+
}
|
|
10926
|
+
async createTopic(args) {
|
|
10927
|
+
if (!this.ws || this.ws.readyState !== ws_1.default.OPEN)
|
|
10928
|
+
return;
|
|
10929
|
+
this.ws.send(JSON.stringify({ type: "topic_create", ...args }));
|
|
10930
|
+
}
|
|
10931
|
+
async joinTopic(topic, role) {
|
|
10932
|
+
if (!this.ws || this.ws.readyState !== ws_1.default.OPEN)
|
|
10933
|
+
return;
|
|
10934
|
+
this.ws.send(JSON.stringify({ type: "topic_join", topic, role }));
|
|
10935
|
+
}
|
|
10936
|
+
async leaveTopic(topic) {
|
|
10937
|
+
if (!this.ws || this.ws.readyState !== ws_1.default.OPEN)
|
|
10938
|
+
return;
|
|
10939
|
+
this.ws.send(JSON.stringify({ type: "topic_leave", topic }));
|
|
10940
|
+
}
|
|
10941
|
+
makeReqId() {
|
|
10942
|
+
return Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
10943
|
+
}
|
|
10944
|
+
flushOutbound() {
|
|
10945
|
+
const queued = this.outbound.slice();
|
|
10946
|
+
this.outbound.length = 0;
|
|
10947
|
+
for (const send of queued)
|
|
10948
|
+
send();
|
|
10949
|
+
}
|
|
10950
|
+
scheduleReconnect() {
|
|
10951
|
+
this._status = "reconnecting";
|
|
10952
|
+
const delay = BACKOFF_CAPS2[Math.min(this.reconnectAttempt, BACKOFF_CAPS2.length - 1)];
|
|
10953
|
+
this.reconnectAttempt += 1;
|
|
10954
|
+
this.debug(`reconnect in ${delay}ms (attempt ${this.reconnectAttempt})`);
|
|
10955
|
+
this.reconnectTimer = setTimeout(() => {
|
|
10956
|
+
if (this.closed)
|
|
10957
|
+
return;
|
|
10958
|
+
this.connect().catch((e) => {
|
|
10959
|
+
this.debug(`reconnect failed: ${e instanceof Error ? e.message : e}`);
|
|
10960
|
+
});
|
|
10961
|
+
}, delay);
|
|
10962
|
+
}
|
|
10963
|
+
handleServerMessage(msg) {
|
|
10964
|
+
const reqId = msg._reqId;
|
|
10965
|
+
if (msg.type === "ack") {
|
|
10966
|
+
const pending = this.pendingSends.get(String(msg.id ?? ""));
|
|
10967
|
+
if (pending) {
|
|
10968
|
+
pending.resolve({
|
|
10969
|
+
ok: true,
|
|
10970
|
+
messageId: String(msg.messageId ?? "")
|
|
10971
|
+
});
|
|
10972
|
+
this.pendingSends.delete(pending.id);
|
|
10973
|
+
}
|
|
10974
|
+
return;
|
|
10975
|
+
}
|
|
10976
|
+
if (msg.type === "peers_list") {
|
|
10977
|
+
const peers = msg.peers ?? [];
|
|
10978
|
+
this.resolveFromMap(this.listPeersResolvers, reqId, peers);
|
|
10979
|
+
return;
|
|
10980
|
+
}
|
|
10981
|
+
if (msg.type === "push") {
|
|
10982
|
+
this.handlePush(msg);
|
|
10983
|
+
return;
|
|
10984
|
+
}
|
|
10985
|
+
if (msg.type === "state_result") {
|
|
10986
|
+
if (msg.key) {
|
|
10987
|
+
this.resolveFromMap(this.stateResolvers, reqId, {
|
|
10988
|
+
key: String(msg.key),
|
|
10989
|
+
value: msg.value,
|
|
10990
|
+
updatedBy: String(msg.updatedBy ?? ""),
|
|
10991
|
+
updatedAt: String(msg.updatedAt ?? "")
|
|
10992
|
+
});
|
|
10993
|
+
} else {
|
|
10994
|
+
this.resolveFromMap(this.stateResolvers, reqId, null);
|
|
10995
|
+
}
|
|
10996
|
+
return;
|
|
10997
|
+
}
|
|
10998
|
+
if (msg.type === "state_change") {
|
|
10999
|
+
this.emit("state_change", {
|
|
11000
|
+
key: String(msg.key ?? ""),
|
|
11001
|
+
value: msg.value,
|
|
11002
|
+
updatedBy: String(msg.updatedBy ?? "")
|
|
11003
|
+
});
|
|
11004
|
+
return;
|
|
11005
|
+
}
|
|
11006
|
+
if (msg.type === "error") {
|
|
11007
|
+
this.debug(`broker error: ${msg.code} ${msg.message}`);
|
|
11008
|
+
const id = msg.id ? String(msg.id) : null;
|
|
11009
|
+
if (id) {
|
|
11010
|
+
const pending = this.pendingSends.get(id);
|
|
11011
|
+
if (pending) {
|
|
11012
|
+
pending.resolve({
|
|
11013
|
+
ok: false,
|
|
11014
|
+
error: `${msg.code}: ${msg.message}`
|
|
11015
|
+
});
|
|
11016
|
+
this.pendingSends.delete(id);
|
|
11017
|
+
}
|
|
11018
|
+
}
|
|
11019
|
+
return;
|
|
11020
|
+
}
|
|
11021
|
+
}
|
|
11022
|
+
async handlePush(msg) {
|
|
11023
|
+
const nonce = String(msg.nonce ?? "");
|
|
11024
|
+
const ciphertext = String(msg.ciphertext ?? "");
|
|
11025
|
+
const senderPubkey = String(msg.senderPubkey ?? "");
|
|
11026
|
+
const kind = senderPubkey ? "direct" : "unknown";
|
|
11027
|
+
let plaintext = null;
|
|
11028
|
+
if (senderPubkey && nonce && ciphertext) {
|
|
11029
|
+
plaintext = await (0, crypto_js_1.decryptDirect)({ nonce, ciphertext }, senderPubkey, this.sessionSecretKey ?? this.opts.secretKey);
|
|
11030
|
+
}
|
|
11031
|
+
if (plaintext === null && ciphertext && !senderPubkey) {
|
|
11032
|
+
try {
|
|
11033
|
+
plaintext = Buffer.from(ciphertext, "base64").toString("utf-8");
|
|
11034
|
+
} catch {
|
|
11035
|
+
plaintext = null;
|
|
11036
|
+
}
|
|
11037
|
+
}
|
|
11038
|
+
if (plaintext === null && ciphertext) {
|
|
11039
|
+
try {
|
|
11040
|
+
const decoded = Buffer.from(ciphertext, "base64").toString("utf-8");
|
|
11041
|
+
if (/^[\x20-\x7E\s\u00A0-\uFFFF]*$/.test(decoded) && decoded.length > 0) {
|
|
11042
|
+
plaintext = decoded;
|
|
11043
|
+
}
|
|
11044
|
+
} catch {
|
|
11045
|
+
plaintext = null;
|
|
11046
|
+
}
|
|
11047
|
+
}
|
|
11048
|
+
const push = {
|
|
11049
|
+
messageId: String(msg.messageId ?? ""),
|
|
11050
|
+
meshId: String(msg.meshId ?? ""),
|
|
11051
|
+
senderPubkey,
|
|
11052
|
+
priority: msg.priority ?? "next",
|
|
11053
|
+
nonce,
|
|
11054
|
+
ciphertext,
|
|
11055
|
+
createdAt: String(msg.createdAt ?? ""),
|
|
11056
|
+
receivedAt: new Date().toISOString(),
|
|
11057
|
+
plaintext,
|
|
11058
|
+
kind,
|
|
11059
|
+
...msg.subtype ? { subtype: msg.subtype } : {},
|
|
11060
|
+
...msg.event ? { event: String(msg.event) } : {},
|
|
11061
|
+
...msg.eventData ? { eventData: msg.eventData } : {}
|
|
11062
|
+
};
|
|
11063
|
+
this.emit("message", push);
|
|
11064
|
+
if (push.event === "peer_joined" && push.eventData) {
|
|
11065
|
+
this.emit("peer_joined", push.eventData);
|
|
11066
|
+
}
|
|
11067
|
+
if (push.event === "peer_left" && push.eventData) {
|
|
11068
|
+
this.emit("peer_left", push.eventData);
|
|
11069
|
+
}
|
|
11070
|
+
}
|
|
11071
|
+
resolveFromMap(map, reqId, value) {
|
|
11072
|
+
let entry = reqId ? map.get(reqId) : undefined;
|
|
11073
|
+
if (!entry) {
|
|
11074
|
+
const first = map.entries().next().value;
|
|
11075
|
+
if (first) {
|
|
11076
|
+
entry = first[1];
|
|
11077
|
+
map.delete(first[0]);
|
|
11078
|
+
}
|
|
11079
|
+
} else {
|
|
11080
|
+
map.delete(reqId);
|
|
11081
|
+
}
|
|
11082
|
+
if (entry) {
|
|
11083
|
+
clearTimeout(entry.timer);
|
|
11084
|
+
entry.resolve(value);
|
|
11085
|
+
return true;
|
|
11086
|
+
}
|
|
11087
|
+
return false;
|
|
11088
|
+
}
|
|
11089
|
+
debug(msg) {
|
|
11090
|
+
if (this.opts.debug)
|
|
11091
|
+
console.error(`[claudemesh-sdk] ${msg}`);
|
|
11092
|
+
}
|
|
11093
|
+
}
|
|
11094
|
+
exports.MeshClient = MeshClient;
|
|
11095
|
+
});
|
|
11096
|
+
|
|
11097
|
+
// ../../packages/sdk/dist/bridge.js
|
|
11098
|
+
var require_bridge = __commonJS((exports) => {
|
|
11099
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11100
|
+
exports.Bridge = undefined;
|
|
11101
|
+
var node_events_1 = __require("node:events");
|
|
11102
|
+
var client_js_1 = require_client();
|
|
11103
|
+
var HOP_PREFIX_RE = /^__cmh(\d+):/;
|
|
11104
|
+
var MAX_HOPS_DEFAULT = 2;
|
|
11105
|
+
|
|
11106
|
+
class Bridge extends node_events_1.EventEmitter {
|
|
11107
|
+
clientA;
|
|
11108
|
+
clientB;
|
|
11109
|
+
maxHops;
|
|
11110
|
+
opts;
|
|
11111
|
+
started = false;
|
|
11112
|
+
constructor(opts) {
|
|
11113
|
+
super();
|
|
11114
|
+
this.opts = opts;
|
|
11115
|
+
this.maxHops = opts.maxHops ?? MAX_HOPS_DEFAULT;
|
|
11116
|
+
this.clientA = new client_js_1.MeshClient(opts.a.client);
|
|
11117
|
+
this.clientB = new client_js_1.MeshClient(opts.b.client);
|
|
11118
|
+
}
|
|
11119
|
+
async start() {
|
|
11120
|
+
if (this.started)
|
|
11121
|
+
return;
|
|
11122
|
+
this.started = true;
|
|
11123
|
+
await Promise.all([this.clientA.connect(), this.clientB.connect()]);
|
|
11124
|
+
await Promise.all([
|
|
11125
|
+
this.clientA.joinTopic(this.opts.a.topic, this.opts.a.role),
|
|
11126
|
+
this.clientB.joinTopic(this.opts.b.topic, this.opts.b.role)
|
|
11127
|
+
]);
|
|
11128
|
+
this.clientA.on("message", (m) => this.handleIncoming("a", m).catch((e) => this.emit("error", e instanceof Error ? e : new Error(String(e)))));
|
|
11129
|
+
this.clientB.on("message", (m) => this.handleIncoming("b", m).catch((e) => this.emit("error", e instanceof Error ? e : new Error(String(e)))));
|
|
11130
|
+
}
|
|
11131
|
+
async stop() {
|
|
11132
|
+
if (!this.started)
|
|
11133
|
+
return;
|
|
11134
|
+
this.started = false;
|
|
11135
|
+
this.clientA.disconnect();
|
|
11136
|
+
this.clientB.disconnect();
|
|
11137
|
+
}
|
|
11138
|
+
async handleIncoming(fromSide, msg) {
|
|
11139
|
+
if (msg.subtype === "system")
|
|
11140
|
+
return;
|
|
11141
|
+
const text = msg.plaintext;
|
|
11142
|
+
if (!text)
|
|
11143
|
+
return;
|
|
11144
|
+
const ownA = this.clientA.pubkey;
|
|
11145
|
+
const ownB = this.clientB.pubkey;
|
|
11146
|
+
if (msg.senderPubkey === ownA || msg.senderPubkey === ownB) {
|
|
11147
|
+
this.emit("dropped", { from: fromSide, reason: "echo", hop: -1 });
|
|
11148
|
+
return;
|
|
11149
|
+
}
|
|
11150
|
+
if (this.opts.filter) {
|
|
11151
|
+
const ok = await this.opts.filter(msg, fromSide);
|
|
11152
|
+
if (!ok) {
|
|
11153
|
+
this.emit("dropped", { from: fromSide, reason: "filter", hop: -1 });
|
|
11154
|
+
return;
|
|
11155
|
+
}
|
|
11156
|
+
}
|
|
11157
|
+
const m = text.match(HOP_PREFIX_RE);
|
|
11158
|
+
const currentHop = m ? Number(m[1]) : 0;
|
|
11159
|
+
const nextHop = currentHop + 1;
|
|
11160
|
+
if (nextHop > this.maxHops) {
|
|
11161
|
+
this.emit("dropped", { from: fromSide, reason: "max_hops", hop: currentHop });
|
|
11162
|
+
return;
|
|
11163
|
+
}
|
|
11164
|
+
const stripped = m ? text.slice(m[0].length) : text;
|
|
11165
|
+
const forwarded = `__cmh${nextHop}:${stripped}`;
|
|
11166
|
+
const targetClient = fromSide === "a" ? this.clientB : this.clientA;
|
|
11167
|
+
const targetTopic = fromSide === "a" ? this.opts.b.topic : this.opts.a.topic;
|
|
11168
|
+
await targetClient.send(`#${targetTopic}`, forwarded, "next");
|
|
11169
|
+
this.emit("forwarded", {
|
|
11170
|
+
from: fromSide,
|
|
11171
|
+
to: fromSide === "a" ? "b" : "a",
|
|
11172
|
+
hop: nextHop,
|
|
11173
|
+
bytes: forwarded.length
|
|
11174
|
+
});
|
|
11175
|
+
}
|
|
11176
|
+
}
|
|
11177
|
+
exports.Bridge = Bridge;
|
|
11178
|
+
});
|
|
11179
|
+
|
|
11180
|
+
// ../../packages/sdk/dist/index.js
|
|
11181
|
+
var require_dist = __commonJS((exports) => {
|
|
11182
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11183
|
+
exports.Bridge = exports.generateKeyPair = exports.MeshClient = undefined;
|
|
11184
|
+
var client_js_1 = require_client();
|
|
11185
|
+
Object.defineProperty(exports, "MeshClient", { enumerable: true, get: function() {
|
|
11186
|
+
return client_js_1.MeshClient;
|
|
11187
|
+
} });
|
|
11188
|
+
var crypto_js_1 = require_crypto();
|
|
11189
|
+
Object.defineProperty(exports, "generateKeyPair", { enumerable: true, get: function() {
|
|
11190
|
+
return crypto_js_1.generateKeyPair;
|
|
11191
|
+
} });
|
|
11192
|
+
var bridge_js_1 = require_bridge();
|
|
11193
|
+
Object.defineProperty(exports, "Bridge", { enumerable: true, get: function() {
|
|
11194
|
+
return bridge_js_1.Bridge;
|
|
11195
|
+
} });
|
|
11196
|
+
});
|
|
11197
|
+
|
|
11198
|
+
// src/commands/bridge.ts
|
|
11199
|
+
var exports_bridge = {};
|
|
11200
|
+
__export(exports_bridge, {
|
|
11201
|
+
runBridge: () => runBridge,
|
|
11202
|
+
bridgeConfigTemplate: () => bridgeConfigTemplate
|
|
11203
|
+
});
|
|
11204
|
+
import { readFileSync as readFileSync14, existsSync as existsSync20 } from "node:fs";
|
|
11205
|
+
function parseConfig(text) {
|
|
11206
|
+
const trimmed = text.trim();
|
|
11207
|
+
if (trimmed.startsWith("{"))
|
|
11208
|
+
return JSON.parse(trimmed);
|
|
11209
|
+
const root = {};
|
|
11210
|
+
let cursor = null;
|
|
11211
|
+
for (const raw of text.split(`
|
|
11212
|
+
`)) {
|
|
11213
|
+
const line = raw.replace(/#.*$/, "").trimEnd();
|
|
11214
|
+
if (!line.trim())
|
|
11215
|
+
continue;
|
|
11216
|
+
const top = line.match(/^(a|b)\s*:\s*$/);
|
|
11217
|
+
if (top) {
|
|
11218
|
+
cursor = {};
|
|
11219
|
+
root[top[1]] = cursor;
|
|
11220
|
+
continue;
|
|
11221
|
+
}
|
|
11222
|
+
const flat = line.match(/^(\w+)\s*:\s*(.+)$/);
|
|
11223
|
+
if (flat && /^\s/.test(line) && cursor) {
|
|
11224
|
+
cursor[flat[1]] = parseScalar(flat[2]);
|
|
11225
|
+
} else if (flat) {
|
|
11226
|
+
const v = parseScalar(flat[2]);
|
|
11227
|
+
if (typeof v === "number")
|
|
11228
|
+
root[flat[1]] = v;
|
|
11229
|
+
}
|
|
11230
|
+
}
|
|
11231
|
+
return root;
|
|
11232
|
+
}
|
|
11233
|
+
function parseScalar(raw) {
|
|
11234
|
+
const v = raw.trim().replace(/^["'](.*)["']$/, "$1");
|
|
11235
|
+
if (v === "true")
|
|
11236
|
+
return true;
|
|
11237
|
+
if (v === "false")
|
|
11238
|
+
return false;
|
|
11239
|
+
if (/^-?\d+(\.\d+)?$/.test(v))
|
|
11240
|
+
return Number(v);
|
|
11241
|
+
return v;
|
|
11242
|
+
}
|
|
11243
|
+
async function runBridge(configPath) {
|
|
11244
|
+
if (!configPath) {
|
|
11245
|
+
render.err("Usage: claudemesh bridge run <config.yaml>");
|
|
11246
|
+
return EXIT.INVALID_ARGS;
|
|
11247
|
+
}
|
|
11248
|
+
if (!existsSync20(configPath)) {
|
|
11249
|
+
render.err(`config file not found: ${configPath}`);
|
|
11250
|
+
return EXIT.NOT_FOUND;
|
|
11251
|
+
}
|
|
11252
|
+
let cfg;
|
|
11253
|
+
try {
|
|
11254
|
+
cfg = parseConfig(readFileSync14(configPath, "utf-8"));
|
|
11255
|
+
} catch (e) {
|
|
11256
|
+
render.err(`failed to parse ${configPath}: ${e instanceof Error ? e.message : String(e)}`);
|
|
11257
|
+
return EXIT.INVALID_ARGS;
|
|
11258
|
+
}
|
|
11259
|
+
if (!cfg.a || !cfg.b) {
|
|
11260
|
+
render.err("config must define 'a:' and 'b:' sections");
|
|
11261
|
+
return EXIT.INVALID_ARGS;
|
|
11262
|
+
}
|
|
11263
|
+
for (const [name, side] of [["a", cfg.a], ["b", cfg.b]]) {
|
|
11264
|
+
if (!side.broker_url || !side.mesh_id || !side.member_id || !side.pubkey || !side.secret_key || !side.topic) {
|
|
11265
|
+
render.err(`config side '${name}' missing required fields: broker_url, mesh_id, member_id, pubkey, secret_key, topic`);
|
|
11266
|
+
return EXIT.INVALID_ARGS;
|
|
11267
|
+
}
|
|
11268
|
+
}
|
|
11269
|
+
const { Bridge } = await Promise.resolve().then(() => __toESM(require_dist(), 1));
|
|
11270
|
+
const bridge = new Bridge({
|
|
11271
|
+
a: {
|
|
11272
|
+
client: {
|
|
11273
|
+
brokerUrl: cfg.a.broker_url,
|
|
11274
|
+
meshId: cfg.a.mesh_id,
|
|
11275
|
+
memberId: cfg.a.member_id,
|
|
11276
|
+
pubkey: cfg.a.pubkey,
|
|
11277
|
+
secretKey: cfg.a.secret_key,
|
|
11278
|
+
displayName: cfg.a.display_name ?? "bridge",
|
|
11279
|
+
peerType: "connector",
|
|
11280
|
+
channel: "bridge"
|
|
11281
|
+
},
|
|
11282
|
+
topic: cfg.a.topic,
|
|
11283
|
+
role: cfg.a.role
|
|
11284
|
+
},
|
|
11285
|
+
b: {
|
|
11286
|
+
client: {
|
|
11287
|
+
brokerUrl: cfg.b.broker_url,
|
|
11288
|
+
meshId: cfg.b.mesh_id,
|
|
11289
|
+
memberId: cfg.b.member_id,
|
|
11290
|
+
pubkey: cfg.b.pubkey,
|
|
11291
|
+
secretKey: cfg.b.secret_key,
|
|
11292
|
+
displayName: cfg.b.display_name ?? "bridge",
|
|
11293
|
+
peerType: "connector",
|
|
11294
|
+
channel: "bridge"
|
|
11295
|
+
},
|
|
11296
|
+
topic: cfg.b.topic,
|
|
11297
|
+
role: cfg.b.role
|
|
11298
|
+
},
|
|
11299
|
+
maxHops: cfg.max_hops
|
|
11300
|
+
});
|
|
11301
|
+
bridge.on("forwarded", (e) => {
|
|
11302
|
+
process.stdout.write(`${dim(new Date().toISOString())} ${green("→")} ${e.from}→${e.to} hop=${e.hop} ${dim(`${e.bytes}b`)}
|
|
11303
|
+
`);
|
|
11304
|
+
});
|
|
11305
|
+
bridge.on("dropped", (e) => {
|
|
11306
|
+
process.stdout.write(`${dim(new Date().toISOString())} ${yellow("·")} drop from=${e.from} reason=${e.reason}${e.hop >= 0 ? ` hop=${e.hop}` : ""}
|
|
11307
|
+
`);
|
|
11308
|
+
});
|
|
11309
|
+
bridge.on("error", (e) => {
|
|
11310
|
+
process.stderr.write(`${red("✘")} ${e.message}
|
|
11311
|
+
`);
|
|
11312
|
+
});
|
|
11313
|
+
try {
|
|
11314
|
+
await bridge.start();
|
|
11315
|
+
} catch (e) {
|
|
11316
|
+
render.err(`bridge failed to start: ${e instanceof Error ? e.message : String(e)}`);
|
|
11317
|
+
return EXIT.NETWORK_ERROR;
|
|
11318
|
+
}
|
|
11319
|
+
render.ok("bridge running", `${clay("#" + cfg.a.topic)} ${dim("⟷")} ${clay("#" + cfg.b.topic)}`);
|
|
11320
|
+
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}`)}
|
|
11321
|
+
`);
|
|
11322
|
+
process.stderr.write(`${dim(" Ctrl-C to stop.")}
|
|
11323
|
+
|
|
11324
|
+
`);
|
|
11325
|
+
await new Promise((resolve3) => {
|
|
11326
|
+
const stop = async () => {
|
|
11327
|
+
process.stderr.write(`
|
|
11328
|
+
${dim("stopping bridge...")}
|
|
11329
|
+
`);
|
|
11330
|
+
await bridge.stop();
|
|
11331
|
+
resolve3();
|
|
11332
|
+
};
|
|
11333
|
+
process.on("SIGINT", stop);
|
|
11334
|
+
process.on("SIGTERM", stop);
|
|
11335
|
+
});
|
|
11336
|
+
return EXIT.SUCCESS;
|
|
11337
|
+
}
|
|
11338
|
+
function bridgeConfigTemplate() {
|
|
11339
|
+
return `# claudemesh bridge config
|
|
11340
|
+
# Spec: .artifacts/specs/2026-05-02-v0.2.0-scope.md
|
|
11341
|
+
#
|
|
11342
|
+
# A bridge holds memberships in two meshes and forwards messages on a
|
|
11343
|
+
# single topic between them. Loop prevention via plaintext hop counter
|
|
11344
|
+
# (visible in message body — minor wart, fixed in v0.3.0).
|
|
11345
|
+
#
|
|
11346
|
+
# Tip: \`claudemesh peer verify\` shows the keys/ids you need below.
|
|
11347
|
+
|
|
11348
|
+
max_hops: 2
|
|
11349
|
+
|
|
11350
|
+
a:
|
|
11351
|
+
broker_url: wss://ic.claudemesh.com/ws
|
|
11352
|
+
mesh_id: <mesh A id>
|
|
11353
|
+
member_id: <bridge member id in mesh A>
|
|
11354
|
+
pubkey: <ed25519 public key hex, 32 bytes>
|
|
11355
|
+
secret_key: <ed25519 secret key hex, 64 bytes>
|
|
11356
|
+
topic: incidents
|
|
11357
|
+
display_name: bridge
|
|
11358
|
+
role: member
|
|
11359
|
+
|
|
11360
|
+
b:
|
|
11361
|
+
broker_url: wss://ic.claudemesh.com/ws
|
|
11362
|
+
mesh_id: <mesh B id>
|
|
11363
|
+
member_id: <bridge member id in mesh B>
|
|
11364
|
+
pubkey: <ed25519 public key hex>
|
|
11365
|
+
secret_key: <ed25519 secret key hex>
|
|
11366
|
+
topic: incidents
|
|
11367
|
+
`;
|
|
11368
|
+
}
|
|
11369
|
+
var init_bridge = __esm(() => {
|
|
11370
|
+
init_render();
|
|
11371
|
+
init_styles();
|
|
11372
|
+
init_exit_codes();
|
|
11373
|
+
});
|
|
11374
|
+
|
|
11375
|
+
// src/commands/apikey.ts
|
|
11376
|
+
var exports_apikey = {};
|
|
11377
|
+
__export(exports_apikey, {
|
|
11378
|
+
runApiKeyRevoke: () => runApiKeyRevoke,
|
|
11379
|
+
runApiKeyList: () => runApiKeyList,
|
|
11380
|
+
runApiKeyCreate: () => runApiKeyCreate
|
|
11381
|
+
});
|
|
11382
|
+
function parseCapabilities(raw) {
|
|
11383
|
+
if (!raw)
|
|
11384
|
+
return ["send", "read"];
|
|
11385
|
+
const parts = raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
11386
|
+
const valid = new Set(["send", "read", "state_write", "admin"]);
|
|
11387
|
+
return parts.filter((p) => valid.has(p));
|
|
11388
|
+
}
|
|
11389
|
+
async function runApiKeyCreate(label, flags) {
|
|
11390
|
+
if (!label) {
|
|
11391
|
+
render.err("Usage: claudemesh apikey create <label> [--cap send,read] [--topic deploys]");
|
|
11392
|
+
return EXIT.INVALID_ARGS;
|
|
11393
|
+
}
|
|
11394
|
+
const caps = parseCapabilities(flags.cap);
|
|
11395
|
+
if (caps.length === 0) {
|
|
11396
|
+
render.err("at least one capability required: --cap send,read,state_write,admin");
|
|
11397
|
+
return EXIT.INVALID_ARGS;
|
|
11398
|
+
}
|
|
11399
|
+
const topicScopes = flags.topic ? flags.topic.split(",").map((s) => s.trim()).filter(Boolean) : undefined;
|
|
11400
|
+
return await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
|
|
11401
|
+
const result = await client.apiKeyCreate({
|
|
11402
|
+
label,
|
|
11403
|
+
capabilities: caps,
|
|
11404
|
+
topicScopes,
|
|
11405
|
+
expiresAt: flags.expires
|
|
11406
|
+
});
|
|
11407
|
+
if (!result) {
|
|
11408
|
+
render.err("apikey create failed");
|
|
11409
|
+
return EXIT.INTERNAL_ERROR;
|
|
11410
|
+
}
|
|
11411
|
+
if (flags.json) {
|
|
11412
|
+
console.log(JSON.stringify(result, null, 2));
|
|
11413
|
+
return EXIT.SUCCESS;
|
|
11414
|
+
}
|
|
11415
|
+
render.ok("created", `${bold(result.label)} ${dim(result.id.slice(0, 8))}`);
|
|
11416
|
+
process.stdout.write(`
|
|
11417
|
+
${yellow("⚠ secret shown once — copy it now:")}
|
|
11418
|
+
|
|
11419
|
+
`);
|
|
11420
|
+
process.stdout.write(` ${green(result.secret)}
|
|
11421
|
+
|
|
11422
|
+
`);
|
|
11423
|
+
process.stdout.write(` ${dim(`capabilities: ${result.capabilities.join(", ")}`)}
|
|
11424
|
+
`);
|
|
11425
|
+
if (result.topicScopes?.length) {
|
|
11426
|
+
process.stdout.write(` ${dim(`topics: ${result.topicScopes.map((t) => "#" + t).join(", ")}`)}
|
|
11427
|
+
`);
|
|
11428
|
+
} else {
|
|
11429
|
+
process.stdout.write(` ${dim("topics: all (no scope)")}
|
|
11430
|
+
`);
|
|
11431
|
+
}
|
|
11432
|
+
return EXIT.SUCCESS;
|
|
11433
|
+
});
|
|
11434
|
+
}
|
|
11435
|
+
async function runApiKeyList(flags) {
|
|
11436
|
+
return await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
|
|
11437
|
+
const keys = await client.apiKeyList();
|
|
11438
|
+
if (flags.json) {
|
|
11439
|
+
console.log(JSON.stringify(keys, null, 2));
|
|
11440
|
+
return EXIT.SUCCESS;
|
|
11441
|
+
}
|
|
11442
|
+
if (keys.length === 0) {
|
|
11443
|
+
render.info(dim("no api keys in this mesh."));
|
|
11444
|
+
return EXIT.SUCCESS;
|
|
11445
|
+
}
|
|
11446
|
+
render.section(`api keys (${keys.length})`);
|
|
11447
|
+
for (const k of keys) {
|
|
11448
|
+
const status = k.revokedAt ? red("revoked") : k.expiresAt && new Date(k.expiresAt) < new Date ? yellow("expired") : green("active");
|
|
11449
|
+
const lastUsed = k.lastUsedAt ? new Date(k.lastUsedAt).toLocaleDateString() : "never";
|
|
11450
|
+
const scope = k.topicScopes?.length ? k.topicScopes.map((t) => "#" + t).join(",") : "all topics";
|
|
11451
|
+
process.stdout.write(` ${bold(k.label)} ${status} ${dim(k.id.slice(0, 8))}
|
|
11452
|
+
`);
|
|
11453
|
+
process.stdout.write(` ${dim(`${k.prefix}… caps: ${k.capabilities.join(",")} scope: ${scope} last_used: ${lastUsed}`)}
|
|
11454
|
+
`);
|
|
11455
|
+
}
|
|
11456
|
+
return EXIT.SUCCESS;
|
|
11457
|
+
});
|
|
11458
|
+
}
|
|
11459
|
+
async function runApiKeyRevoke(id, flags) {
|
|
11460
|
+
if (!id) {
|
|
11461
|
+
render.err("Usage: claudemesh apikey revoke <id>");
|
|
11462
|
+
return EXIT.INVALID_ARGS;
|
|
11463
|
+
}
|
|
11464
|
+
return await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
|
|
11465
|
+
const result = await client.apiKeyRevoke(id);
|
|
11466
|
+
if (!result.ok) {
|
|
11467
|
+
if (flags.json) {
|
|
11468
|
+
console.log(JSON.stringify({ ok: false, code: result.code, message: result.message }));
|
|
11469
|
+
} else {
|
|
11470
|
+
render.err(`${result.code}: ${result.message}`);
|
|
11471
|
+
}
|
|
11472
|
+
return result.code === "not_found" ? EXIT.NOT_FOUND : result.code === "not_unique" ? EXIT.INVALID_ARGS : EXIT.INTERNAL_ERROR;
|
|
11473
|
+
}
|
|
11474
|
+
if (flags.json)
|
|
11475
|
+
console.log(JSON.stringify({ revoked: result.id }));
|
|
11476
|
+
else
|
|
11477
|
+
render.ok("revoked", clay(result.id.slice(0, 8)));
|
|
11478
|
+
return EXIT.SUCCESS;
|
|
11479
|
+
});
|
|
11480
|
+
}
|
|
11481
|
+
var init_apikey = __esm(() => {
|
|
11482
|
+
init_connect();
|
|
11483
|
+
init_render();
|
|
11484
|
+
init_styles();
|
|
11485
|
+
init_exit_codes();
|
|
11486
|
+
});
|
|
11487
|
+
|
|
11488
|
+
// src/commands/topic.ts
|
|
11489
|
+
var exports_topic = {};
|
|
11490
|
+
__export(exports_topic, {
|
|
11491
|
+
runTopicMembers: () => runTopicMembers,
|
|
11492
|
+
runTopicMarkRead: () => runTopicMarkRead,
|
|
11493
|
+
runTopicList: () => runTopicList,
|
|
11494
|
+
runTopicLeave: () => runTopicLeave,
|
|
11495
|
+
runTopicJoin: () => runTopicJoin,
|
|
11496
|
+
runTopicHistory: () => runTopicHistory,
|
|
11497
|
+
runTopicCreate: () => runTopicCreate
|
|
11498
|
+
});
|
|
11499
|
+
async function runTopicCreate(name, flags) {
|
|
11500
|
+
if (!name) {
|
|
11501
|
+
render.err("Usage: claudemesh topic create <name> [--description X] [--visibility V]");
|
|
11502
|
+
return EXIT.INVALID_ARGS;
|
|
11503
|
+
}
|
|
11504
|
+
return await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
|
|
11505
|
+
const result = await client.topicCreate({
|
|
11506
|
+
name,
|
|
11507
|
+
description: flags.description,
|
|
11508
|
+
visibility: flags.visibility
|
|
11509
|
+
});
|
|
11510
|
+
if (!result) {
|
|
11511
|
+
render.err("topic create failed");
|
|
11512
|
+
return EXIT.INTERNAL_ERROR;
|
|
11513
|
+
}
|
|
11514
|
+
if (flags.json) {
|
|
11515
|
+
console.log(JSON.stringify(result));
|
|
11516
|
+
return EXIT.SUCCESS;
|
|
11517
|
+
}
|
|
11518
|
+
if (result.created) {
|
|
11519
|
+
render.ok("created", `${clay("#" + name)} ${dim(result.id.slice(0, 8))}`);
|
|
11520
|
+
} else {
|
|
11521
|
+
render.info(dim(`already exists: #${name} ${result.id.slice(0, 8)}`));
|
|
11522
|
+
}
|
|
11523
|
+
return EXIT.SUCCESS;
|
|
11524
|
+
});
|
|
11525
|
+
}
|
|
11526
|
+
async function runTopicList(flags) {
|
|
11527
|
+
return await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
|
|
11528
|
+
const topics = await client.topicList();
|
|
11529
|
+
if (flags.json) {
|
|
11530
|
+
console.log(JSON.stringify(topics, null, 2));
|
|
11531
|
+
return EXIT.SUCCESS;
|
|
11532
|
+
}
|
|
11533
|
+
if (topics.length === 0) {
|
|
11534
|
+
render.info(dim("no topics in this mesh."));
|
|
11535
|
+
return EXIT.SUCCESS;
|
|
11536
|
+
}
|
|
11537
|
+
render.section(`topics (${topics.length})`);
|
|
11538
|
+
for (const t of topics) {
|
|
11539
|
+
const vis = t.visibility === "public" ? green(t.visibility) : dim(t.visibility);
|
|
11540
|
+
process.stdout.write(` ${clay("#" + t.name)} ${vis} ${dim(`${t.memberCount} member${t.memberCount === 1 ? "" : "s"}`)}
|
|
11541
|
+
`);
|
|
11542
|
+
if (t.description)
|
|
11543
|
+
process.stdout.write(` ${dim(t.description)}
|
|
11544
|
+
`);
|
|
11545
|
+
}
|
|
11546
|
+
return EXIT.SUCCESS;
|
|
11547
|
+
});
|
|
11548
|
+
}
|
|
11549
|
+
async function runTopicJoin(topic, flags) {
|
|
11550
|
+
if (!topic) {
|
|
11551
|
+
render.err("Usage: claudemesh topic join <topic> [--role lead|member|observer]");
|
|
11552
|
+
return EXIT.INVALID_ARGS;
|
|
11553
|
+
}
|
|
11554
|
+
return await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
|
|
11555
|
+
await client.topicJoin(topic, flags.role);
|
|
11556
|
+
if (flags.json)
|
|
11557
|
+
console.log(JSON.stringify({ joined: topic }));
|
|
11558
|
+
else
|
|
11559
|
+
render.ok("joined", clay("#" + topic));
|
|
11560
|
+
return EXIT.SUCCESS;
|
|
11561
|
+
});
|
|
11562
|
+
}
|
|
11563
|
+
async function runTopicLeave(topic, flags) {
|
|
11564
|
+
if (!topic) {
|
|
11565
|
+
render.err("Usage: claudemesh topic leave <topic>");
|
|
11566
|
+
return EXIT.INVALID_ARGS;
|
|
11567
|
+
}
|
|
11568
|
+
return await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
|
|
11569
|
+
await client.topicLeave(topic);
|
|
11570
|
+
if (flags.json)
|
|
11571
|
+
console.log(JSON.stringify({ left: topic }));
|
|
11572
|
+
else
|
|
11573
|
+
render.ok("left", clay("#" + topic));
|
|
11574
|
+
return EXIT.SUCCESS;
|
|
11575
|
+
});
|
|
11576
|
+
}
|
|
11577
|
+
async function runTopicMembers(topic, flags) {
|
|
11578
|
+
if (!topic) {
|
|
11579
|
+
render.err("Usage: claudemesh topic members <topic>");
|
|
11580
|
+
return EXIT.INVALID_ARGS;
|
|
11581
|
+
}
|
|
11582
|
+
return await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
|
|
11583
|
+
const members = await client.topicMembers(topic);
|
|
11584
|
+
if (flags.json) {
|
|
11585
|
+
console.log(JSON.stringify(members, null, 2));
|
|
11586
|
+
return EXIT.SUCCESS;
|
|
11587
|
+
}
|
|
11588
|
+
if (members.length === 0) {
|
|
11589
|
+
render.info(dim(`no members in ${clay("#" + topic)}.`));
|
|
11590
|
+
return EXIT.SUCCESS;
|
|
11591
|
+
}
|
|
11592
|
+
render.section(`${clay("#" + topic)} members (${members.length})`);
|
|
11593
|
+
for (const m of members) {
|
|
11594
|
+
process.stdout.write(` ${bold(m.displayName)} ${dim(m.role)} ${dim(m.pubkey.slice(0, 8))}
|
|
11595
|
+
`);
|
|
11596
|
+
}
|
|
11597
|
+
return EXIT.SUCCESS;
|
|
11598
|
+
});
|
|
11599
|
+
}
|
|
11600
|
+
async function runTopicHistory(topic, flags) {
|
|
11601
|
+
if (!topic) {
|
|
11602
|
+
render.err("Usage: claudemesh topic history <topic> [--limit N] [--before <id>]");
|
|
11603
|
+
return EXIT.INVALID_ARGS;
|
|
11604
|
+
}
|
|
11605
|
+
return await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
|
|
11606
|
+
const limit = flags.limit ? Number(flags.limit) : undefined;
|
|
11607
|
+
const messages = await client.topicHistory({
|
|
11608
|
+
topic,
|
|
11609
|
+
limit,
|
|
11610
|
+
beforeId: flags.before
|
|
11611
|
+
});
|
|
11612
|
+
if (flags.json) {
|
|
11613
|
+
console.log(JSON.stringify(messages, null, 2));
|
|
11614
|
+
return EXIT.SUCCESS;
|
|
11615
|
+
}
|
|
11616
|
+
if (messages.length === 0) {
|
|
11617
|
+
render.info(dim(`no messages in ${clay("#" + topic)}.`));
|
|
11618
|
+
return EXIT.SUCCESS;
|
|
11619
|
+
}
|
|
11620
|
+
const ordered = [...messages].reverse();
|
|
11621
|
+
render.section(`${clay("#" + topic)} history (${ordered.length})`);
|
|
11622
|
+
for (const m of ordered) {
|
|
11623
|
+
const t = new Date(m.createdAt).toLocaleString();
|
|
11624
|
+
process.stdout.write(` ${dim(t)} ${bold(m.senderPubkey.slice(0, 8))} ${dim("(encrypted, " + m.ciphertext.length + "b)")}
|
|
11625
|
+
`);
|
|
11626
|
+
}
|
|
11627
|
+
return EXIT.SUCCESS;
|
|
11628
|
+
});
|
|
11629
|
+
}
|
|
11630
|
+
async function runTopicMarkRead(topic, flags) {
|
|
11631
|
+
if (!topic) {
|
|
11632
|
+
render.err("Usage: claudemesh topic read <topic>");
|
|
11633
|
+
return EXIT.INVALID_ARGS;
|
|
11634
|
+
}
|
|
11635
|
+
return await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
|
|
11636
|
+
await client.topicMarkRead(topic);
|
|
11637
|
+
if (flags.json)
|
|
11638
|
+
console.log(JSON.stringify({ read: topic }));
|
|
11639
|
+
else
|
|
11640
|
+
render.ok("marked read", clay("#" + topic));
|
|
11641
|
+
return EXIT.SUCCESS;
|
|
11642
|
+
});
|
|
11643
|
+
}
|
|
11644
|
+
var init_topic = __esm(() => {
|
|
11645
|
+
init_connect();
|
|
11646
|
+
init_render();
|
|
11647
|
+
init_styles();
|
|
11648
|
+
init_exit_codes();
|
|
11649
|
+
});
|
|
11650
|
+
|
|
10381
11651
|
// src/mcp/tools/definitions.ts
|
|
10382
11652
|
var TOOLS;
|
|
10383
11653
|
var init_definitions = __esm(() => {
|
|
@@ -10386,7 +11656,7 @@ var init_definitions = __esm(() => {
|
|
|
10386
11656
|
|
|
10387
11657
|
// src/services/bridge/server.ts
|
|
10388
11658
|
import { createServer as createServer2 } from "node:net";
|
|
10389
|
-
import { mkdirSync as mkdirSync7, unlinkSync as unlinkSync2, existsSync as
|
|
11659
|
+
import { mkdirSync as mkdirSync7, unlinkSync as unlinkSync2, existsSync as existsSync21, chmodSync as chmodSync5 } from "node:fs";
|
|
10390
11660
|
async function resolveTarget2(client, to) {
|
|
10391
11661
|
if (to.startsWith("@") || to === "*" || /^[0-9a-f]{64}$/i.test(to)) {
|
|
10392
11662
|
return { ok: true, spec: to };
|
|
@@ -10500,10 +11770,10 @@ function handleConnection(socket, client) {
|
|
|
10500
11770
|
function startBridgeServer(client) {
|
|
10501
11771
|
const path = socketPath(client.meshSlug);
|
|
10502
11772
|
const dir = socketDir();
|
|
10503
|
-
if (!
|
|
11773
|
+
if (!existsSync21(dir)) {
|
|
10504
11774
|
mkdirSync7(dir, { recursive: true, mode: 448 });
|
|
10505
11775
|
}
|
|
10506
|
-
if (
|
|
11776
|
+
if (existsSync21(path)) {
|
|
10507
11777
|
try {
|
|
10508
11778
|
unlinkSync2(path);
|
|
10509
11779
|
} catch {}
|
|
@@ -11553,6 +12823,21 @@ function classifyInvocation(command, positionals) {
|
|
|
11553
12823
|
case "task": {
|
|
11554
12824
|
return { resource: "task", verb: sub || "list", isWrite: isWrite(sub) };
|
|
11555
12825
|
}
|
|
12826
|
+
case "topic": {
|
|
12827
|
+
const verb = sub || "list";
|
|
12828
|
+
const writeVerbs = new Set(["create", "join", "leave"]);
|
|
12829
|
+
return { resource: "topic", verb, isWrite: writeVerbs.has(verb) };
|
|
12830
|
+
}
|
|
12831
|
+
case "apikey":
|
|
12832
|
+
case "api-key": {
|
|
12833
|
+
const verb = sub || "list";
|
|
12834
|
+
const writeVerbs = new Set(["create", "revoke"]);
|
|
12835
|
+
return { resource: "apikey", verb, isWrite: writeVerbs.has(verb) };
|
|
12836
|
+
}
|
|
12837
|
+
case "bridge": {
|
|
12838
|
+
const verb = sub || "init";
|
|
12839
|
+
return { resource: "bridge", verb, isWrite: verb === "run" };
|
|
12840
|
+
}
|
|
11556
12841
|
case "vector":
|
|
11557
12842
|
case "graph":
|
|
11558
12843
|
case "context":
|
|
@@ -11656,7 +12941,9 @@ var DEFAULT_POLICY = {
|
|
|
11656
12941
|
{ resource: "watch", verb: "remove", decision: "prompt", reason: "removes URL watcher" },
|
|
11657
12942
|
{ resource: "sql", verb: "execute", decision: "prompt", reason: "raw SQL write to mesh DB" },
|
|
11658
12943
|
{ resource: "graph", verb: "execute", decision: "prompt", reason: "graph mutation" },
|
|
11659
|
-
{ resource: "mesh", verb: "delete", decision: "prompt", reason: "deletes the mesh for everyone" }
|
|
12944
|
+
{ resource: "mesh", verb: "delete", decision: "prompt", reason: "deletes the mesh for everyone" },
|
|
12945
|
+
{ resource: "apikey", verb: "create", decision: "prompt", reason: "issues a long-lived credential" },
|
|
12946
|
+
{ resource: "apikey", verb: "revoke", decision: "prompt", reason: "irreversibly disables a credential" }
|
|
11660
12947
|
]
|
|
11661
12948
|
};
|
|
11662
12949
|
var USER_POLICY_PATH = join(homedir(), ".claudemesh", "policy.yaml");
|
|
@@ -11915,6 +13202,25 @@ Profile / presence (resource form)
|
|
|
11915
13202
|
claudemesh group join @<name> join a group (--role X)
|
|
11916
13203
|
claudemesh group leave @<name> leave a group
|
|
11917
13204
|
|
|
13205
|
+
API keys (REST + external WS auth, v0.2.0)
|
|
13206
|
+
claudemesh apikey create <label> issue [--cap send,read] [--topic deploys]
|
|
13207
|
+
claudemesh apikey list show keys (status, last-used, scope)
|
|
13208
|
+
claudemesh apikey revoke <id> revoke a key
|
|
13209
|
+
|
|
13210
|
+
Bridge (forward a topic between two meshes, v0.2.0)
|
|
13211
|
+
claudemesh bridge init print config template
|
|
13212
|
+
claudemesh bridge run <config> run bridge as a long-lived process
|
|
13213
|
+
|
|
13214
|
+
Topic (conversation scope, v0.2.0)
|
|
13215
|
+
claudemesh topic create <name> create a topic [--description --visibility]
|
|
13216
|
+
claudemesh topic list list topics in the mesh
|
|
13217
|
+
claudemesh topic join <topic> subscribe (via name or id)
|
|
13218
|
+
claudemesh topic leave <topic> unsubscribe
|
|
13219
|
+
claudemesh topic members <t> list topic subscribers
|
|
13220
|
+
claudemesh topic history <t> fetch message history [--limit --before]
|
|
13221
|
+
claudemesh topic read <topic> mark all as read
|
|
13222
|
+
claudemesh send "#topic" "msg" send to a topic
|
|
13223
|
+
|
|
11918
13224
|
Schedule (resource form)
|
|
11919
13225
|
claudemesh schedule msg <m> one-shot or recurring (alias: remind)
|
|
11920
13226
|
claudemesh schedule list list pending
|
|
@@ -12618,6 +13924,86 @@ async function main() {
|
|
|
12618
13924
|
}
|
|
12619
13925
|
break;
|
|
12620
13926
|
}
|
|
13927
|
+
case "bridge": {
|
|
13928
|
+
const sub = positionals[0];
|
|
13929
|
+
if (sub === "run") {
|
|
13930
|
+
const { runBridge: runBridge2 } = await Promise.resolve().then(() => (init_bridge(), exports_bridge));
|
|
13931
|
+
process.exit(await runBridge2(positionals[1] ?? ""));
|
|
13932
|
+
} else if (sub === "init" || sub === "config") {
|
|
13933
|
+
const { bridgeConfigTemplate: bridgeConfigTemplate2 } = await Promise.resolve().then(() => (init_bridge(), exports_bridge));
|
|
13934
|
+
console.log(bridgeConfigTemplate2());
|
|
13935
|
+
process.exit(EXIT.SUCCESS);
|
|
13936
|
+
} else {
|
|
13937
|
+
console.error("Usage: claudemesh bridge <run <config.yaml> | init>");
|
|
13938
|
+
process.exit(EXIT.INVALID_ARGS);
|
|
13939
|
+
}
|
|
13940
|
+
break;
|
|
13941
|
+
}
|
|
13942
|
+
case "apikey":
|
|
13943
|
+
case "api-key": {
|
|
13944
|
+
const sub = positionals[0];
|
|
13945
|
+
const f = {
|
|
13946
|
+
mesh: flags.mesh,
|
|
13947
|
+
json: !!flags.json,
|
|
13948
|
+
cap: flags.cap,
|
|
13949
|
+
topic: flags.topic,
|
|
13950
|
+
expires: flags.expires
|
|
13951
|
+
};
|
|
13952
|
+
const arg = positionals[1] ?? "";
|
|
13953
|
+
if (sub === "create") {
|
|
13954
|
+
const { runApiKeyCreate: runApiKeyCreate2 } = await Promise.resolve().then(() => (init_apikey(), exports_apikey));
|
|
13955
|
+
process.exit(await runApiKeyCreate2(arg, f));
|
|
13956
|
+
} else if (sub === "list") {
|
|
13957
|
+
const { runApiKeyList: runApiKeyList2 } = await Promise.resolve().then(() => (init_apikey(), exports_apikey));
|
|
13958
|
+
process.exit(await runApiKeyList2(f));
|
|
13959
|
+
} else if (sub === "revoke") {
|
|
13960
|
+
const { runApiKeyRevoke: runApiKeyRevoke2 } = await Promise.resolve().then(() => (init_apikey(), exports_apikey));
|
|
13961
|
+
process.exit(await runApiKeyRevoke2(arg, f));
|
|
13962
|
+
} else {
|
|
13963
|
+
console.error("Usage: claudemesh apikey <create|list|revoke>");
|
|
13964
|
+
process.exit(EXIT.INVALID_ARGS);
|
|
13965
|
+
}
|
|
13966
|
+
break;
|
|
13967
|
+
}
|
|
13968
|
+
case "topic": {
|
|
13969
|
+
const sub = positionals[0];
|
|
13970
|
+
const f = {
|
|
13971
|
+
mesh: flags.mesh,
|
|
13972
|
+
json: !!flags.json,
|
|
13973
|
+
description: flags.description,
|
|
13974
|
+
visibility: flags.visibility,
|
|
13975
|
+
role: flags.role,
|
|
13976
|
+
limit: flags.limit,
|
|
13977
|
+
before: flags.before
|
|
13978
|
+
};
|
|
13979
|
+
const arg = positionals[1] ?? "";
|
|
13980
|
+
if (sub === "create") {
|
|
13981
|
+
const { runTopicCreate: runTopicCreate2 } = await Promise.resolve().then(() => (init_topic(), exports_topic));
|
|
13982
|
+
process.exit(await runTopicCreate2(arg, f));
|
|
13983
|
+
} else if (sub === "list") {
|
|
13984
|
+
const { runTopicList: runTopicList2 } = await Promise.resolve().then(() => (init_topic(), exports_topic));
|
|
13985
|
+
process.exit(await runTopicList2(f));
|
|
13986
|
+
} else if (sub === "join") {
|
|
13987
|
+
const { runTopicJoin: runTopicJoin2 } = await Promise.resolve().then(() => (init_topic(), exports_topic));
|
|
13988
|
+
process.exit(await runTopicJoin2(arg, f));
|
|
13989
|
+
} else if (sub === "leave") {
|
|
13990
|
+
const { runTopicLeave: runTopicLeave2 } = await Promise.resolve().then(() => (init_topic(), exports_topic));
|
|
13991
|
+
process.exit(await runTopicLeave2(arg, f));
|
|
13992
|
+
} else if (sub === "members") {
|
|
13993
|
+
const { runTopicMembers: runTopicMembers2 } = await Promise.resolve().then(() => (init_topic(), exports_topic));
|
|
13994
|
+
process.exit(await runTopicMembers2(arg, f));
|
|
13995
|
+
} else if (sub === "history") {
|
|
13996
|
+
const { runTopicHistory: runTopicHistory2 } = await Promise.resolve().then(() => (init_topic(), exports_topic));
|
|
13997
|
+
process.exit(await runTopicHistory2(arg, f));
|
|
13998
|
+
} else if (sub === "read") {
|
|
13999
|
+
const { runTopicMarkRead: runTopicMarkRead2 } = await Promise.resolve().then(() => (init_topic(), exports_topic));
|
|
14000
|
+
process.exit(await runTopicMarkRead2(arg, f));
|
|
14001
|
+
} else {
|
|
14002
|
+
console.error("Usage: claudemesh topic <create|list|join|leave|members|history|read>");
|
|
14003
|
+
process.exit(EXIT.INVALID_ARGS);
|
|
14004
|
+
}
|
|
14005
|
+
break;
|
|
14006
|
+
}
|
|
12621
14007
|
case "task": {
|
|
12622
14008
|
const sub = positionals[0];
|
|
12623
14009
|
const f = { mesh: flags.mesh, json: !!flags.json };
|
|
@@ -12666,4 +14052,4 @@ main().catch((err) => {
|
|
|
12666
14052
|
process.exit(EXIT.INTERNAL_ERROR);
|
|
12667
14053
|
});
|
|
12668
14054
|
|
|
12669
|
-
//# debugId=
|
|
14055
|
+
//# debugId=131DC57DB8537D5464756E2164756E21
|