claudemesh-cli 1.6.0 → 1.7.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 +6 -1
- package/dist/entrypoints/cli.js +440 -20
- package/dist/entrypoints/cli.js.map +12 -8
- package/dist/entrypoints/mcp.js +61 -8
- package/dist/entrypoints/mcp.js.map +4 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Peer mesh for Claude Code sessions. Connect multiple Claude Code instances into a shared mesh with real-time messaging, shared state, memory, file sharing, vector store, scheduled jobs, and more — all driven from the `claudemesh` CLI. The MCP server is a tool-less push-pipe that delivers inbound peer messages to Claude as `<channel>` interrupts; everything else lives behind CLI verbs that Claude learns from the auto-installed `claudemesh` skill.
|
|
4
4
|
|
|
5
|
-
> **What's new in 1.
|
|
5
|
+
> **What's new in 1.7.0:** terminal parity for the v1.6.x server features. New verbs: `claudemesh topic tail` (live SSE message stream — Ctrl-C to exit), `claudemesh notification list` (recent `@you` mentions across topics), `claudemesh member list` (mesh roster with online dots, distinct from `peer list`'s live-session view). Each command auto-mints a 5-minute read-only apikey via the WebSocket and revokes it on exit, so no token plumbing is needed.
|
|
6
|
+
>
|
|
7
|
+
> **What was new in 1.6.0:** topics (channel pub/sub), API keys for human/REST clients, and bridge peers that forward a topic between two meshes. New verbs: `claudemesh topic`, `claudemesh apikey`, `claudemesh bridge`. A REST surface at `https://claudemesh.com/api/v1/*` (messages, topics, peers, history) accepts `Authorization: Bearer cm_...` keys, so any HTTPS client can participate without WebSocket + ed25519 plumbing. **Note**: REST lives on the web host (`claudemesh.com`), not the broker host (`ic.claudemesh.com`) — the broker only speaks WebSocket.
|
|
6
8
|
>
|
|
7
9
|
> **Migration note (1.5.0):** the previous 79 MCP tools (`send_message`, `list_peers`, `remember`, …) are removed. Use the matching CLI verbs (`claudemesh send`, `claudemesh peers`, `claudemesh remember`). Run `claudemesh install` and the bundled skill teaches Claude the full surface.
|
|
8
10
|
|
|
@@ -43,6 +45,9 @@ USAGE
|
|
|
43
45
|
claudemesh profile view or edit your profile
|
|
44
46
|
|
|
45
47
|
claudemesh topic ... create, list, join, send to topics
|
|
48
|
+
claudemesh topic tail <t> live SSE tail of a topic
|
|
49
|
+
claudemesh member list mesh roster with online state
|
|
50
|
+
claudemesh notification list recent @-mentions of you
|
|
46
51
|
claudemesh apikey ... issue, list, revoke API keys (REST clients)
|
|
47
52
|
claudemesh bridge ... forward a topic between two meshes
|
|
48
53
|
|
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.7.0", 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
|
});
|
|
@@ -1138,6 +1156,7 @@ class BrokerClient {
|
|
|
1138
1156
|
topicHistoryResolvers = new Map;
|
|
1139
1157
|
apiKeyCreatedResolvers = new Map;
|
|
1140
1158
|
apiKeyListResolvers = new Map;
|
|
1159
|
+
apiKeyRevokeResolvers = new Map;
|
|
1141
1160
|
sharedDirs = [process.cwd()];
|
|
1142
1161
|
_serviceCatalog = [];
|
|
1143
1162
|
get serviceCatalog() {
|
|
@@ -1546,9 +1565,21 @@ class BrokerClient {
|
|
|
1546
1565
|
});
|
|
1547
1566
|
}
|
|
1548
1567
|
async apiKeyRevoke(id) {
|
|
1549
|
-
if (!this.ws || this.ws.readyState !== this.ws.OPEN)
|
|
1550
|
-
return;
|
|
1551
|
-
|
|
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
|
+
});
|
|
1552
1583
|
}
|
|
1553
1584
|
async setState(key, value) {
|
|
1554
1585
|
if (!this.ws || this.ws.readyState !== this.ws.OPEN)
|
|
@@ -2667,6 +2698,28 @@ class BrokerClient {
|
|
|
2667
2698
|
this.resolveFromMap(this.apiKeyListResolvers, msgReqId, msg.keys ?? []);
|
|
2668
2699
|
return;
|
|
2669
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
|
+
}
|
|
2670
2723
|
if (msg.type === "push") {
|
|
2671
2724
|
this._statsCounters.messagesIn++;
|
|
2672
2725
|
const nonce = String(msg.nonce ?? "");
|
|
@@ -7521,18 +7574,33 @@ async function whoami(opts) {
|
|
|
7521
7574
|
const result = await whoAmI();
|
|
7522
7575
|
if (opts.json) {
|
|
7523
7576
|
console.log(JSON.stringify({ schema_version: "1.0", ...result }, null, 2));
|
|
7524
|
-
return EXIT.SUCCESS;
|
|
7577
|
+
return result.signed_in || result.local ? EXIT.SUCCESS : EXIT.AUTH_FAILED;
|
|
7525
7578
|
}
|
|
7526
|
-
if (!result.signed_in) {
|
|
7527
|
-
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.");
|
|
7528
7581
|
return EXIT.AUTH_FAILED;
|
|
7529
7582
|
}
|
|
7530
7583
|
render.section("whoami");
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
|
|
7535
|
-
|
|
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
|
+
}
|
|
7536
7604
|
render.blank();
|
|
7537
7605
|
return EXIT.SUCCESS;
|
|
7538
7606
|
}
|
|
@@ -11394,11 +11462,19 @@ async function runApiKeyRevoke(id, flags) {
|
|
|
11394
11462
|
return EXIT.INVALID_ARGS;
|
|
11395
11463
|
}
|
|
11396
11464
|
return await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
|
|
11397
|
-
await client.apiKeyRevoke(id);
|
|
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
|
+
}
|
|
11398
11474
|
if (flags.json)
|
|
11399
|
-
console.log(JSON.stringify({ revoked: id }));
|
|
11475
|
+
console.log(JSON.stringify({ revoked: result.id }));
|
|
11400
11476
|
else
|
|
11401
|
-
render.ok("revoked", clay(id.slice(0, 8)));
|
|
11477
|
+
render.ok("revoked", clay(result.id.slice(0, 8)));
|
|
11402
11478
|
return EXIT.SUCCESS;
|
|
11403
11479
|
});
|
|
11404
11480
|
}
|
|
@@ -11572,6 +11648,304 @@ var init_topic = __esm(() => {
|
|
|
11572
11648
|
init_exit_codes();
|
|
11573
11649
|
});
|
|
11574
11650
|
|
|
11651
|
+
// src/services/api/with-rest-key.ts
|
|
11652
|
+
async function withRestKey(opts, fn) {
|
|
11653
|
+
return withMesh({ meshSlug: opts.meshSlug ?? null }, async (client, mesh) => {
|
|
11654
|
+
const result = await client.apiKeyCreate({
|
|
11655
|
+
label: `cli-${opts.purpose ?? "rest"}-${process.pid}`,
|
|
11656
|
+
capabilities: opts.capabilities ?? ["read"],
|
|
11657
|
+
topicScopes: opts.topicScopes ?? undefined,
|
|
11658
|
+
expiresAt: new Date(Date.now() + 5 * 60 * 1000).toISOString()
|
|
11659
|
+
});
|
|
11660
|
+
if (!result || !result.secret) {
|
|
11661
|
+
throw new Error("apikey mint failed — broker did not return a secret");
|
|
11662
|
+
}
|
|
11663
|
+
try {
|
|
11664
|
+
return await fn({
|
|
11665
|
+
secret: result.secret,
|
|
11666
|
+
meshId: mesh.meshId,
|
|
11667
|
+
meshSlug: mesh.slug,
|
|
11668
|
+
client,
|
|
11669
|
+
mesh
|
|
11670
|
+
});
|
|
11671
|
+
} finally {
|
|
11672
|
+
try {
|
|
11673
|
+
await client.apiKeyRevoke(result.id);
|
|
11674
|
+
} catch {}
|
|
11675
|
+
}
|
|
11676
|
+
});
|
|
11677
|
+
}
|
|
11678
|
+
var init_with_rest_key = __esm(() => {
|
|
11679
|
+
init_connect();
|
|
11680
|
+
});
|
|
11681
|
+
|
|
11682
|
+
// src/commands/topic-tail.ts
|
|
11683
|
+
var exports_topic_tail = {};
|
|
11684
|
+
__export(exports_topic_tail, {
|
|
11685
|
+
runTopicTail: () => runTopicTail
|
|
11686
|
+
});
|
|
11687
|
+
function decodeCiphertext(b64) {
|
|
11688
|
+
try {
|
|
11689
|
+
return Buffer.from(b64, "base64").toString("utf-8");
|
|
11690
|
+
} catch {
|
|
11691
|
+
return "[decode failed]";
|
|
11692
|
+
}
|
|
11693
|
+
}
|
|
11694
|
+
function fmtTime(iso) {
|
|
11695
|
+
try {
|
|
11696
|
+
return new Date(iso).toLocaleTimeString([], {
|
|
11697
|
+
hour: "2-digit",
|
|
11698
|
+
minute: "2-digit",
|
|
11699
|
+
second: "2-digit"
|
|
11700
|
+
});
|
|
11701
|
+
} catch {
|
|
11702
|
+
return iso;
|
|
11703
|
+
}
|
|
11704
|
+
}
|
|
11705
|
+
function printMessage(m, json) {
|
|
11706
|
+
const text = decodeCiphertext(m.ciphertext);
|
|
11707
|
+
if (json) {
|
|
11708
|
+
console.log(JSON.stringify({ ...m, message: text }));
|
|
11709
|
+
return;
|
|
11710
|
+
}
|
|
11711
|
+
process.stdout.write(` ${dim(fmtTime(m.createdAt))} ${bold(m.senderName || m.senderPubkey.slice(0, 8))} ${text}
|
|
11712
|
+
`);
|
|
11713
|
+
}
|
|
11714
|
+
async function* readSseStream(reader) {
|
|
11715
|
+
const decoder = new TextDecoder;
|
|
11716
|
+
let buffer = "";
|
|
11717
|
+
while (true) {
|
|
11718
|
+
const { value, done } = await reader.read();
|
|
11719
|
+
if (done)
|
|
11720
|
+
break;
|
|
11721
|
+
buffer += decoder.decode(value, { stream: true });
|
|
11722
|
+
let idx;
|
|
11723
|
+
while ((idx = buffer.indexOf(`
|
|
11724
|
+
|
|
11725
|
+
`)) !== -1) {
|
|
11726
|
+
const block = buffer.slice(0, idx);
|
|
11727
|
+
buffer = buffer.slice(idx + 2);
|
|
11728
|
+
const ev = { event: "message", data: "" };
|
|
11729
|
+
const dataLines = [];
|
|
11730
|
+
for (const line of block.split(`
|
|
11731
|
+
`)) {
|
|
11732
|
+
if (!line || line.startsWith(":"))
|
|
11733
|
+
continue;
|
|
11734
|
+
const colon = line.indexOf(":");
|
|
11735
|
+
if (colon < 0)
|
|
11736
|
+
continue;
|
|
11737
|
+
const field = line.slice(0, colon);
|
|
11738
|
+
const val = line.slice(colon + 1).replace(/^ /, "");
|
|
11739
|
+
if (field === "event")
|
|
11740
|
+
ev.event = val;
|
|
11741
|
+
else if (field === "id")
|
|
11742
|
+
ev.id = val;
|
|
11743
|
+
else if (field === "data")
|
|
11744
|
+
dataLines.push(val);
|
|
11745
|
+
}
|
|
11746
|
+
ev.data = dataLines.join(`
|
|
11747
|
+
`);
|
|
11748
|
+
yield ev;
|
|
11749
|
+
}
|
|
11750
|
+
}
|
|
11751
|
+
}
|
|
11752
|
+
async function runTopicTail(name, flags) {
|
|
11753
|
+
if (!name) {
|
|
11754
|
+
render.err("Usage: claudemesh topic tail <topic> [--limit N]");
|
|
11755
|
+
return EXIT.INVALID_ARGS;
|
|
11756
|
+
}
|
|
11757
|
+
const cleanName = name.replace(/^#/, "");
|
|
11758
|
+
const limit = flags.limit ? Number(flags.limit) : 20;
|
|
11759
|
+
return withRestKey({
|
|
11760
|
+
meshSlug: flags.mesh ?? null,
|
|
11761
|
+
purpose: `tail-${cleanName}`,
|
|
11762
|
+
capabilities: ["read"],
|
|
11763
|
+
topicScopes: [cleanName]
|
|
11764
|
+
}, async ({ secret, meshSlug }) => {
|
|
11765
|
+
if (!flags.forwardOnly && limit > 0) {
|
|
11766
|
+
try {
|
|
11767
|
+
const history = await request({
|
|
11768
|
+
path: `/api/v1/topics/${encodeURIComponent(cleanName)}/messages?limit=${limit}`,
|
|
11769
|
+
token: secret
|
|
11770
|
+
});
|
|
11771
|
+
if (!flags.json) {
|
|
11772
|
+
render.section(`${clay("#" + cleanName)} on ${dim(meshSlug)} — backfill ${history.messages.length}, then live`);
|
|
11773
|
+
}
|
|
11774
|
+
for (const m of history.messages.slice().reverse()) {
|
|
11775
|
+
printMessage(m, flags.json ?? false);
|
|
11776
|
+
}
|
|
11777
|
+
} catch (err) {
|
|
11778
|
+
render.warn(`backfill failed: ${err.message}`);
|
|
11779
|
+
}
|
|
11780
|
+
}
|
|
11781
|
+
const url = `${URLS.API_BASE}/api/v1/topics/${encodeURIComponent(cleanName)}/stream`;
|
|
11782
|
+
const ctl = new AbortController;
|
|
11783
|
+
const onSig = () => ctl.abort();
|
|
11784
|
+
process.once("SIGINT", onSig);
|
|
11785
|
+
process.once("SIGTERM", onSig);
|
|
11786
|
+
try {
|
|
11787
|
+
const res = await fetch(url, {
|
|
11788
|
+
headers: { Authorization: `Bearer ${secret}` },
|
|
11789
|
+
signal: ctl.signal
|
|
11790
|
+
});
|
|
11791
|
+
if (!res.ok || !res.body) {
|
|
11792
|
+
render.err(`stream open failed: ${res.status}`);
|
|
11793
|
+
return EXIT.INTERNAL_ERROR;
|
|
11794
|
+
}
|
|
11795
|
+
if (!flags.json) {
|
|
11796
|
+
render.info(dim("tailing — Ctrl-C to exit"));
|
|
11797
|
+
}
|
|
11798
|
+
const reader = res.body.getReader();
|
|
11799
|
+
for await (const ev of readSseStream(reader)) {
|
|
11800
|
+
if (ev.event === "ready" || ev.event === "heartbeat")
|
|
11801
|
+
continue;
|
|
11802
|
+
if (ev.event === "error") {
|
|
11803
|
+
try {
|
|
11804
|
+
const parsed = JSON.parse(ev.data);
|
|
11805
|
+
render.err(`stream error: ${parsed.error ?? "unknown"}`);
|
|
11806
|
+
} catch {
|
|
11807
|
+
render.err("stream error");
|
|
11808
|
+
}
|
|
11809
|
+
continue;
|
|
11810
|
+
}
|
|
11811
|
+
if (ev.event === "message") {
|
|
11812
|
+
try {
|
|
11813
|
+
const m = JSON.parse(ev.data);
|
|
11814
|
+
printMessage(m, flags.json ?? false);
|
|
11815
|
+
} catch {}
|
|
11816
|
+
}
|
|
11817
|
+
}
|
|
11818
|
+
return EXIT.SUCCESS;
|
|
11819
|
+
} catch (err) {
|
|
11820
|
+
if (ctl.signal.aborted)
|
|
11821
|
+
return EXIT.SUCCESS;
|
|
11822
|
+
render.err(`tail aborted: ${err.message}`);
|
|
11823
|
+
return EXIT.INTERNAL_ERROR;
|
|
11824
|
+
} finally {
|
|
11825
|
+
process.removeListener("SIGINT", onSig);
|
|
11826
|
+
process.removeListener("SIGTERM", onSig);
|
|
11827
|
+
}
|
|
11828
|
+
});
|
|
11829
|
+
}
|
|
11830
|
+
var init_topic_tail = __esm(() => {
|
|
11831
|
+
init_urls();
|
|
11832
|
+
init_with_rest_key();
|
|
11833
|
+
init_client();
|
|
11834
|
+
init_render();
|
|
11835
|
+
init_styles();
|
|
11836
|
+
init_exit_codes();
|
|
11837
|
+
});
|
|
11838
|
+
|
|
11839
|
+
// src/commands/notification.ts
|
|
11840
|
+
var exports_notification = {};
|
|
11841
|
+
__export(exports_notification, {
|
|
11842
|
+
runNotificationList: () => runNotificationList
|
|
11843
|
+
});
|
|
11844
|
+
function decodeCiphertext2(b64) {
|
|
11845
|
+
try {
|
|
11846
|
+
return Buffer.from(b64, "base64").toString("utf-8");
|
|
11847
|
+
} catch {
|
|
11848
|
+
return "[decode failed]";
|
|
11849
|
+
}
|
|
11850
|
+
}
|
|
11851
|
+
function fmtRelative(iso) {
|
|
11852
|
+
const ms = Date.now() - new Date(iso).getTime();
|
|
11853
|
+
if (ms < 60000)
|
|
11854
|
+
return "now";
|
|
11855
|
+
if (ms < 3600000)
|
|
11856
|
+
return `${Math.floor(ms / 60000)}m`;
|
|
11857
|
+
if (ms < 86400000)
|
|
11858
|
+
return `${Math.floor(ms / 3600000)}h`;
|
|
11859
|
+
return `${Math.floor(ms / 86400000)}d`;
|
|
11860
|
+
}
|
|
11861
|
+
async function runNotificationList(flags) {
|
|
11862
|
+
return withRestKey({ meshSlug: flags.mesh ?? null, purpose: "notifications" }, async ({ secret }) => {
|
|
11863
|
+
const qs = flags.since ? `?since=${encodeURIComponent(flags.since)}` : "";
|
|
11864
|
+
const result = await request({
|
|
11865
|
+
path: `/api/v1/notifications${qs}`,
|
|
11866
|
+
token: secret
|
|
11867
|
+
});
|
|
11868
|
+
if (flags.json) {
|
|
11869
|
+
const decoded = result.notifications.map((n) => ({
|
|
11870
|
+
...n,
|
|
11871
|
+
message: decodeCiphertext2(n.ciphertext)
|
|
11872
|
+
}));
|
|
11873
|
+
console.log(JSON.stringify({ ...result, notifications: decoded }, null, 2));
|
|
11874
|
+
return EXIT.SUCCESS;
|
|
11875
|
+
}
|
|
11876
|
+
if (result.notifications.length === 0) {
|
|
11877
|
+
render.info(dim(`no mentions of @${result.mentionedAs} since ${result.since}.`));
|
|
11878
|
+
return EXIT.SUCCESS;
|
|
11879
|
+
}
|
|
11880
|
+
render.section(`mentions of @${bold(result.mentionedAs)} (${result.notifications.length})`);
|
|
11881
|
+
for (const n of result.notifications) {
|
|
11882
|
+
const when = fmtRelative(n.createdAt);
|
|
11883
|
+
const msg = decodeCiphertext2(n.ciphertext).replace(/\s+/g, " ").trim();
|
|
11884
|
+
const snippet = msg.length > 100 ? msg.slice(0, 97) + "…" : msg;
|
|
11885
|
+
process.stdout.write(` ${clay("#" + n.topicName)} ${dim(when)} ${bold(n.senderName)}
|
|
11886
|
+
`);
|
|
11887
|
+
process.stdout.write(` ${snippet}
|
|
11888
|
+
`);
|
|
11889
|
+
}
|
|
11890
|
+
return EXIT.SUCCESS;
|
|
11891
|
+
});
|
|
11892
|
+
}
|
|
11893
|
+
var init_notification = __esm(() => {
|
|
11894
|
+
init_with_rest_key();
|
|
11895
|
+
init_client();
|
|
11896
|
+
init_render();
|
|
11897
|
+
init_styles();
|
|
11898
|
+
init_exit_codes();
|
|
11899
|
+
});
|
|
11900
|
+
|
|
11901
|
+
// src/commands/member.ts
|
|
11902
|
+
var exports_member = {};
|
|
11903
|
+
__export(exports_member, {
|
|
11904
|
+
runMemberList: () => runMemberList
|
|
11905
|
+
});
|
|
11906
|
+
function statusGlyph(m) {
|
|
11907
|
+
if (!m.online)
|
|
11908
|
+
return dim("○");
|
|
11909
|
+
if (m.status === "dnd")
|
|
11910
|
+
return red("●");
|
|
11911
|
+
if (m.status === "working")
|
|
11912
|
+
return yellow("●");
|
|
11913
|
+
return green("●");
|
|
11914
|
+
}
|
|
11915
|
+
async function runMemberList(flags) {
|
|
11916
|
+
return withRestKey({ meshSlug: flags.mesh ?? null, purpose: "members" }, async ({ secret, meshSlug }) => {
|
|
11917
|
+
const result = await request({
|
|
11918
|
+
path: "/api/v1/members",
|
|
11919
|
+
token: secret
|
|
11920
|
+
});
|
|
11921
|
+
const filtered = flags.online ? result.members.filter((m) => m.online) : result.members;
|
|
11922
|
+
if (flags.json) {
|
|
11923
|
+
console.log(JSON.stringify({ members: filtered }, null, 2));
|
|
11924
|
+
return EXIT.SUCCESS;
|
|
11925
|
+
}
|
|
11926
|
+
if (filtered.length === 0) {
|
|
11927
|
+
render.info(dim(flags.online ? `no online members in ${meshSlug}.` : `no members in ${meshSlug}.`));
|
|
11928
|
+
return EXIT.SUCCESS;
|
|
11929
|
+
}
|
|
11930
|
+
const onlineCount = result.members.filter((m) => m.online).length;
|
|
11931
|
+
render.section(`${clay(meshSlug)} members (${onlineCount}/${result.members.length} online)`);
|
|
11932
|
+
for (const m of filtered) {
|
|
11933
|
+
const tag = m.isHuman ? dim("human") : dim("bot");
|
|
11934
|
+
const summary = m.summary ? ` — ${dim(m.summary)}` : "";
|
|
11935
|
+
process.stdout.write(` ${statusGlyph(m)} ${bold(m.displayName)} ${tag} ${dim(m.role)} ${dim(m.pubkey.slice(0, 8))}${summary}
|
|
11936
|
+
`);
|
|
11937
|
+
}
|
|
11938
|
+
return EXIT.SUCCESS;
|
|
11939
|
+
});
|
|
11940
|
+
}
|
|
11941
|
+
var init_member = __esm(() => {
|
|
11942
|
+
init_with_rest_key();
|
|
11943
|
+
init_client();
|
|
11944
|
+
init_render();
|
|
11945
|
+
init_styles();
|
|
11946
|
+
init_exit_codes();
|
|
11947
|
+
});
|
|
11948
|
+
|
|
11575
11949
|
// src/mcp/tools/definitions.ts
|
|
11576
11950
|
var TOOLS;
|
|
11577
11951
|
var init_definitions = __esm(() => {
|
|
@@ -13143,7 +13517,10 @@ Topic (conversation scope, v0.2.0)
|
|
|
13143
13517
|
claudemesh topic members <t> list topic subscribers
|
|
13144
13518
|
claudemesh topic history <t> fetch message history [--limit --before]
|
|
13145
13519
|
claudemesh topic read <topic> mark all as read
|
|
13520
|
+
claudemesh topic tail <topic> live SSE tail [--limit --forward-only]
|
|
13146
13521
|
claudemesh send "#topic" "msg" send to a topic
|
|
13522
|
+
claudemesh member list mesh roster with online state [--online]
|
|
13523
|
+
claudemesh notification list recent @-mentions of you [--since <ISO>]
|
|
13147
13524
|
|
|
13148
13525
|
Schedule (resource form)
|
|
13149
13526
|
claudemesh schedule msg <m> one-shot or recurring (alias: remind)
|
|
@@ -13922,8 +14299,51 @@ async function main() {
|
|
|
13922
14299
|
} else if (sub === "read") {
|
|
13923
14300
|
const { runTopicMarkRead: runTopicMarkRead2 } = await Promise.resolve().then(() => (init_topic(), exports_topic));
|
|
13924
14301
|
process.exit(await runTopicMarkRead2(arg, f));
|
|
14302
|
+
} else if (sub === "tail") {
|
|
14303
|
+
const tailFlags = {
|
|
14304
|
+
mesh: flags.mesh,
|
|
14305
|
+
json: !!flags.json,
|
|
14306
|
+
limit: flags.limit,
|
|
14307
|
+
forwardOnly: !!flags["forward-only"]
|
|
14308
|
+
};
|
|
14309
|
+
const { runTopicTail: runTopicTail2 } = await Promise.resolve().then(() => (init_topic_tail(), exports_topic_tail));
|
|
14310
|
+
process.exit(await runTopicTail2(arg, tailFlags));
|
|
14311
|
+
} else {
|
|
14312
|
+
console.error("Usage: claudemesh topic <create|list|join|leave|members|history|read|tail>");
|
|
14313
|
+
process.exit(EXIT.INVALID_ARGS);
|
|
14314
|
+
}
|
|
14315
|
+
break;
|
|
14316
|
+
}
|
|
14317
|
+
case "notification":
|
|
14318
|
+
case "notifications": {
|
|
14319
|
+
const sub = positionals[0] ?? "list";
|
|
14320
|
+
const f = {
|
|
14321
|
+
mesh: flags.mesh,
|
|
14322
|
+
json: !!flags.json,
|
|
14323
|
+
since: flags.since
|
|
14324
|
+
};
|
|
14325
|
+
if (sub === "list") {
|
|
14326
|
+
const { runNotificationList: runNotificationList2 } = await Promise.resolve().then(() => (init_notification(), exports_notification));
|
|
14327
|
+
process.exit(await runNotificationList2(f));
|
|
14328
|
+
} else {
|
|
14329
|
+
console.error("Usage: claudemesh notification list [--since <ISO>]");
|
|
14330
|
+
process.exit(EXIT.INVALID_ARGS);
|
|
14331
|
+
}
|
|
14332
|
+
break;
|
|
14333
|
+
}
|
|
14334
|
+
case "member":
|
|
14335
|
+
case "members": {
|
|
14336
|
+
const sub = positionals[0] ?? "list";
|
|
14337
|
+
const f = {
|
|
14338
|
+
mesh: flags.mesh,
|
|
14339
|
+
json: !!flags.json,
|
|
14340
|
+
online: !!flags.online
|
|
14341
|
+
};
|
|
14342
|
+
if (sub === "list") {
|
|
14343
|
+
const { runMemberList: runMemberList2 } = await Promise.resolve().then(() => (init_member(), exports_member));
|
|
14344
|
+
process.exit(await runMemberList2(f));
|
|
13925
14345
|
} else {
|
|
13926
|
-
console.error("Usage: claudemesh
|
|
14346
|
+
console.error("Usage: claudemesh member list [--online]");
|
|
13927
14347
|
process.exit(EXIT.INVALID_ARGS);
|
|
13928
14348
|
}
|
|
13929
14349
|
break;
|
|
@@ -13976,4 +14396,4 @@ main().catch((err) => {
|
|
|
13976
14396
|
process.exit(EXIT.INTERNAL_ERROR);
|
|
13977
14397
|
});
|
|
13978
14398
|
|
|
13979
|
-
//# debugId=
|
|
14399
|
+
//# debugId=81FA5977F36B50C364756E2164756E21
|