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 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.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`. The broker now exposes a REST surface at `/api/v1/*` (messages, topics, peers, history) for non-WebSocket clients.
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
 
@@ -88,7 +88,7 @@ __export(exports_urls, {
88
88
  VERSION: () => VERSION,
89
89
  URLS: () => URLS
90
90
  });
91
- var URLS, VERSION = "1.6.0", env;
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
- this.ws.send(JSON.stringify({ type: "apikey_revoke", 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
+ });
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
- render.kv([
7532
- ["user", `${bold(result.user.display_name)} ${dim(`(${result.user.email})`)}`],
7533
- ["token", `${result.token_source} ${dim("(~/.claudemesh/auth.json)")}`],
7534
- ...result.meshes ? [["meshes", `${result.meshes.owned} owned · ${result.meshes.guest} guest`]] : []
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 topic <create|list|join|leave|members|history|read>");
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=E78F8FEDBA22CD8C64756E2164756E21
14399
+ //# debugId=81FA5977F36B50C364756E2164756E21