claudemesh-cli 1.25.0 → 1.27.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.
@@ -103,7 +103,7 @@ __export(exports_urls, {
103
103
  VERSION: () => VERSION,
104
104
  URLS: () => URLS
105
105
  });
106
- var URLS, VERSION = "1.25.0", env;
106
+ var URLS, VERSION = "1.27.0", env;
107
107
  var init_urls = __esm(() => {
108
108
  URLS = {
109
109
  BROKER: process.env.CLAUDEMESH_BROKER_URL ?? "wss://ic.claudemesh.com/ws",
@@ -6524,1226 +6524,1369 @@ var init_connect = __esm(() => {
6524
6524
  init_facade();
6525
6525
  });
6526
6526
 
6527
- // src/commands/kick.ts
6528
- var exports_kick = {};
6529
- __export(exports_kick, {
6530
- runKick: () => runKick,
6531
- runDisconnect: () => runDisconnect
6527
+ // src/commands/info.ts
6528
+ var exports_info = {};
6529
+ __export(exports_info, {
6530
+ runInfo: () => runInfo
6532
6531
  });
6533
- function parseStaleMs(input) {
6534
- const m = input.match(/^(\d+)(s|m|h)$/);
6535
- if (!m)
6536
- return null;
6537
- const val = parseInt(m[1], 10);
6538
- const unit = m[2];
6539
- if (unit === "s")
6540
- return val * 1000;
6541
- if (unit === "m")
6542
- return val * 60000;
6543
- if (unit === "h")
6544
- return val * 3600000;
6545
- return null;
6546
- }
6547
- function buildPayload(kind, target, opts) {
6548
- if (opts.all)
6549
- return { type: kind, all: true };
6550
- if (opts.stale) {
6551
- const ms = parseStaleMs(opts.stale);
6552
- if (!ms)
6553
- return { error: `Invalid stale duration: "${opts.stale}". Use e.g. 30m, 1h, 300s.` };
6554
- return { type: kind, stale: ms };
6555
- }
6556
- if (target)
6557
- return { type: kind, target };
6558
- return { error: `Usage: claudemesh ${kind} <peer> | --stale 30m | --all` };
6559
- }
6560
- async function runDisconnect(target, opts = {}) {
6532
+ async function runInfo(flags) {
6561
6533
  const config = readConfig();
6562
- const meshSlug = opts.mesh ?? config.meshes[0]?.slug;
6563
- if (!meshSlug) {
6564
- render.err("No mesh joined.");
6565
- return EXIT.NOT_FOUND;
6566
- }
6567
- const built = buildPayload("disconnect", target, opts);
6568
- if ("error" in built) {
6569
- render.err(String(built.error));
6570
- return EXIT.INVALID_ARGS;
6571
- }
6572
- return await withMesh({ meshSlug }, async (client) => {
6573
- const result = await client.sendAndWait(built);
6574
- const peers = result?.affected ?? result?.kicked ?? [];
6575
- if (peers.length === 0)
6576
- render.info("No peers matched.");
6577
- else {
6578
- render.ok(`Disconnected ${peers.length} peer(s): ${peers.join(", ")}`);
6579
- render.hint("They will auto-reconnect within seconds. For a session-ending kick, use `claudemesh kick`.");
6534
+ await withMesh({ meshSlug: flags.mesh ?? null }, async (client, mesh) => {
6535
+ const [brokerInfo, peers, state] = await Promise.all([
6536
+ client.meshInfo(),
6537
+ client.listPeers(),
6538
+ client.listState()
6539
+ ]);
6540
+ const output = {
6541
+ slug: mesh.slug,
6542
+ meshId: mesh.meshId,
6543
+ memberId: mesh.memberId,
6544
+ brokerUrl: mesh.brokerUrl,
6545
+ displayName: config.displayName ?? null,
6546
+ peerCount: peers.length,
6547
+ stateCount: state.length,
6548
+ ...brokerInfo ?? {}
6549
+ };
6550
+ if (flags.json) {
6551
+ process.stdout.write(JSON.stringify(output, null, 2) + `
6552
+ `);
6553
+ return;
6580
6554
  }
6581
- return EXIT.SUCCESS;
6582
- });
6583
- }
6584
- async function runKick(target, opts = {}) {
6585
- const config = readConfig();
6586
- const meshSlug = opts.mesh ?? config.meshes[0]?.slug;
6587
- if (!meshSlug) {
6588
- render.err("No mesh joined.");
6589
- return EXIT.NOT_FOUND;
6590
- }
6591
- const built = buildPayload("kick", target, opts);
6592
- if ("error" in built) {
6593
- render.err(String(built.error));
6594
- return EXIT.INVALID_ARGS;
6595
- }
6596
- return await withMesh({ meshSlug }, async (client) => {
6597
- const result = await client.sendAndWait(built);
6598
- const peers = result?.affected ?? result?.kicked ?? [];
6599
- if (peers.length === 0)
6600
- render.info("No peers matched.");
6601
- else {
6602
- render.ok(`Kicked ${peers.length} peer(s): ${peers.join(", ")}`);
6603
- render.hint("Their Claude Code session ended. They can rejoin anytime by running `claudemesh`.");
6555
+ render.section(`${mesh.slug} · ${mesh.brokerUrl}`);
6556
+ render.kv([
6557
+ ["mesh", mesh.meshId],
6558
+ ["member", mesh.memberId],
6559
+ ["peers", `${peers.length} connected`],
6560
+ ["state", `${state.length} keys`]
6561
+ ]);
6562
+ if (brokerInfo && typeof brokerInfo === "object") {
6563
+ const extras = [];
6564
+ for (const [k, v] of Object.entries(brokerInfo)) {
6565
+ if (["slug", "meshId", "brokerUrl"].includes(k))
6566
+ continue;
6567
+ extras.push([k, JSON.stringify(v)]);
6568
+ }
6569
+ if (extras.length)
6570
+ render.kv(extras);
6604
6571
  }
6605
- return EXIT.SUCCESS;
6606
6572
  });
6607
6573
  }
6608
- var init_kick = __esm(() => {
6574
+ var init_info2 = __esm(() => {
6609
6575
  init_connect();
6610
6576
  init_facade();
6611
6577
  init_render();
6612
- init_exit_codes();
6613
6578
  });
6614
6579
 
6615
- // src/commands/ban.ts
6616
- var exports_ban = {};
6617
- __export(exports_ban, {
6618
- runUnban: () => runUnban,
6619
- runBans: () => runBans,
6620
- runBan: () => runBan
6621
- });
6622
- async function runBan(target, opts = {}) {
6623
- if (!target) {
6624
- render.err("Usage: claudemesh ban <peer-name-or-pubkey>");
6625
- return EXIT.INVALID_ARGS;
6626
- }
6627
- const config = readConfig();
6628
- const meshSlug = opts.mesh ?? config.meshes[0]?.slug;
6629
- if (!meshSlug) {
6630
- render.err("No mesh joined.");
6631
- return EXIT.NOT_FOUND;
6632
- }
6633
- return await withMesh({ meshSlug }, async (client) => {
6634
- const result = await client.sendAndWait({ type: "ban", target });
6635
- if (result?.banned) {
6636
- render.ok(`Banned ${result.banned} from ${meshSlug}. They cannot reconnect until unbanned.`);
6637
- render.hint(`Undo: claudemesh unban ${result.banned} --mesh ${meshSlug}`);
6638
- } else {
6639
- render.err(result?.message ?? result?.error ?? result?.code ?? "ban failed");
6640
- }
6641
- return result?.banned ? EXIT.SUCCESS : EXIT.INTERNAL_ERROR;
6642
- });
6643
- }
6644
- async function runUnban(target, opts = {}) {
6645
- if (!target) {
6646
- render.err("Usage: claudemesh unban <peer-name-or-pubkey>");
6647
- return EXIT.INVALID_ARGS;
6648
- }
6649
- const config = readConfig();
6650
- const meshSlug = opts.mesh ?? config.meshes[0]?.slug;
6651
- if (!meshSlug) {
6652
- render.err("No mesh joined.");
6653
- return EXIT.NOT_FOUND;
6654
- }
6655
- return await withMesh({ meshSlug }, async (client) => {
6656
- const result = await client.sendAndWait({ type: "unban", target });
6657
- if (result?.unbanned) {
6658
- render.ok(`Unbanned ${result.unbanned} from ${meshSlug}. They can rejoin.`);
6659
- } else {
6660
- render.err(result?.message ?? result?.error ?? result?.code ?? "unban failed");
6661
- }
6662
- return result?.unbanned ? EXIT.SUCCESS : EXIT.INTERNAL_ERROR;
6663
- });
6664
- }
6665
- async function runBans(opts = {}) {
6666
- const config = readConfig();
6667
- const meshSlug = opts.mesh ?? config.meshes[0]?.slug;
6668
- if (!meshSlug) {
6669
- render.err("No mesh joined.");
6670
- return EXIT.NOT_FOUND;
6671
- }
6672
- return await withMesh({ meshSlug }, async (client) => {
6673
- const result = await client.sendAndWait({ type: "list_bans" });
6674
- const bans = result?.bans ?? [];
6675
- if (opts.json) {
6676
- process.stdout.write(JSON.stringify(bans, null, 2) + `
6677
- `);
6678
- return EXIT.SUCCESS;
6679
- }
6680
- if (bans.length === 0) {
6681
- render.info("No banned members.");
6682
- return EXIT.SUCCESS;
6580
+ // src/services/api/with-rest-key.ts
6581
+ async function withRestKey(opts, fn) {
6582
+ return withMesh({ meshSlug: opts.meshSlug ?? null }, async (client, mesh) => {
6583
+ const result = await client.apiKeyCreate({
6584
+ label: `cli-${opts.purpose ?? "rest"}-${process.pid}`,
6585
+ capabilities: opts.capabilities ?? ["read"],
6586
+ topicScopes: opts.topicScopes ?? undefined,
6587
+ expiresAt: new Date(Date.now() + 5 * 60 * 1000).toISOString()
6588
+ });
6589
+ if (!result || !result.secret) {
6590
+ throw new Error("apikey mint failed — broker did not return a secret");
6683
6591
  }
6684
- render.section(`banned members on ${meshSlug}`);
6685
- for (const b of bans) {
6686
- render.kv([[b.name, `${b.pubkey.slice(0, 16)}… · banned ${new Date(b.revokedAt).toLocaleDateString()}`]]);
6592
+ try {
6593
+ return await fn({
6594
+ secret: result.secret,
6595
+ meshId: mesh.meshId,
6596
+ meshSlug: mesh.slug,
6597
+ client,
6598
+ mesh
6599
+ });
6600
+ } finally {
6601
+ try {
6602
+ await client.apiKeyRevoke(result.id);
6603
+ } catch {}
6687
6604
  }
6688
- return EXIT.SUCCESS;
6689
6605
  });
6690
6606
  }
6691
- var init_ban = __esm(() => {
6607
+ var init_with_rest_key = __esm(() => {
6692
6608
  init_connect();
6693
- init_facade();
6694
- init_render();
6695
- init_exit_codes();
6696
6609
  });
6697
6610
 
6698
- // src/services/bridge/protocol.ts
6699
- import { homedir as homedir5 } from "node:os";
6700
- import { join as join6 } from "node:path";
6701
- function socketPath(meshSlug) {
6702
- return join6(homedir5(), ".claudemesh", "sockets", `${meshSlug}.sock`);
6703
- }
6704
- function frame(obj) {
6705
- return JSON.stringify(obj) + `
6706
- `;
6611
+ // src/commands/me.ts
6612
+ var exports_me = {};
6613
+ __export(exports_me, {
6614
+ runMeTopics: () => runMeTopics,
6615
+ runMeTasks: () => runMeTasks,
6616
+ runMeState: () => runMeState,
6617
+ runMeSearch: () => runMeSearch,
6618
+ runMeNotifications: () => runMeNotifications,
6619
+ runMeMemory: () => runMeMemory,
6620
+ runMeActivity: () => runMeActivity,
6621
+ runMe: () => runMe
6622
+ });
6623
+ function resolveMeshForMint(explicit) {
6624
+ if (explicit)
6625
+ return explicit;
6626
+ const cfg = readConfig();
6627
+ return cfg.meshes[0]?.slug ?? null;
6707
6628
  }
6708
-
6709
- class LineParser {
6710
- buf = "";
6711
- feed(chunk) {
6712
- this.buf += typeof chunk === "string" ? chunk : chunk.toString("utf-8");
6713
- const lines = [];
6714
- let nl = this.buf.indexOf(`
6715
- `);
6716
- while (nl !== -1) {
6717
- lines.push(this.buf.slice(0, nl));
6718
- this.buf = this.buf.slice(nl + 1);
6719
- nl = this.buf.indexOf(`
6629
+ async function runMe(flags) {
6630
+ return withRestKey({
6631
+ meshSlug: resolveMeshForMint(flags.mesh),
6632
+ purpose: "workspace-overview",
6633
+ capabilities: ["read"]
6634
+ }, async ({ secret }) => {
6635
+ const ws = await request({
6636
+ path: "/api/v1/me/workspace",
6637
+ token: secret
6638
+ });
6639
+ if (flags.json) {
6640
+ console.log(JSON.stringify(ws, null, 2));
6641
+ return EXIT.SUCCESS;
6642
+ }
6643
+ render.section(`${clay("workspace")} — ${bold(ws.userId.slice(0, 8))} ${dim(`· ${ws.totals.meshes} mesh${ws.totals.meshes === 1 ? "" : "es"}`)}`);
6644
+ const totalsLine = [
6645
+ `${green(String(ws.totals.online))}/${ws.totals.peers} online`,
6646
+ `${ws.totals.topics} topic${ws.totals.topics === 1 ? "" : "s"}`,
6647
+ ws.totals.unreadMentions > 0 ? yellow(`${ws.totals.unreadMentions} unread @you`) : dim("0 unread @you")
6648
+ ].join(dim(" · "));
6649
+ process.stdout.write(" " + totalsLine + `
6650
+
6720
6651
  `);
6652
+ if (ws.meshes.length === 0) {
6653
+ process.stdout.write(dim(" no meshes joined — run `claudemesh new` or accept an invite\n"));
6654
+ return EXIT.SUCCESS;
6721
6655
  }
6722
- return lines;
6723
- }
6656
+ const slugWidth = Math.max(...ws.meshes.map((m) => m.slug.length), 8);
6657
+ for (const m of ws.meshes) {
6658
+ const slug = cyan(m.slug.padEnd(slugWidth));
6659
+ const peers = `${m.online}/${m.peers}`;
6660
+ const role = dim(m.myRole);
6661
+ const unread = m.unreadMentions > 0 ? " " + yellow(`${m.unreadMentions} @you`) : "";
6662
+ process.stdout.write(` ${slug} ${peers.padStart(5)} online ${dim(String(m.topics).padStart(2) + " topics")} ${role}${unread}
6663
+ `);
6664
+ }
6665
+ return EXIT.SUCCESS;
6666
+ });
6724
6667
  }
6725
- var init_protocol = () => {};
6726
-
6727
- // src/services/bridge/client.ts
6728
- import { createConnection } from "node:net";
6729
- import { existsSync as existsSync6 } from "node:fs";
6730
- import { randomUUID as randomUUID2 } from "node:crypto";
6731
- async function tryBridge(meshSlug, verb, args = {}, timeoutMs = DEFAULT_TIMEOUT_MS) {
6732
- const path = socketPath(meshSlug);
6733
- if (!existsSync6(path))
6734
- return null;
6735
- return new Promise((resolve) => {
6736
- const id = randomUUID2();
6737
- const req = { id, verb, args };
6738
- const parser = new LineParser;
6739
- let settled = false;
6740
- const finish = (value) => {
6741
- if (settled)
6742
- return;
6743
- settled = true;
6744
- try {
6745
- socket.destroy();
6746
- } catch {}
6747
- clearTimeout(timer);
6748
- resolve(value);
6749
- };
6750
- const socket = createConnection({ path });
6751
- const timer = setTimeout(() => {
6752
- finish(null);
6753
- }, timeoutMs);
6754
- socket.on("connect", () => {
6755
- try {
6756
- socket.write(frame(req));
6757
- } catch {
6758
- finish(null);
6759
- }
6760
- });
6761
- socket.on("data", (chunk) => {
6762
- const lines = parser.feed(chunk);
6763
- for (const line of lines) {
6764
- if (!line.trim())
6765
- continue;
6766
- let res;
6767
- try {
6768
- res = JSON.parse(line);
6769
- } catch {
6770
- continue;
6771
- }
6772
- if (res.id !== id)
6773
- continue;
6774
- if (res.ok)
6775
- finish({ ok: true, result: res.result });
6776
- else
6777
- finish({ ok: false, error: res.error });
6778
- return;
6779
- }
6780
- });
6781
- socket.on("error", (err) => {
6782
- const code = err.code;
6783
- if (code === "ECONNREFUSED" || code === "ENOENT" || code === "EPERM") {
6784
- finish(null);
6785
- } else {
6786
- finish(null);
6787
- }
6788
- });
6789
- socket.on("close", () => {
6790
- finish(null);
6668
+ async function runMeTopics(flags) {
6669
+ return withRestKey({
6670
+ meshSlug: resolveMeshForMint(flags.mesh),
6671
+ purpose: "workspace-topics",
6672
+ capabilities: ["read"]
6673
+ }, async ({ secret }) => {
6674
+ const ws = await request({
6675
+ path: "/api/v1/me/topics",
6676
+ token: secret
6791
6677
  });
6678
+ const visible = flags.unread ? ws.topics.filter((t) => t.unread > 0) : ws.topics;
6679
+ if (flags.json) {
6680
+ console.log(JSON.stringify({ topics: visible, totals: ws.totals }, null, 2));
6681
+ return EXIT.SUCCESS;
6682
+ }
6683
+ render.section(`${clay("topics")} — ${ws.totals.topics} across all meshes ${dim(ws.totals.unread > 0 ? `· ${ws.totals.unread} unread` : "· all read")}`);
6684
+ if (visible.length === 0) {
6685
+ process.stdout.write(dim(flags.unread ? ` no unread topics
6686
+ ` : " no topics — run `claudemesh topic create #general`\n"));
6687
+ return EXIT.SUCCESS;
6688
+ }
6689
+ const slugWidth = Math.max(...visible.map((t) => t.meshSlug.length), 6);
6690
+ const nameWidth = Math.max(...visible.map((t) => t.name.length), 8);
6691
+ for (const t of visible) {
6692
+ const slug = dim(t.meshSlug.padEnd(slugWidth));
6693
+ const name = cyan(t.name.padEnd(nameWidth));
6694
+ const unread = t.unread > 0 ? yellow(`${t.unread} unread`.padStart(10)) : dim("·".padStart(10));
6695
+ const last = t.lastMessageAt ? dim(formatRelativeTime(t.lastMessageAt)) : dim("never");
6696
+ process.stdout.write(` ${slug} ${name} ${unread} ${last}
6697
+ `);
6698
+ }
6699
+ return EXIT.SUCCESS;
6792
6700
  });
6793
6701
  }
6794
- var DEFAULT_TIMEOUT_MS = 5000;
6795
- var init_client3 = __esm(() => {
6796
- init_protocol();
6797
- });
6798
-
6799
- // src/daemon/local-token.ts
6800
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "node:fs";
6801
- import { dirname as dirname3 } from "node:path";
6802
- import { randomBytes as randomBytes4 } from "node:crypto";
6803
- function readLocalToken() {
6804
- try {
6805
- return readFileSync5(DAEMON_PATHS.TOKEN_FILE, "utf8").trim();
6806
- } catch {
6807
- return null;
6808
- }
6702
+ async function runMeNotifications(flags) {
6703
+ return withRestKey({
6704
+ meshSlug: resolveMeshForMint(flags.mesh),
6705
+ purpose: "workspace-notifications",
6706
+ capabilities: ["read"]
6707
+ }, async ({ secret }) => {
6708
+ const params = new URLSearchParams;
6709
+ if (flags.all)
6710
+ params.set("include", "all");
6711
+ if (flags.since)
6712
+ params.set("since", flags.since);
6713
+ const path = "/api/v1/me/notifications" + (params.toString() ? `?${params.toString()}` : "");
6714
+ const ws = await request({
6715
+ path,
6716
+ token: secret
6717
+ });
6718
+ if (flags.json) {
6719
+ console.log(JSON.stringify(ws, null, 2));
6720
+ return EXIT.SUCCESS;
6721
+ }
6722
+ const headerLabel = flags.all ? "@-mentions (all)" : "@-mentions (unread)";
6723
+ render.section(`${clay(headerLabel)} — ${ws.totals.total} ${dim(ws.totals.unread > 0 ? `· ${ws.totals.unread} unread` : "· nothing pending")}`);
6724
+ if (ws.notifications.length === 0) {
6725
+ process.stdout.write(dim(flags.all ? ` no @-mentions in window
6726
+ ` : ` inbox zero — nothing waiting
6727
+ `));
6728
+ return EXIT.SUCCESS;
6729
+ }
6730
+ const slugWidth = Math.max(...ws.notifications.map((n) => n.meshSlug.length), 6);
6731
+ for (const n of ws.notifications) {
6732
+ const slug = dim(n.meshSlug.padEnd(slugWidth));
6733
+ const topic = cyan(`#${n.topicName}`);
6734
+ const sender = n.senderName ? `from ${n.senderName}` : "from ?";
6735
+ const ago = formatRelativeTime(n.createdAt);
6736
+ const dot = n.read ? dim("·") : yellow("●");
6737
+ const snippet = n.snippet ?? (n.ciphertext ? dim("[encrypted]") : dim("[empty]"));
6738
+ process.stdout.write(` ${dot} ${slug} ${topic} ${dim(sender)} ${dim(ago)}
6739
+ ` + ` ${snippet.length > 200 ? snippet.slice(0, 200) + "…" : snippet}
6740
+ `);
6741
+ }
6742
+ return EXIT.SUCCESS;
6743
+ });
6809
6744
  }
6810
- function ensureLocalToken() {
6811
- const existing = readLocalToken();
6812
- if (existing)
6813
- return existing;
6814
- mkdirSync4(dirname3(DAEMON_PATHS.TOKEN_FILE), { recursive: true, mode: 448 });
6815
- const tok = randomBytes4(32).toString("base64url");
6816
- writeFileSync6(DAEMON_PATHS.TOKEN_FILE, tok + `
6817
- `, { mode: 384 });
6818
- return tok;
6745
+ async function runMeActivity(flags) {
6746
+ return withRestKey({
6747
+ meshSlug: resolveMeshForMint(flags.mesh),
6748
+ purpose: "workspace-activity",
6749
+ capabilities: ["read"]
6750
+ }, async ({ secret }) => {
6751
+ const params = new URLSearchParams;
6752
+ if (flags.since)
6753
+ params.set("since", flags.since);
6754
+ const path = "/api/v1/me/activity" + (params.toString() ? `?${params.toString()}` : "");
6755
+ const ws = await request({
6756
+ path,
6757
+ token: secret
6758
+ });
6759
+ if (flags.json) {
6760
+ console.log(JSON.stringify(ws, null, 2));
6761
+ return EXIT.SUCCESS;
6762
+ }
6763
+ render.section(`${clay("activity")} — ${ws.totals.events} ${dim(flags.since ? `since ${flags.since}` : "in the last 24h")}`);
6764
+ if (ws.activity.length === 0) {
6765
+ process.stdout.write(dim(` quiet — no activity in window
6766
+ `));
6767
+ return EXIT.SUCCESS;
6768
+ }
6769
+ const slugWidth = Math.max(...ws.activity.map((a) => a.meshSlug.length), 6);
6770
+ for (const a of ws.activity) {
6771
+ const slug = dim(a.meshSlug.padEnd(slugWidth));
6772
+ const topic = cyan(`#${a.topicName}`);
6773
+ const sender = a.senderName ?? "?";
6774
+ const ago = formatRelativeTime(a.createdAt);
6775
+ const snippet = a.snippet ?? (a.ciphertext ? dim("[encrypted]") : dim("[empty]"));
6776
+ process.stdout.write(` ${slug} ${topic} ${dim(sender + " ·")} ${dim(ago)}
6777
+ ` + ` ${snippet.length > 200 ? snippet.slice(0, 200) + "…" : snippet}
6778
+ `);
6779
+ }
6780
+ return EXIT.SUCCESS;
6781
+ });
6819
6782
  }
6820
- var init_local_token = __esm(() => {
6821
- init_paths2();
6822
- });
6823
-
6824
- // src/daemon/ipc/client.ts
6825
- import { request as httpRequest } from "node:http";
6826
- async function ipc(opts) {
6827
- const useTcp = !!opts.preferTcp;
6828
- const headers = {
6829
- accept: "application/json",
6830
- host: "localhost"
6831
- };
6832
- let bodyBuf;
6833
- if (opts.body !== undefined) {
6834
- bodyBuf = Buffer.from(JSON.stringify(opts.body), "utf8");
6835
- headers["content-type"] = "application/json";
6836
- headers["content-length"] = String(bodyBuf.length);
6837
- }
6838
- if (useTcp) {
6839
- const tok = readLocalToken();
6840
- if (!tok)
6841
- throw new IpcError(0, null, "daemon local token not found; is the daemon running?");
6842
- headers.authorization = `Bearer ${tok}`;
6783
+ async function runMeSearch(flags) {
6784
+ if (!flags.query || flags.query.length < 2) {
6785
+ process.stderr.write(`Usage: claudemesh me search <query> (min 2 chars)
6786
+ `);
6787
+ return EXIT.INVALID_ARGS;
6843
6788
  }
6844
- return new Promise((resolve, reject) => {
6845
- const req = httpRequest(useTcp ? { host: DAEMON_TCP_HOST, port: DAEMON_TCP_DEFAULT_PORT, path: opts.path, method: opts.method ?? "GET", headers } : { socketPath: DAEMON_PATHS.SOCK_FILE, path: opts.path, method: opts.method ?? "GET", headers }, (res) => {
6846
- const chunks = [];
6847
- res.on("data", (c) => chunks.push(c));
6848
- res.on("end", () => {
6849
- const raw = Buffer.concat(chunks).toString("utf8");
6850
- let parsed = raw;
6851
- try {
6852
- parsed = raw.length > 0 ? JSON.parse(raw) : null;
6853
- } catch {}
6854
- resolve({ status: res.statusCode ?? 0, body: parsed });
6855
- });
6789
+ return withRestKey({
6790
+ meshSlug: resolveMeshForMint(flags.mesh),
6791
+ purpose: "workspace-search",
6792
+ capabilities: ["read"]
6793
+ }, async ({ secret }) => {
6794
+ const params = new URLSearchParams({ q: flags.query });
6795
+ const ws = await request({
6796
+ path: `/api/v1/me/search?${params.toString()}`,
6797
+ token: secret
6856
6798
  });
6857
- req.setTimeout(opts.timeoutMs ?? 5000, () => req.destroy(new Error("ipc_timeout")));
6858
- req.on("error", (err) => reject(err));
6859
- if (bodyBuf)
6860
- req.write(bodyBuf);
6861
- req.end();
6799
+ if (flags.json) {
6800
+ console.log(JSON.stringify(ws, null, 2));
6801
+ return EXIT.SUCCESS;
6802
+ }
6803
+ render.section(`${clay("search")} — "${flags.query}" ${dim(`${ws.totals.topics} topic${ws.totals.topics === 1 ? "" : "s"}, ` + `${ws.totals.messages} message${ws.totals.messages === 1 ? "" : "s"}`)}`);
6804
+ if (ws.topics.length === 0 && ws.messages.length === 0) {
6805
+ process.stdout.write(dim(` no matches
6806
+ `));
6807
+ return EXIT.SUCCESS;
6808
+ }
6809
+ if (ws.topics.length > 0) {
6810
+ process.stdout.write(dim(`
6811
+ topics
6812
+ `));
6813
+ const slugWidth = Math.max(...ws.topics.map((t) => t.meshSlug.length), 6);
6814
+ for (const t of ws.topics) {
6815
+ const slug = dim(t.meshSlug.padEnd(slugWidth));
6816
+ const name = cyan(`#${t.name}`);
6817
+ const desc = t.description ? dim(` — ${t.description}`) : "";
6818
+ process.stdout.write(` ${slug} ${name}${desc}
6819
+ `);
6820
+ }
6821
+ }
6822
+ if (ws.messages.length > 0) {
6823
+ process.stdout.write(dim(`
6824
+ messages
6825
+ `));
6826
+ const slugWidth = Math.max(...ws.messages.map((m) => m.meshSlug.length), 6);
6827
+ for (const m of ws.messages) {
6828
+ const slug = dim(m.meshSlug.padEnd(slugWidth));
6829
+ const topic = cyan(`#${m.topicName}`);
6830
+ const sender = m.senderName;
6831
+ const ago = formatRelativeTime(m.createdAt);
6832
+ const snippet = m.snippet ?? (m.bodyVersion === 2 ? dim("[encrypted — open the topic to decrypt]") : dim("[empty]"));
6833
+ const highlighted = m.snippet ? highlightMatch(snippet, flags.query) : snippet;
6834
+ process.stdout.write(` ${slug} ${topic} ${dim(sender + " ·")} ${dim(ago)}
6835
+ ` + ` ${highlighted}
6836
+ `);
6837
+ }
6838
+ }
6839
+ return EXIT.SUCCESS;
6862
6840
  });
6863
6841
  }
6864
- var IpcError;
6865
- var init_client4 = __esm(() => {
6866
- init_paths2();
6867
- init_local_token();
6868
- IpcError = class IpcError extends Error {
6869
- status;
6870
- payload;
6871
- constructor(status, payload, msg) {
6872
- super(msg);
6873
- this.status = status;
6874
- this.payload = payload;
6875
- }
6876
- };
6877
- });
6878
-
6879
- // src/services/bridge/daemon-route.ts
6880
- var exports_daemon_route = {};
6881
- __export(exports_daemon_route, {
6882
- trySendViaDaemon: () => trySendViaDaemon,
6883
- tryListSkillsViaDaemon: () => tryListSkillsViaDaemon,
6884
- tryListPeersViaDaemon: () => tryListPeersViaDaemon,
6885
- tryGetSkillViaDaemon: () => tryGetSkillViaDaemon
6886
- });
6887
- import { existsSync as existsSync7 } from "node:fs";
6888
- async function tryListPeersViaDaemon() {
6889
- if (!existsSync7(DAEMON_PATHS.SOCK_FILE))
6890
- return null;
6891
- try {
6892
- const res = await ipc({ path: "/v1/peers", timeoutMs: 3000 });
6893
- if (res.status !== 200)
6894
- return null;
6895
- return Array.isArray(res.body.peers) ? res.body.peers : [];
6896
- } catch (err) {
6897
- const msg = String(err);
6898
- if (/ENOENT|ECONNREFUSED|ipc_timeout/.test(msg))
6899
- return null;
6900
- return null;
6901
- }
6842
+ function highlightMatch(text, query) {
6843
+ if (!query)
6844
+ return text;
6845
+ const idx = text.toLowerCase().indexOf(query.toLowerCase());
6846
+ if (idx === -1)
6847
+ return text;
6848
+ const before = text.slice(0, idx);
6849
+ const match = text.slice(idx, idx + query.length);
6850
+ const after = text.slice(idx + query.length);
6851
+ return `${before}${yellow(match)}${after}`;
6902
6852
  }
6903
- async function tryListSkillsViaDaemon() {
6904
- if (!existsSync7(DAEMON_PATHS.SOCK_FILE))
6905
- return null;
6906
- try {
6907
- const res = await ipc({ path: "/v1/skills", timeoutMs: 3000 });
6908
- if (res.status !== 200)
6909
- return null;
6910
- return Array.isArray(res.body.skills) ? res.body.skills : [];
6911
- } catch (err) {
6912
- const msg = String(err);
6913
- if (/ENOENT|ECONNREFUSED|ipc_timeout/.test(msg))
6914
- return null;
6915
- return null;
6916
- }
6853
+ async function runMeTasks(flags) {
6854
+ return withRestKey({
6855
+ meshSlug: resolveMeshForMint(flags.mesh),
6856
+ purpose: "workspace-tasks",
6857
+ capabilities: ["read"]
6858
+ }, async ({ secret }) => {
6859
+ const params = new URLSearchParams;
6860
+ if (flags.status)
6861
+ params.set("status", flags.status);
6862
+ const path = "/api/v1/me/tasks" + (params.toString() ? `?${params.toString()}` : "");
6863
+ const ws = await request({
6864
+ path,
6865
+ token: secret
6866
+ });
6867
+ if (flags.json) {
6868
+ console.log(JSON.stringify(ws, null, 2));
6869
+ return EXIT.SUCCESS;
6870
+ }
6871
+ render.section(`${clay("tasks")} — ${dim(`${ws.totals.open} open · ${ws.totals.claimed} in-flight · ${ws.totals.completed} done`)}`);
6872
+ if (ws.tasks.length === 0) {
6873
+ process.stdout.write(dim(` no tasks in window
6874
+ `));
6875
+ return EXIT.SUCCESS;
6876
+ }
6877
+ const slugWidth = Math.max(...ws.tasks.map((t) => t.meshSlug.length), 6);
6878
+ for (const t of ws.tasks) {
6879
+ const slug = dim(t.meshSlug.padEnd(slugWidth));
6880
+ const status = t.status === "open" ? yellow("open ") : t.status === "claimed" ? cyan("working ") : green("done ");
6881
+ const prio = t.priority === "urgent" ? yellow("!") : t.priority === "low" ? dim("·") : " ";
6882
+ const claimer = t.claimedByName ? dim(` ← ${t.claimedByName}`) : "";
6883
+ process.stdout.write(` ${slug} ${prio} ${status} ${t.title}${claimer}
6884
+ `);
6885
+ }
6886
+ return EXIT.SUCCESS;
6887
+ });
6917
6888
  }
6918
- async function tryGetSkillViaDaemon(name) {
6919
- if (!existsSync7(DAEMON_PATHS.SOCK_FILE))
6920
- return null;
6921
- try {
6922
- const res = await ipc({
6923
- path: `/v1/skills/${encodeURIComponent(name)}`,
6924
- timeoutMs: 3000
6889
+ async function runMeState(flags) {
6890
+ return withRestKey({
6891
+ meshSlug: resolveMeshForMint(flags.mesh),
6892
+ purpose: "workspace-state",
6893
+ capabilities: ["read"]
6894
+ }, async ({ secret }) => {
6895
+ const params = new URLSearchParams;
6896
+ if (flags.key)
6897
+ params.set("key", flags.key);
6898
+ const path = "/api/v1/me/state" + (params.toString() ? `?${params.toString()}` : "");
6899
+ const ws = await request({
6900
+ path,
6901
+ token: secret
6925
6902
  });
6926
- if (res.status === 404)
6927
- return null;
6928
- if (res.status !== 200)
6929
- return null;
6930
- return res.body.skill ?? null;
6931
- } catch {
6932
- return null;
6933
- }
6903
+ if (flags.json) {
6904
+ console.log(JSON.stringify(ws, null, 2));
6905
+ return EXIT.SUCCESS;
6906
+ }
6907
+ render.section(`${clay("state")} — ${ws.totals.entries} entr${ws.totals.entries === 1 ? "y" : "ies"} ${dim(`across ${ws.totals.meshes} mesh${ws.totals.meshes === 1 ? "" : "es"}`)}`);
6908
+ if (ws.entries.length === 0) {
6909
+ process.stdout.write(dim(` no state entries
6910
+ `));
6911
+ return EXIT.SUCCESS;
6912
+ }
6913
+ const slugWidth = Math.max(...ws.entries.map((e) => e.meshSlug.length), 6);
6914
+ const keyWidth = Math.max(...ws.entries.map((e) => e.key.length), 8);
6915
+ for (const e of ws.entries) {
6916
+ const slug = dim(e.meshSlug.padEnd(slugWidth));
6917
+ const key = cyan(e.key.padEnd(keyWidth));
6918
+ const valueStr = typeof e.value === "string" ? e.value : JSON.stringify(e.value);
6919
+ const trimmed = valueStr.length > 80 ? valueStr.slice(0, 80) + "…" : valueStr;
6920
+ const ago = dim(formatRelativeTime(e.updatedAt));
6921
+ process.stdout.write(` ${slug} ${key} ${trimmed} ${ago}
6922
+ `);
6923
+ }
6924
+ return EXIT.SUCCESS;
6925
+ });
6934
6926
  }
6935
- async function trySendViaDaemon(args) {
6936
- if (!existsSync7(DAEMON_PATHS.SOCK_FILE))
6937
- return null;
6938
- try {
6939
- const res = await ipc({
6940
- method: "POST",
6941
- path: "/v1/send",
6942
- timeoutMs: 3000,
6943
- body: {
6944
- to: args.to,
6945
- message: args.message,
6946
- priority: args.priority,
6947
- ...args.idempotencyKey ? { client_message_id: args.idempotencyKey } : {}
6948
- }
6927
+ async function runMeMemory(flags) {
6928
+ return withRestKey({
6929
+ meshSlug: resolveMeshForMint(flags.mesh),
6930
+ purpose: "workspace-memory",
6931
+ capabilities: ["read"]
6932
+ }, async ({ secret }) => {
6933
+ const params = new URLSearchParams;
6934
+ if (flags.query)
6935
+ params.set("q", flags.query);
6936
+ const path = "/api/v1/me/memory" + (params.toString() ? `?${params.toString()}` : "");
6937
+ const ws = await request({
6938
+ path,
6939
+ token: secret
6949
6940
  });
6950
- if (res.status === 202 || res.status === 200) {
6951
- return {
6952
- ok: true,
6953
- messageId: res.body.broker_message_id ?? res.body.client_message_id ?? "",
6954
- duplicate: res.body.duplicate,
6955
- status: res.body.status
6956
- };
6941
+ if (flags.json) {
6942
+ console.log(JSON.stringify(ws, null, 2));
6943
+ return EXIT.SUCCESS;
6957
6944
  }
6958
- return { ok: false, error: res.body.error ?? `daemon http ${res.status}` };
6959
- } catch (err) {
6960
- const msg = String(err);
6961
- if (/ENOENT|ECONNREFUSED|ipc_timeout/.test(msg))
6962
- return null;
6963
- return { ok: false, error: msg };
6964
- }
6945
+ const headerLabel = flags.query ? `recall "${flags.query}"` : "recall — last 30 days";
6946
+ render.section(`${clay(headerLabel)} ${dim(`${ws.totals.entries} match${ws.totals.entries === 1 ? "" : "es"}`)}`);
6947
+ if (ws.memories.length === 0) {
6948
+ process.stdout.write(dim(` no memories
6949
+ `));
6950
+ return EXIT.SUCCESS;
6951
+ }
6952
+ const slugWidth = Math.max(...ws.memories.map((m) => m.meshSlug.length), 6);
6953
+ for (const m of ws.memories) {
6954
+ const slug = dim(m.meshSlug.padEnd(slugWidth));
6955
+ const ago = dim(formatRelativeTime(m.rememberedAt));
6956
+ const tags = m.tags.length > 0 ? " " + dim("[" + m.tags.join(", ") + "]") : "";
6957
+ const content = m.content.length > 240 ? m.content.slice(0, 240) + "…" : m.content;
6958
+ process.stdout.write(` ${slug} ${ago}${tags}
6959
+ ${content}
6960
+ `);
6961
+ }
6962
+ return EXIT.SUCCESS;
6963
+ });
6965
6964
  }
6966
- var init_daemon_route = __esm(() => {
6967
- init_client4();
6968
- init_paths2();
6965
+ function formatRelativeTime(iso) {
6966
+ const then = new Date(iso).getTime();
6967
+ const now = Date.now();
6968
+ const sec = Math.max(0, Math.floor((now - then) / 1000));
6969
+ if (sec < 60)
6970
+ return `${sec}s ago`;
6971
+ if (sec < 3600)
6972
+ return `${Math.floor(sec / 60)}m ago`;
6973
+ if (sec < 86400)
6974
+ return `${Math.floor(sec / 3600)}h ago`;
6975
+ if (sec < 86400 * 30)
6976
+ return `${Math.floor(sec / 86400)}d ago`;
6977
+ if (sec < 86400 * 365)
6978
+ return `${Math.floor(sec / (86400 * 30))}mo ago`;
6979
+ return `${Math.floor(sec / (86400 * 365))}y ago`;
6980
+ }
6981
+ var init_me = __esm(() => {
6982
+ init_with_rest_key();
6983
+ init_client();
6984
+ init_facade();
6985
+ init_render();
6986
+ init_styles();
6987
+ init_exit_codes();
6969
6988
  });
6970
6989
 
6971
- // src/commands/peers.ts
6972
- var exports_peers = {};
6973
- __export(exports_peers, {
6974
- runPeers: () => runPeers
6990
+ // src/commands/kick.ts
6991
+ var exports_kick = {};
6992
+ __export(exports_kick, {
6993
+ runKick: () => runKick,
6994
+ runDisconnect: () => runDisconnect
6975
6995
  });
6976
- function projectFields(record, fields) {
6977
- const out = {};
6978
- for (const f of fields) {
6979
- const sourceKey = FIELD_ALIAS[f] ?? f;
6980
- out[f] = record[sourceKey];
6996
+ function parseStaleMs(input) {
6997
+ const m = input.match(/^(\d+)(s|m|h)$/);
6998
+ if (!m)
6999
+ return null;
7000
+ const val = parseInt(m[1], 10);
7001
+ const unit = m[2];
7002
+ if (unit === "s")
7003
+ return val * 1000;
7004
+ if (unit === "m")
7005
+ return val * 60000;
7006
+ if (unit === "h")
7007
+ return val * 3600000;
7008
+ return null;
7009
+ }
7010
+ function buildPayload(kind, target, opts) {
7011
+ if (opts.all)
7012
+ return { type: kind, all: true };
7013
+ if (opts.stale) {
7014
+ const ms = parseStaleMs(opts.stale);
7015
+ if (!ms)
7016
+ return { error: `Invalid stale duration: "${opts.stale}". Use e.g. 30m, 1h, 300s.` };
7017
+ return { type: kind, stale: ms };
6981
7018
  }
6982
- return out;
7019
+ if (target)
7020
+ return { type: kind, target };
7021
+ return { error: `Usage: claudemesh ${kind} <peer> | --stale 30m | --all` };
6983
7022
  }
6984
- async function listPeersForMesh(slug) {
7023
+ async function runDisconnect(target, opts = {}) {
6985
7024
  const config = readConfig();
6986
- const joined = config.meshes.find((m) => m.slug === slug);
6987
- const selfMemberPubkey = joined?.pubkey ?? null;
6988
- try {
6989
- const { tryListPeersViaDaemon: tryListPeersViaDaemon2 } = await Promise.resolve().then(() => (init_daemon_route(), exports_daemon_route));
6990
- const dr = await tryListPeersViaDaemon2();
6991
- if (dr !== null) {
6992
- return dr.map((p) => annotateSelf(p, selfMemberPubkey, null));
6993
- }
6994
- } catch {}
6995
- const bridged = await tryBridge(slug, "peers");
6996
- if (bridged && bridged.ok) {
6997
- const peers = bridged.result;
6998
- return peers.map((p) => annotateSelf(p, selfMemberPubkey, null));
7025
+ const meshSlug = opts.mesh ?? config.meshes[0]?.slug;
7026
+ if (!meshSlug) {
7027
+ render.err("No mesh joined.");
7028
+ return EXIT.NOT_FOUND;
6999
7029
  }
7000
- let result = [];
7001
- await withMesh({ meshSlug: slug }, async (client) => {
7002
- const all = await client.listPeers();
7003
- const selfSessionPubkey = client.getSessionPubkey();
7004
- result = all.map((p) => annotateSelf(p, selfMemberPubkey, selfSessionPubkey));
7030
+ const built = buildPayload("disconnect", target, opts);
7031
+ if ("error" in built) {
7032
+ render.err(String(built.error));
7033
+ return EXIT.INVALID_ARGS;
7034
+ }
7035
+ return await withMesh({ meshSlug }, async (client) => {
7036
+ const result = await client.sendAndWait(built);
7037
+ const peers = result?.affected ?? result?.kicked ?? [];
7038
+ if (peers.length === 0)
7039
+ render.info("No peers matched.");
7040
+ else {
7041
+ render.ok(`Disconnected ${peers.length} peer(s): ${peers.join(", ")}`);
7042
+ render.hint("They will auto-reconnect within seconds. For a session-ending kick, use `claudemesh kick`.");
7043
+ }
7044
+ return EXIT.SUCCESS;
7005
7045
  });
7006
- return result;
7007
- }
7008
- function annotateSelf(peer, selfMemberPubkey, selfSessionPubkey) {
7009
- const isSelf = !!(selfMemberPubkey && peer.memberPubkey && peer.memberPubkey === selfMemberPubkey);
7010
- const isThisSession = !!(isSelf && selfSessionPubkey && peer.pubkey === selfSessionPubkey);
7011
- return { ...peer, isSelf, isThisSession };
7012
7046
  }
7013
- async function runPeers(flags) {
7047
+ async function runKick(target, opts = {}) {
7014
7048
  const config = readConfig();
7015
- const slugs = flags.mesh ? [flags.mesh] : config.meshes.map((m) => m.slug);
7016
- if (slugs.length === 0) {
7017
- render.err("No meshes joined.");
7018
- render.hint("claudemesh <invite-url> # join + launch");
7019
- process.exit(1);
7049
+ const meshSlug = opts.mesh ?? config.meshes[0]?.slug;
7050
+ if (!meshSlug) {
7051
+ render.err("No mesh joined.");
7052
+ return EXIT.NOT_FOUND;
7020
7053
  }
7021
- const fieldList = typeof flags.json === "string" && flags.json.length > 0 ? flags.json.split(",").map((s) => s.trim()).filter(Boolean) : null;
7022
- const wantsJson = flags.json !== undefined && flags.json !== false;
7023
- const allJson = [];
7024
- for (const slug of slugs) {
7025
- try {
7026
- const peers = await listPeersForMesh(slug);
7027
- if (wantsJson) {
7028
- const projected = fieldList ? peers.map((p) => projectFields(p, fieldList)) : peers;
7029
- allJson.push({ mesh: slug, peers: projected });
7030
- continue;
7031
- }
7032
- render.section(`peers on ${slug} (${peers.length})`);
7033
- if (peers.length === 0) {
7034
- render.info(dim(" (no peers connected)"));
7035
- continue;
7036
- }
7037
- for (const p of peers) {
7038
- const groups = p.groups.length ? " [" + p.groups.map((g) => `@${g.name}${g.role ? `:${g.role}` : ""}`).join(", ") + "]" : "";
7039
- const statusDot = p.status === "working" ? yellow("●") : green("●");
7040
- const name = bold(p.displayName);
7041
- const meta = [];
7042
- if (p.peerType)
7043
- meta.push(p.peerType);
7044
- if (p.channel)
7045
- meta.push(p.channel);
7046
- if (p.model)
7047
- meta.push(p.model);
7048
- const metaStr = meta.length ? dim(` (${meta.join(", ")})`) : "";
7049
- const summary = p.summary ? dim(` — ${p.summary}`) : "";
7050
- const pubkeyTag = dim(` · ${p.pubkey.slice(0, 16)}…`);
7051
- const selfTag = p.isThisSession ? dim(" ") + yellow("(this session)") : p.isSelf ? dim(" ") + yellow("(your other session)") : "";
7052
- render.info(`${statusDot} ${name}${selfTag}${groups}${metaStr}${pubkeyTag}${summary}`);
7053
- if (p.cwd)
7054
- render.info(dim(` cwd: ${p.cwd}`));
7055
- }
7056
- } catch (e) {
7057
- render.err(`${slug}: ${e instanceof Error ? e.message : String(e)}`);
7054
+ const built = buildPayload("kick", target, opts);
7055
+ if ("error" in built) {
7056
+ render.err(String(built.error));
7057
+ return EXIT.INVALID_ARGS;
7058
+ }
7059
+ return await withMesh({ meshSlug }, async (client) => {
7060
+ const result = await client.sendAndWait(built);
7061
+ const peers = result?.affected ?? result?.kicked ?? [];
7062
+ if (peers.length === 0)
7063
+ render.info("No peers matched.");
7064
+ else {
7065
+ render.ok(`Kicked ${peers.length} peer(s): ${peers.join(", ")}`);
7066
+ render.hint("Their Claude Code session ended. They can rejoin anytime by running `claudemesh`.");
7058
7067
  }
7059
- }
7060
- if (wantsJson) {
7061
- process.stdout.write(JSON.stringify(slugs.length === 1 ? allJson[0]?.peers : allJson, null, 2) + `
7062
- `);
7063
- }
7068
+ return EXIT.SUCCESS;
7069
+ });
7064
7070
  }
7065
- var FIELD_ALIAS;
7066
- var init_peers = __esm(() => {
7071
+ var init_kick = __esm(() => {
7067
7072
  init_connect();
7068
7073
  init_facade();
7069
- init_client3();
7070
7074
  init_render();
7071
- init_styles();
7072
- FIELD_ALIAS = {
7073
- name: "displayName"
7074
- };
7075
+ init_exit_codes();
7075
7076
  });
7076
7077
 
7077
- // src/commands/send.ts
7078
- var exports_send = {};
7079
- __export(exports_send, {
7080
- runSend: () => runSend
7078
+ // src/commands/ban.ts
7079
+ var exports_ban = {};
7080
+ __export(exports_ban, {
7081
+ runUnban: () => runUnban,
7082
+ runBans: () => runBans,
7083
+ runBan: () => runBan
7081
7084
  });
7082
- async function runSend(flags, to, message) {
7083
- if (!to || !message) {
7084
- render.err("Usage: claudemesh send <to> <message>");
7085
- process.exit(1);
7085
+ async function runBan(target, opts = {}) {
7086
+ if (!target) {
7087
+ render.err("Usage: claudemesh ban <peer-name-or-pubkey>");
7088
+ return EXIT.INVALID_ARGS;
7086
7089
  }
7087
- const priority = flags.priority === "now" ? "now" : flags.priority === "low" ? "low" : "next";
7088
7090
  const config = readConfig();
7089
- const meshSlug = flags.mesh ?? (config.meshes.length === 1 ? config.meshes[0].slug : null);
7090
- if (!flags.self && meshSlug) {
7091
- const joined = config.meshes.find((m) => m.slug === meshSlug);
7092
- if (joined && /^[0-9a-f]{64}$/i.test(to) && to.toLowerCase() === joined.pubkey.toLowerCase()) {
7093
- render.err(`Target "${to.slice(0, 16)}…" is your own member pubkey on mesh "${meshSlug}".`);
7094
- render.hint("Pass --self to message a sibling session of your own member, or pick a different peer's pubkey.");
7095
- process.exit(1);
7091
+ const meshSlug = opts.mesh ?? config.meshes[0]?.slug;
7092
+ if (!meshSlug) {
7093
+ render.err("No mesh joined.");
7094
+ return EXIT.NOT_FOUND;
7095
+ }
7096
+ return await withMesh({ meshSlug }, async (client) => {
7097
+ const result = await client.sendAndWait({ type: "ban", target });
7098
+ if (result?.banned) {
7099
+ render.ok(`Banned ${result.banned} from ${meshSlug}. They cannot reconnect until unbanned.`);
7100
+ render.hint(`Undo: claudemesh unban ${result.banned} --mesh ${meshSlug}`);
7101
+ } else {
7102
+ render.err(result?.message ?? result?.error ?? result?.code ?? "ban failed");
7096
7103
  }
7104
+ return result?.banned ? EXIT.SUCCESS : EXIT.INTERNAL_ERROR;
7105
+ });
7106
+ }
7107
+ async function runUnban(target, opts = {}) {
7108
+ if (!target) {
7109
+ render.err("Usage: claudemesh unban <peer-name-or-pubkey>");
7110
+ return EXIT.INVALID_ARGS;
7097
7111
  }
7098
- {
7099
- const dr = await trySendViaDaemon({ to, message, priority, expectedMesh: meshSlug ?? undefined });
7100
- if (dr !== null) {
7101
- if (dr.ok) {
7102
- if (flags.json)
7103
- console.log(JSON.stringify({ ok: true, messageId: dr.messageId, target: to, via: "daemon", duplicate: !!dr.duplicate }));
7104
- else
7105
- render.ok(`sent to ${to} (daemon)`, dr.messageId ? dim(dr.messageId.slice(0, 8)) : undefined);
7106
- return;
7107
- }
7108
- if (flags.json)
7109
- console.log(JSON.stringify({ ok: false, error: dr.error, via: "daemon" }));
7110
- else
7111
- render.err(`send failed (daemon): ${dr.error}`);
7112
- process.exit(1);
7112
+ const config = readConfig();
7113
+ const meshSlug = opts.mesh ?? config.meshes[0]?.slug;
7114
+ if (!meshSlug) {
7115
+ render.err("No mesh joined.");
7116
+ return EXIT.NOT_FOUND;
7117
+ }
7118
+ return await withMesh({ meshSlug }, async (client) => {
7119
+ const result = await client.sendAndWait({ type: "unban", target });
7120
+ if (result?.unbanned) {
7121
+ render.ok(`Unbanned ${result.unbanned} from ${meshSlug}. They can rejoin.`);
7122
+ } else {
7123
+ render.err(result?.message ?? result?.error ?? result?.code ?? "unban failed");
7113
7124
  }
7125
+ return result?.unbanned ? EXIT.SUCCESS : EXIT.INTERNAL_ERROR;
7126
+ });
7127
+ }
7128
+ async function runBans(opts = {}) {
7129
+ const config = readConfig();
7130
+ const meshSlug = opts.mesh ?? config.meshes[0]?.slug;
7131
+ if (!meshSlug) {
7132
+ render.err("No mesh joined.");
7133
+ return EXIT.NOT_FOUND;
7114
7134
  }
7115
- if (meshSlug) {
7116
- const bridged = await tryBridge(meshSlug, "send", { to, message, priority });
7117
- if (bridged !== null) {
7118
- if (bridged.ok) {
7119
- const r = bridged.result;
7120
- if (flags.json) {
7121
- console.log(JSON.stringify({ ok: true, messageId: r.messageId, target: to }));
7122
- } else {
7123
- render.ok(`sent to ${to}`, r.messageId ? dim(r.messageId.slice(0, 8)) : undefined);
7124
- }
7125
- return;
7126
- }
7127
- if (flags.json) {
7128
- console.log(JSON.stringify({ ok: false, error: bridged.error }));
7129
- } else {
7130
- render.err(`send failed: ${bridged.error}`);
7131
- }
7132
- process.exit(1);
7135
+ return await withMesh({ meshSlug }, async (client) => {
7136
+ const result = await client.sendAndWait({ type: "list_bans" });
7137
+ const bans = result?.bans ?? [];
7138
+ if (opts.json) {
7139
+ process.stdout.write(JSON.stringify(bans, null, 2) + `
7140
+ `);
7141
+ return EXIT.SUCCESS;
7142
+ }
7143
+ if (bans.length === 0) {
7144
+ render.info("No banned members.");
7145
+ return EXIT.SUCCESS;
7146
+ }
7147
+ render.section(`banned members on ${meshSlug}`);
7148
+ for (const b of bans) {
7149
+ render.kv([[b.name, `${b.pubkey.slice(0, 16)} · banned ${new Date(b.revokedAt).toLocaleDateString()}`]]);
7150
+ }
7151
+ return EXIT.SUCCESS;
7152
+ });
7153
+ }
7154
+ var init_ban = __esm(() => {
7155
+ init_connect();
7156
+ init_facade();
7157
+ init_render();
7158
+ init_exit_codes();
7159
+ });
7160
+
7161
+ // src/services/bridge/protocol.ts
7162
+ import { homedir as homedir5 } from "node:os";
7163
+ import { join as join6 } from "node:path";
7164
+ function socketPath(meshSlug) {
7165
+ return join6(homedir5(), ".claudemesh", "sockets", `${meshSlug}.sock`);
7166
+ }
7167
+ function frame(obj) {
7168
+ return JSON.stringify(obj) + `
7169
+ `;
7170
+ }
7171
+
7172
+ class LineParser {
7173
+ buf = "";
7174
+ feed(chunk) {
7175
+ this.buf += typeof chunk === "string" ? chunk : chunk.toString("utf-8");
7176
+ const lines = [];
7177
+ let nl = this.buf.indexOf(`
7178
+ `);
7179
+ while (nl !== -1) {
7180
+ lines.push(this.buf.slice(0, nl));
7181
+ this.buf = this.buf.slice(nl + 1);
7182
+ nl = this.buf.indexOf(`
7183
+ `);
7133
7184
  }
7185
+ return lines;
7134
7186
  }
7135
- await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
7136
- let targetSpec = to;
7137
- if (to.startsWith("#") && !/^#[0-9a-z_-]{20,}$/i.test(to)) {
7138
- const name = to.slice(1);
7139
- const topics = await client.topicList();
7140
- const match = topics.find((t) => t.name === name);
7141
- if (!match) {
7142
- const names = topics.map((t) => "#" + t.name).join(", ");
7143
- render.err(`Topic "${to}" not found.`, `topics: ${names || "(none)"}`);
7144
- process.exit(1);
7145
- }
7146
- targetSpec = "#" + match.id;
7147
- } else if (!to.startsWith("@") && !to.startsWith("#") && to !== "*" && !/^[0-9a-f]{64}$/i.test(to)) {
7148
- const peers = await client.listPeers();
7149
- const match = peers.find((p) => p.displayName.toLowerCase() === to.toLowerCase());
7150
- if (!match) {
7151
- const names = peers.map((p) => p.displayName).join(", ");
7152
- render.err(`Peer "${to}" not found.`, `online: ${names || "(none)"}`);
7153
- process.exit(1);
7187
+ }
7188
+ var init_protocol = () => {};
7189
+
7190
+ // src/services/bridge/client.ts
7191
+ import { createConnection } from "node:net";
7192
+ import { existsSync as existsSync6 } from "node:fs";
7193
+ import { randomUUID as randomUUID2 } from "node:crypto";
7194
+ async function tryBridge(meshSlug, verb, args = {}, timeoutMs = DEFAULT_TIMEOUT_MS) {
7195
+ const path = socketPath(meshSlug);
7196
+ if (!existsSync6(path))
7197
+ return null;
7198
+ return new Promise((resolve) => {
7199
+ const id = randomUUID2();
7200
+ const req = { id, verb, args };
7201
+ const parser = new LineParser;
7202
+ let settled = false;
7203
+ const finish = (value) => {
7204
+ if (settled)
7205
+ return;
7206
+ settled = true;
7207
+ try {
7208
+ socket.destroy();
7209
+ } catch {}
7210
+ clearTimeout(timer);
7211
+ resolve(value);
7212
+ };
7213
+ const socket = createConnection({ path });
7214
+ const timer = setTimeout(() => {
7215
+ finish(null);
7216
+ }, timeoutMs);
7217
+ socket.on("connect", () => {
7218
+ try {
7219
+ socket.write(frame(req));
7220
+ } catch {
7221
+ finish(null);
7154
7222
  }
7155
- targetSpec = match.pubkey;
7156
- }
7157
- const result = await client.send(targetSpec, message, priority);
7158
- if (result.ok) {
7159
- if (flags.json) {
7160
- console.log(JSON.stringify({ ok: true, messageId: result.messageId, target: to }));
7161
- } else {
7162
- render.ok(`sent to ${to}`, result.messageId ? dim(result.messageId.slice(0, 8)) : undefined);
7223
+ });
7224
+ socket.on("data", (chunk) => {
7225
+ const lines = parser.feed(chunk);
7226
+ for (const line of lines) {
7227
+ if (!line.trim())
7228
+ continue;
7229
+ let res;
7230
+ try {
7231
+ res = JSON.parse(line);
7232
+ } catch {
7233
+ continue;
7234
+ }
7235
+ if (res.id !== id)
7236
+ continue;
7237
+ if (res.ok)
7238
+ finish({ ok: true, result: res.result });
7239
+ else
7240
+ finish({ ok: false, error: res.error });
7241
+ return;
7163
7242
  }
7164
- } else {
7165
- if (flags.json) {
7166
- console.log(JSON.stringify({ ok: false, error: result.error ?? "unknown" }));
7243
+ });
7244
+ socket.on("error", (err) => {
7245
+ const code = err.code;
7246
+ if (code === "ECONNREFUSED" || code === "ENOENT" || code === "EPERM") {
7247
+ finish(null);
7167
7248
  } else {
7168
- render.err(`send failed: ${result.error ?? "unknown error"}`);
7249
+ finish(null);
7169
7250
  }
7170
- process.exit(1);
7171
- }
7251
+ });
7252
+ socket.on("close", () => {
7253
+ finish(null);
7254
+ });
7172
7255
  });
7173
7256
  }
7174
- var init_send = __esm(() => {
7175
- init_connect();
7176
- init_facade();
7177
- init_client3();
7178
- init_daemon_route();
7179
- init_render();
7180
- init_styles();
7257
+ var DEFAULT_TIMEOUT_MS = 5000;
7258
+ var init_client3 = __esm(() => {
7259
+ init_protocol();
7181
7260
  });
7182
7261
 
7183
- // src/commands/inbox.ts
7184
- var exports_inbox = {};
7185
- __export(exports_inbox, {
7186
- runInbox: () => runInbox
7187
- });
7188
- function formatMessage(msg) {
7189
- const text = msg.plaintext ?? `[encrypted: ${msg.ciphertext.slice(0, 32)}…]`;
7190
- const from = msg.senderPubkey.slice(0, 8);
7191
- const time = new Date(msg.createdAt).toLocaleTimeString();
7192
- const kindTag = msg.kind === "direct" ? "→ direct" : msg.kind;
7193
- return ` ${bold(from)} ${dim(`[${kindTag}] ${time}`)}
7194
- ${text}`;
7262
+ // src/daemon/local-token.ts
7263
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "node:fs";
7264
+ import { dirname as dirname3 } from "node:path";
7265
+ import { randomBytes as randomBytes4 } from "node:crypto";
7266
+ function readLocalToken() {
7267
+ try {
7268
+ return readFileSync5(DAEMON_PATHS.TOKEN_FILE, "utf8").trim();
7269
+ } catch {
7270
+ return null;
7271
+ }
7195
7272
  }
7196
- async function runInbox(flags) {
7197
- const waitMs = (flags.wait ?? 1) * 1000;
7198
- await withMesh({ meshSlug: flags.mesh ?? null }, async (client, mesh) => {
7199
- await new Promise((resolve) => setTimeout(resolve, waitMs));
7200
- const messages = client.drainPushBuffer();
7201
- if (flags.json) {
7202
- process.stdout.write(JSON.stringify(messages, null, 2) + `
7203
- `);
7204
- return;
7205
- }
7206
- if (messages.length === 0) {
7207
- render.info(dim(`No messages on mesh "${mesh.slug}".`));
7208
- return;
7209
- }
7210
- render.section(`inbox — ${mesh.slug} (${messages.length} message${messages.length === 1 ? "" : "s"})`);
7211
- for (const msg of messages) {
7212
- process.stdout.write(formatMessage(msg) + `
7273
+ function ensureLocalToken() {
7274
+ const existing = readLocalToken();
7275
+ if (existing)
7276
+ return existing;
7277
+ mkdirSync4(dirname3(DAEMON_PATHS.TOKEN_FILE), { recursive: true, mode: 448 });
7278
+ const tok = randomBytes4(32).toString("base64url");
7279
+ writeFileSync6(DAEMON_PATHS.TOKEN_FILE, tok + `
7280
+ `, { mode: 384 });
7281
+ return tok;
7282
+ }
7283
+ var init_local_token = __esm(() => {
7284
+ init_paths2();
7285
+ });
7213
7286
 
7214
- `);
7215
- }
7287
+ // src/daemon/ipc/client.ts
7288
+ import { request as httpRequest } from "node:http";
7289
+ async function ipc(opts) {
7290
+ const useTcp = !!opts.preferTcp;
7291
+ const headers = {
7292
+ accept: "application/json",
7293
+ host: "localhost"
7294
+ };
7295
+ let bodyBuf;
7296
+ if (opts.body !== undefined) {
7297
+ bodyBuf = Buffer.from(JSON.stringify(opts.body), "utf8");
7298
+ headers["content-type"] = "application/json";
7299
+ headers["content-length"] = String(bodyBuf.length);
7300
+ }
7301
+ if (useTcp) {
7302
+ const tok = readLocalToken();
7303
+ if (!tok)
7304
+ throw new IpcError(0, null, "daemon local token not found; is the daemon running?");
7305
+ headers.authorization = `Bearer ${tok}`;
7306
+ }
7307
+ return new Promise((resolve, reject) => {
7308
+ const req = httpRequest(useTcp ? { host: DAEMON_TCP_HOST, port: DAEMON_TCP_DEFAULT_PORT, path: opts.path, method: opts.method ?? "GET", headers } : { socketPath: DAEMON_PATHS.SOCK_FILE, path: opts.path, method: opts.method ?? "GET", headers }, (res) => {
7309
+ const chunks = [];
7310
+ res.on("data", (c) => chunks.push(c));
7311
+ res.on("end", () => {
7312
+ const raw = Buffer.concat(chunks).toString("utf8");
7313
+ let parsed = raw;
7314
+ try {
7315
+ parsed = raw.length > 0 ? JSON.parse(raw) : null;
7316
+ } catch {}
7317
+ resolve({ status: res.statusCode ?? 0, body: parsed });
7318
+ });
7319
+ });
7320
+ req.setTimeout(opts.timeoutMs ?? 5000, () => req.destroy(new Error("ipc_timeout")));
7321
+ req.on("error", (err) => reject(err));
7322
+ if (bodyBuf)
7323
+ req.write(bodyBuf);
7324
+ req.end();
7216
7325
  });
7217
7326
  }
7218
- var init_inbox = __esm(() => {
7219
- init_connect();
7220
- init_render();
7221
- init_styles();
7327
+ var IpcError;
7328
+ var init_client4 = __esm(() => {
7329
+ init_paths2();
7330
+ init_local_token();
7331
+ IpcError = class IpcError extends Error {
7332
+ status;
7333
+ payload;
7334
+ constructor(status, payload, msg) {
7335
+ super(msg);
7336
+ this.status = status;
7337
+ this.payload = payload;
7338
+ }
7339
+ };
7222
7340
  });
7223
7341
 
7224
- // src/commands/state.ts
7225
- var exports_state = {};
7226
- __export(exports_state, {
7227
- runStateSet: () => runStateSet,
7228
- runStateList: () => runStateList,
7229
- runStateGet: () => runStateGet
7342
+ // src/services/bridge/daemon-route.ts
7343
+ var exports_daemon_route = {};
7344
+ __export(exports_daemon_route, {
7345
+ trySetStateViaDaemon: () => trySetStateViaDaemon,
7346
+ trySendViaDaemon: () => trySendViaDaemon,
7347
+ tryRememberViaDaemon: () => tryRememberViaDaemon,
7348
+ tryRecallViaDaemon: () => tryRecallViaDaemon,
7349
+ tryListStateViaDaemon: () => tryListStateViaDaemon,
7350
+ tryListSkillsViaDaemon: () => tryListSkillsViaDaemon,
7351
+ tryListPeersViaDaemon: () => tryListPeersViaDaemon,
7352
+ tryGetStateViaDaemon: () => tryGetStateViaDaemon,
7353
+ tryGetSkillViaDaemon: () => tryGetSkillViaDaemon,
7354
+ tryForgetViaDaemon: () => tryForgetViaDaemon
7230
7355
  });
7231
- async function runStateGet(flags, key) {
7232
- await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
7233
- const entry = await client.getState(key);
7234
- if (!entry) {
7235
- render.info(dim("(not set)"));
7236
- return;
7237
- }
7238
- if (flags.json) {
7239
- console.log(JSON.stringify(entry, null, 2));
7240
- return;
7241
- }
7242
- const val = typeof entry.value === "string" ? entry.value : JSON.stringify(entry.value);
7243
- render.info(val);
7244
- render.info(dim(` set by ${entry.updatedBy} at ${new Date(entry.updatedAt).toLocaleString()}`));
7245
- });
7356
+ import { existsSync as existsSync7 } from "node:fs";
7357
+ function meshQuery(mesh) {
7358
+ return mesh ? `?mesh=${encodeURIComponent(mesh)}` : "";
7246
7359
  }
7247
- async function runStateSet(flags, key, value) {
7248
- let parsed;
7360
+ async function tryListPeersViaDaemon(mesh) {
7361
+ if (!existsSync7(DAEMON_PATHS.SOCK_FILE))
7362
+ return null;
7249
7363
  try {
7250
- parsed = JSON.parse(value);
7251
- } catch {
7252
- parsed = value;
7364
+ const res = await ipc({ path: `/v1/peers${meshQuery(mesh)}`, timeoutMs: 3000 });
7365
+ if (res.status !== 200)
7366
+ return null;
7367
+ return Array.isArray(res.body.peers) ? res.body.peers : [];
7368
+ } catch (err) {
7369
+ const msg = String(err);
7370
+ if (/ENOENT|ECONNREFUSED|ipc_timeout/.test(msg))
7371
+ return null;
7372
+ return null;
7253
7373
  }
7254
- await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
7255
- await client.setState(key, parsed);
7256
- render.ok(`${bold(key)} = ${JSON.stringify(parsed)}`);
7257
- });
7258
7374
  }
7259
- async function runStateList(flags) {
7260
- await withMesh({ meshSlug: flags.mesh ?? null }, async (client, mesh) => {
7261
- const entries = await client.listState();
7262
- if (flags.json) {
7263
- console.log(JSON.stringify(entries, null, 2));
7264
- return;
7265
- }
7266
- if (entries.length === 0) {
7267
- render.info(dim(`No state on mesh "${mesh.slug}".`));
7268
- return;
7269
- }
7270
- render.section(`state (${entries.length})`);
7271
- for (const e of entries) {
7272
- const val = typeof e.value === "string" ? e.value : JSON.stringify(e.value);
7273
- process.stdout.write(` ${bold(e.key)}: ${val}
7274
- `);
7275
- process.stdout.write(` ${dim(e.updatedBy + " · " + new Date(e.updatedAt).toLocaleString())}
7276
- `);
7277
- }
7278
- });
7375
+ async function tryListSkillsViaDaemon(mesh) {
7376
+ if (!existsSync7(DAEMON_PATHS.SOCK_FILE))
7377
+ return null;
7378
+ try {
7379
+ const res = await ipc({ path: `/v1/skills${meshQuery(mesh)}`, timeoutMs: 3000 });
7380
+ if (res.status !== 200)
7381
+ return null;
7382
+ return Array.isArray(res.body.skills) ? res.body.skills : [];
7383
+ } catch (err) {
7384
+ const msg = String(err);
7385
+ if (/ENOENT|ECONNREFUSED|ipc_timeout/.test(msg))
7386
+ return null;
7387
+ return null;
7388
+ }
7279
7389
  }
7280
- var init_state = __esm(() => {
7281
- init_connect();
7282
- init_render();
7283
- init_styles();
7284
- });
7285
-
7286
- // src/services/api/with-rest-key.ts
7287
- async function withRestKey(opts, fn) {
7288
- return withMesh({ meshSlug: opts.meshSlug ?? null }, async (client, mesh) => {
7289
- const result = await client.apiKeyCreate({
7290
- label: `cli-${opts.purpose ?? "rest"}-${process.pid}`,
7291
- capabilities: opts.capabilities ?? ["read"],
7292
- topicScopes: opts.topicScopes ?? undefined,
7293
- expiresAt: new Date(Date.now() + 5 * 60 * 1000).toISOString()
7390
+ async function tryGetSkillViaDaemon(name, mesh) {
7391
+ if (!existsSync7(DAEMON_PATHS.SOCK_FILE))
7392
+ return null;
7393
+ try {
7394
+ const res = await ipc({
7395
+ path: `/v1/skills/${encodeURIComponent(name)}${meshQuery(mesh)}`,
7396
+ timeoutMs: 3000
7294
7397
  });
7295
- if (!result || !result.secret) {
7296
- throw new Error("apikey mint failed — broker did not return a secret");
7297
- }
7298
- try {
7299
- return await fn({
7300
- secret: result.secret,
7301
- meshId: mesh.meshId,
7302
- meshSlug: mesh.slug,
7303
- client,
7304
- mesh
7305
- });
7306
- } finally {
7307
- try {
7308
- await client.apiKeyRevoke(result.id);
7309
- } catch {}
7310
- }
7311
- });
7398
+ if (res.status === 404)
7399
+ return null;
7400
+ if (res.status !== 200)
7401
+ return null;
7402
+ return res.body.skill ?? null;
7403
+ } catch {
7404
+ return null;
7405
+ }
7406
+ }
7407
+ async function tryGetStateViaDaemon(key, mesh) {
7408
+ if (!existsSync7(DAEMON_PATHS.SOCK_FILE))
7409
+ return null;
7410
+ try {
7411
+ const path = `/v1/state?key=${encodeURIComponent(key)}${mesh ? `&mesh=${encodeURIComponent(mesh)}` : ""}`;
7412
+ const res = await ipc({ path, timeoutMs: 3000 });
7413
+ if (res.status === 404)
7414
+ return;
7415
+ if (res.status !== 200)
7416
+ return null;
7417
+ return res.body.state ?? undefined;
7418
+ } catch (err) {
7419
+ const msg = String(err);
7420
+ if (/ENOENT|ECONNREFUSED|ipc_timeout/.test(msg))
7421
+ return null;
7422
+ return null;
7423
+ }
7312
7424
  }
7313
- var init_with_rest_key = __esm(() => {
7314
- init_connect();
7315
- });
7316
-
7317
- // src/commands/me.ts
7318
- var exports_me = {};
7319
- __export(exports_me, {
7320
- runMeTopics: () => runMeTopics,
7321
- runMeTasks: () => runMeTasks,
7322
- runMeState: () => runMeState,
7323
- runMeSearch: () => runMeSearch,
7324
- runMeNotifications: () => runMeNotifications,
7325
- runMeMemory: () => runMeMemory,
7326
- runMeActivity: () => runMeActivity,
7327
- runMe: () => runMe
7328
- });
7329
- function resolveMeshForMint(explicit) {
7330
- if (explicit)
7331
- return explicit;
7332
- const cfg = readConfig();
7333
- return cfg.meshes[0]?.slug ?? null;
7425
+ async function tryListStateViaDaemon(mesh) {
7426
+ if (!existsSync7(DAEMON_PATHS.SOCK_FILE))
7427
+ return null;
7428
+ try {
7429
+ const res = await ipc({ path: `/v1/state${meshQuery(mesh)}`, timeoutMs: 3000 });
7430
+ if (res.status !== 200)
7431
+ return null;
7432
+ return Array.isArray(res.body.entries) ? res.body.entries : [];
7433
+ } catch (err) {
7434
+ const msg = String(err);
7435
+ if (/ENOENT|ECONNREFUSED|ipc_timeout/.test(msg))
7436
+ return null;
7437
+ return null;
7438
+ }
7334
7439
  }
7335
- async function runMe(flags) {
7336
- return withRestKey({
7337
- meshSlug: resolveMeshForMint(flags.mesh),
7338
- purpose: "workspace-overview",
7339
- capabilities: ["read"]
7340
- }, async ({ secret }) => {
7341
- const ws = await request({
7342
- path: "/api/v1/me/workspace",
7343
- token: secret
7440
+ async function trySetStateViaDaemon(key, value, mesh) {
7441
+ if (!existsSync7(DAEMON_PATHS.SOCK_FILE))
7442
+ return false;
7443
+ try {
7444
+ const res = await ipc({
7445
+ method: "POST",
7446
+ path: "/v1/state",
7447
+ timeoutMs: 3000,
7448
+ body: { key, value, ...mesh ? { mesh } : {} }
7344
7449
  });
7345
- if (flags.json) {
7346
- console.log(JSON.stringify(ws, null, 2));
7347
- return EXIT.SUCCESS;
7348
- }
7349
- render.section(`${clay("workspace")} — ${bold(ws.userId.slice(0, 8))} ${dim(`· ${ws.totals.meshes} mesh${ws.totals.meshes === 1 ? "" : "es"}`)}`);
7350
- const totalsLine = [
7351
- `${green(String(ws.totals.online))}/${ws.totals.peers} online`,
7352
- `${ws.totals.topics} topic${ws.totals.topics === 1 ? "" : "s"}`,
7353
- ws.totals.unreadMentions > 0 ? yellow(`${ws.totals.unreadMentions} unread @you`) : dim("0 unread @you")
7354
- ].join(dim(" · "));
7355
- process.stdout.write(" " + totalsLine + `
7356
-
7357
- `);
7358
- if (ws.meshes.length === 0) {
7359
- process.stdout.write(dim(" no meshes joined — run `claudemesh new` or accept an invite\n"));
7360
- return EXIT.SUCCESS;
7361
- }
7362
- const slugWidth = Math.max(...ws.meshes.map((m) => m.slug.length), 8);
7363
- for (const m of ws.meshes) {
7364
- const slug = cyan(m.slug.padEnd(slugWidth));
7365
- const peers = `${m.online}/${m.peers}`;
7366
- const role = dim(m.myRole);
7367
- const unread = m.unreadMentions > 0 ? " " + yellow(`${m.unreadMentions} @you`) : "";
7368
- process.stdout.write(` ${slug} ${peers.padStart(5)} online ${dim(String(m.topics).padStart(2) + " topics")} ${role}${unread}
7369
- `);
7370
- }
7371
- return EXIT.SUCCESS;
7372
- });
7450
+ return res.status === 200 && res.body.ok === true;
7451
+ } catch {
7452
+ return false;
7453
+ }
7373
7454
  }
7374
- async function runMeTopics(flags) {
7375
- return withRestKey({
7376
- meshSlug: resolveMeshForMint(flags.mesh),
7377
- purpose: "workspace-topics",
7378
- capabilities: ["read"]
7379
- }, async ({ secret }) => {
7380
- const ws = await request({
7381
- path: "/api/v1/me/topics",
7382
- token: secret
7455
+ async function tryRememberViaDaemon(content, tags, mesh) {
7456
+ if (!existsSync7(DAEMON_PATHS.SOCK_FILE))
7457
+ return null;
7458
+ try {
7459
+ const res = await ipc({
7460
+ method: "POST",
7461
+ path: "/v1/memory",
7462
+ timeoutMs: 5000,
7463
+ body: { content, ...tags?.length ? { tags } : {}, ...mesh ? { mesh } : {} }
7383
7464
  });
7384
- const visible = flags.unread ? ws.topics.filter((t) => t.unread > 0) : ws.topics;
7385
- if (flags.json) {
7386
- console.log(JSON.stringify({ topics: visible, totals: ws.totals }, null, 2));
7387
- return EXIT.SUCCESS;
7388
- }
7389
- render.section(`${clay("topics")} — ${ws.totals.topics} across all meshes ${dim(ws.totals.unread > 0 ? `· ${ws.totals.unread} unread` : "· all read")}`);
7390
- if (visible.length === 0) {
7391
- process.stdout.write(dim(flags.unread ? ` no unread topics
7392
- ` : " no topics — run `claudemesh topic create #general`\n"));
7393
- return EXIT.SUCCESS;
7394
- }
7395
- const slugWidth = Math.max(...visible.map((t) => t.meshSlug.length), 6);
7396
- const nameWidth = Math.max(...visible.map((t) => t.name.length), 8);
7397
- for (const t of visible) {
7398
- const slug = dim(t.meshSlug.padEnd(slugWidth));
7399
- const name = cyan(t.name.padEnd(nameWidth));
7400
- const unread = t.unread > 0 ? yellow(`${t.unread} unread`.padStart(10)) : dim("·".padStart(10));
7401
- const last = t.lastMessageAt ? dim(formatRelativeTime(t.lastMessageAt)) : dim("never");
7402
- process.stdout.write(` ${slug} ${name} ${unread} ${last}
7403
- `);
7404
- }
7405
- return EXIT.SUCCESS;
7406
- });
7465
+ if (res.status !== 200 || !res.body.id)
7466
+ return null;
7467
+ return { id: res.body.id, mesh: res.body.mesh };
7468
+ } catch {
7469
+ return null;
7470
+ }
7407
7471
  }
7408
- async function runMeNotifications(flags) {
7409
- return withRestKey({
7410
- meshSlug: resolveMeshForMint(flags.mesh),
7411
- purpose: "workspace-notifications",
7412
- capabilities: ["read"]
7413
- }, async ({ secret }) => {
7414
- const params = new URLSearchParams;
7415
- if (flags.all)
7416
- params.set("include", "all");
7417
- if (flags.since)
7418
- params.set("since", flags.since);
7419
- const path = "/api/v1/me/notifications" + (params.toString() ? `?${params.toString()}` : "");
7420
- const ws = await request({
7421
- path,
7422
- token: secret
7423
- });
7424
- if (flags.json) {
7425
- console.log(JSON.stringify(ws, null, 2));
7426
- return EXIT.SUCCESS;
7427
- }
7428
- const headerLabel = flags.all ? "@-mentions (all)" : "@-mentions (unread)";
7429
- render.section(`${clay(headerLabel)} — ${ws.totals.total} ${dim(ws.totals.unread > 0 ? `· ${ws.totals.unread} unread` : "· nothing pending")}`);
7430
- if (ws.notifications.length === 0) {
7431
- process.stdout.write(dim(flags.all ? ` no @-mentions in window
7432
- ` : ` inbox zero — nothing waiting
7433
- `));
7434
- return EXIT.SUCCESS;
7435
- }
7436
- const slugWidth = Math.max(...ws.notifications.map((n) => n.meshSlug.length), 6);
7437
- for (const n of ws.notifications) {
7438
- const slug = dim(n.meshSlug.padEnd(slugWidth));
7439
- const topic = cyan(`#${n.topicName}`);
7440
- const sender = n.senderName ? `from ${n.senderName}` : "from ?";
7441
- const ago = formatRelativeTime(n.createdAt);
7442
- const dot = n.read ? dim("·") : yellow("●");
7443
- const snippet = n.snippet ?? (n.ciphertext ? dim("[encrypted]") : dim("[empty]"));
7444
- process.stdout.write(` ${dot} ${slug} ${topic} ${dim(sender)} ${dim(ago)}
7445
- ` + ` ${snippet.length > 200 ? snippet.slice(0, 200) + "…" : snippet}
7446
- `);
7447
- }
7448
- return EXIT.SUCCESS;
7449
- });
7472
+ async function tryRecallViaDaemon(query, mesh) {
7473
+ if (!existsSync7(DAEMON_PATHS.SOCK_FILE))
7474
+ return null;
7475
+ try {
7476
+ const path = `/v1/memory?q=${encodeURIComponent(query)}${mesh ? `&mesh=${encodeURIComponent(mesh)}` : ""}`;
7477
+ const res = await ipc({ path, timeoutMs: 5000 });
7478
+ if (res.status !== 200)
7479
+ return null;
7480
+ return Array.isArray(res.body.matches) ? res.body.matches : [];
7481
+ } catch (err) {
7482
+ const msg = String(err);
7483
+ if (/ENOENT|ECONNREFUSED|ipc_timeout/.test(msg))
7484
+ return null;
7485
+ return null;
7486
+ }
7450
7487
  }
7451
- async function runMeActivity(flags) {
7452
- return withRestKey({
7453
- meshSlug: resolveMeshForMint(flags.mesh),
7454
- purpose: "workspace-activity",
7455
- capabilities: ["read"]
7456
- }, async ({ secret }) => {
7457
- const params = new URLSearchParams;
7458
- if (flags.since)
7459
- params.set("since", flags.since);
7460
- const path = "/api/v1/me/activity" + (params.toString() ? `?${params.toString()}` : "");
7461
- const ws = await request({
7462
- path,
7463
- token: secret
7488
+ async function tryForgetViaDaemon(id, mesh) {
7489
+ if (!existsSync7(DAEMON_PATHS.SOCK_FILE))
7490
+ return false;
7491
+ try {
7492
+ const path = `/v1/memory/${encodeURIComponent(id)}${meshQuery(mesh)}`;
7493
+ const res = await ipc({ method: "DELETE", path, timeoutMs: 3000 });
7494
+ return res.status === 200 && res.body.ok === true;
7495
+ } catch {
7496
+ return false;
7497
+ }
7498
+ }
7499
+ async function trySendViaDaemon(args) {
7500
+ if (!existsSync7(DAEMON_PATHS.SOCK_FILE))
7501
+ return null;
7502
+ try {
7503
+ const res = await ipc({
7504
+ method: "POST",
7505
+ path: "/v1/send",
7506
+ timeoutMs: 3000,
7507
+ body: {
7508
+ to: args.to,
7509
+ message: args.message,
7510
+ priority: args.priority,
7511
+ ...args.idempotencyKey ? { client_message_id: args.idempotencyKey } : {},
7512
+ ...args.expectedMesh ? { mesh: args.expectedMesh } : {}
7513
+ }
7464
7514
  });
7465
- if (flags.json) {
7466
- console.log(JSON.stringify(ws, null, 2));
7467
- return EXIT.SUCCESS;
7468
- }
7469
- render.section(`${clay("activity")} — ${ws.totals.events} ${dim(flags.since ? `since ${flags.since}` : "in the last 24h")}`);
7470
- if (ws.activity.length === 0) {
7471
- process.stdout.write(dim(` quiet — no activity in window
7472
- `));
7473
- return EXIT.SUCCESS;
7515
+ if (res.status === 202 || res.status === 200) {
7516
+ return {
7517
+ ok: true,
7518
+ messageId: res.body.broker_message_id ?? res.body.client_message_id ?? "",
7519
+ duplicate: res.body.duplicate,
7520
+ status: res.body.status
7521
+ };
7474
7522
  }
7475
- const slugWidth = Math.max(...ws.activity.map((a) => a.meshSlug.length), 6);
7476
- for (const a of ws.activity) {
7477
- const slug = dim(a.meshSlug.padEnd(slugWidth));
7478
- const topic = cyan(`#${a.topicName}`);
7479
- const sender = a.senderName ?? "?";
7480
- const ago = formatRelativeTime(a.createdAt);
7481
- const snippet = a.snippet ?? (a.ciphertext ? dim("[encrypted]") : dim("[empty]"));
7482
- process.stdout.write(` ${slug} ${topic} ${dim(sender + " ·")} ${dim(ago)}
7483
- ` + ` ${snippet.length > 200 ? snippet.slice(0, 200) + "…" : snippet}
7484
- `);
7523
+ return { ok: false, error: res.body.error ?? `daemon http ${res.status}` };
7524
+ } catch (err) {
7525
+ const msg = String(err);
7526
+ if (/ENOENT|ECONNREFUSED|ipc_timeout/.test(msg))
7527
+ return null;
7528
+ return { ok: false, error: msg };
7529
+ }
7530
+ }
7531
+ var init_daemon_route = __esm(() => {
7532
+ init_client4();
7533
+ init_paths2();
7534
+ });
7535
+
7536
+ // src/commands/peers.ts
7537
+ var exports_peers = {};
7538
+ __export(exports_peers, {
7539
+ runPeers: () => runPeers
7540
+ });
7541
+ function projectFields(record, fields) {
7542
+ const out = {};
7543
+ for (const f of fields) {
7544
+ const sourceKey = FIELD_ALIAS[f] ?? f;
7545
+ out[f] = record[sourceKey];
7546
+ }
7547
+ return out;
7548
+ }
7549
+ async function listPeersForMesh(slug) {
7550
+ const config = readConfig();
7551
+ const joined = config.meshes.find((m) => m.slug === slug);
7552
+ const selfMemberPubkey = joined?.pubkey ?? null;
7553
+ try {
7554
+ const { tryListPeersViaDaemon: tryListPeersViaDaemon2 } = await Promise.resolve().then(() => (init_daemon_route(), exports_daemon_route));
7555
+ const dr = await tryListPeersViaDaemon2();
7556
+ if (dr !== null) {
7557
+ return dr.map((p) => annotateSelf(p, selfMemberPubkey, null));
7485
7558
  }
7486
- return EXIT.SUCCESS;
7559
+ } catch {}
7560
+ const bridged = await tryBridge(slug, "peers");
7561
+ if (bridged && bridged.ok) {
7562
+ const peers = bridged.result;
7563
+ return peers.map((p) => annotateSelf(p, selfMemberPubkey, null));
7564
+ }
7565
+ let result = [];
7566
+ await withMesh({ meshSlug: slug }, async (client) => {
7567
+ const all = await client.listPeers();
7568
+ const selfSessionPubkey = client.getSessionPubkey();
7569
+ result = all.map((p) => annotateSelf(p, selfMemberPubkey, selfSessionPubkey));
7487
7570
  });
7571
+ return result;
7488
7572
  }
7489
- async function runMeSearch(flags) {
7490
- if (!flags.query || flags.query.length < 2) {
7491
- process.stderr.write(`Usage: claudemesh me search <query> (min 2 chars)
7573
+ function annotateSelf(peer, selfMemberPubkey, selfSessionPubkey) {
7574
+ const isSelf = !!(selfMemberPubkey && peer.memberPubkey && peer.memberPubkey === selfMemberPubkey);
7575
+ const isThisSession = !!(isSelf && selfSessionPubkey && peer.pubkey === selfSessionPubkey);
7576
+ return { ...peer, isSelf, isThisSession };
7577
+ }
7578
+ async function runPeers(flags) {
7579
+ const config = readConfig();
7580
+ const slugs = flags.mesh ? [flags.mesh] : config.meshes.map((m) => m.slug);
7581
+ if (slugs.length === 0) {
7582
+ render.err("No meshes joined.");
7583
+ render.hint("claudemesh <invite-url> # join + launch");
7584
+ process.exit(1);
7585
+ }
7586
+ const fieldList = typeof flags.json === "string" && flags.json.length > 0 ? flags.json.split(",").map((s) => s.trim()).filter(Boolean) : null;
7587
+ const wantsJson = flags.json !== undefined && flags.json !== false;
7588
+ const allJson = [];
7589
+ for (const slug of slugs) {
7590
+ try {
7591
+ const peers = await listPeersForMesh(slug);
7592
+ if (wantsJson) {
7593
+ const projected = fieldList ? peers.map((p) => projectFields(p, fieldList)) : peers;
7594
+ allJson.push({ mesh: slug, peers: projected });
7595
+ continue;
7596
+ }
7597
+ render.section(`peers on ${slug} (${peers.length})`);
7598
+ if (peers.length === 0) {
7599
+ render.info(dim(" (no peers connected)"));
7600
+ continue;
7601
+ }
7602
+ for (const p of peers) {
7603
+ const groups = p.groups.length ? " [" + p.groups.map((g) => `@${g.name}${g.role ? `:${g.role}` : ""}`).join(", ") + "]" : "";
7604
+ const statusDot = p.status === "working" ? yellow("●") : green("●");
7605
+ const name = bold(p.displayName);
7606
+ const meta = [];
7607
+ if (p.peerType)
7608
+ meta.push(p.peerType);
7609
+ if (p.channel)
7610
+ meta.push(p.channel);
7611
+ if (p.model)
7612
+ meta.push(p.model);
7613
+ const metaStr = meta.length ? dim(` (${meta.join(", ")})`) : "";
7614
+ const summary = p.summary ? dim(` — ${p.summary}`) : "";
7615
+ const pubkeyTag = dim(` · ${p.pubkey.slice(0, 16)}…`);
7616
+ const selfTag = p.isThisSession ? dim(" ") + yellow("(this session)") : p.isSelf ? dim(" ") + yellow("(your other session)") : "";
7617
+ render.info(`${statusDot} ${name}${selfTag}${groups}${metaStr}${pubkeyTag}${summary}`);
7618
+ if (p.cwd)
7619
+ render.info(dim(` cwd: ${p.cwd}`));
7620
+ }
7621
+ } catch (e) {
7622
+ render.err(`${slug}: ${e instanceof Error ? e.message : String(e)}`);
7623
+ }
7624
+ }
7625
+ if (wantsJson) {
7626
+ process.stdout.write(JSON.stringify(slugs.length === 1 ? allJson[0]?.peers : allJson, null, 2) + `
7492
7627
  `);
7493
- return EXIT.INVALID_ARGS;
7494
7628
  }
7495
- return withRestKey({
7496
- meshSlug: resolveMeshForMint(flags.mesh),
7497
- purpose: "workspace-search",
7498
- capabilities: ["read"]
7499
- }, async ({ secret }) => {
7500
- const params = new URLSearchParams({ q: flags.query });
7501
- const ws = await request({
7502
- path: `/api/v1/me/search?${params.toString()}`,
7503
- token: secret
7504
- });
7505
- if (flags.json) {
7506
- console.log(JSON.stringify(ws, null, 2));
7507
- return EXIT.SUCCESS;
7629
+ }
7630
+ var FIELD_ALIAS;
7631
+ var init_peers = __esm(() => {
7632
+ init_connect();
7633
+ init_facade();
7634
+ init_client3();
7635
+ init_render();
7636
+ init_styles();
7637
+ FIELD_ALIAS = {
7638
+ name: "displayName"
7639
+ };
7640
+ });
7641
+
7642
+ // src/commands/send.ts
7643
+ var exports_send = {};
7644
+ __export(exports_send, {
7645
+ runSend: () => runSend
7646
+ });
7647
+ async function runSend(flags, to, message) {
7648
+ if (!to || !message) {
7649
+ render.err("Usage: claudemesh send <to> <message>");
7650
+ process.exit(1);
7651
+ }
7652
+ const priority = flags.priority === "now" ? "now" : flags.priority === "low" ? "low" : "next";
7653
+ const config = readConfig();
7654
+ const meshSlug = flags.mesh ?? (config.meshes.length === 1 ? config.meshes[0].slug : null);
7655
+ if (!flags.self && meshSlug) {
7656
+ const joined = config.meshes.find((m) => m.slug === meshSlug);
7657
+ if (joined && /^[0-9a-f]{64}$/i.test(to) && to.toLowerCase() === joined.pubkey.toLowerCase()) {
7658
+ render.err(`Target "${to.slice(0, 16)}…" is your own member pubkey on mesh "${meshSlug}".`);
7659
+ render.hint("Pass --self to message a sibling session of your own member, or pick a different peer's pubkey.");
7660
+ process.exit(1);
7508
7661
  }
7509
- render.section(`${clay("search")} — "${flags.query}" ${dim(`${ws.totals.topics} topic${ws.totals.topics === 1 ? "" : "s"}, ` + `${ws.totals.messages} message${ws.totals.messages === 1 ? "" : "s"}`)}`);
7510
- if (ws.topics.length === 0 && ws.messages.length === 0) {
7511
- process.stdout.write(dim(` no matches
7512
- `));
7513
- return EXIT.SUCCESS;
7662
+ }
7663
+ {
7664
+ const dr = await trySendViaDaemon({ to, message, priority, expectedMesh: meshSlug ?? undefined });
7665
+ if (dr !== null) {
7666
+ if (dr.ok) {
7667
+ if (flags.json)
7668
+ console.log(JSON.stringify({ ok: true, messageId: dr.messageId, target: to, via: "daemon", duplicate: !!dr.duplicate }));
7669
+ else
7670
+ render.ok(`sent to ${to} (daemon)`, dr.messageId ? dim(dr.messageId.slice(0, 8)) : undefined);
7671
+ return;
7672
+ }
7673
+ if (flags.json)
7674
+ console.log(JSON.stringify({ ok: false, error: dr.error, via: "daemon" }));
7675
+ else
7676
+ render.err(`send failed (daemon): ${dr.error}`);
7677
+ process.exit(1);
7514
7678
  }
7515
- if (ws.topics.length > 0) {
7516
- process.stdout.write(dim(`
7517
- topics
7518
- `));
7519
- const slugWidth = Math.max(...ws.topics.map((t) => t.meshSlug.length), 6);
7520
- for (const t of ws.topics) {
7521
- const slug = dim(t.meshSlug.padEnd(slugWidth));
7522
- const name = cyan(`#${t.name}`);
7523
- const desc = t.description ? dim(` — ${t.description}`) : "";
7524
- process.stdout.write(` ${slug} ${name}${desc}
7525
- `);
7679
+ }
7680
+ if (meshSlug) {
7681
+ const bridged = await tryBridge(meshSlug, "send", { to, message, priority });
7682
+ if (bridged !== null) {
7683
+ if (bridged.ok) {
7684
+ const r = bridged.result;
7685
+ if (flags.json) {
7686
+ console.log(JSON.stringify({ ok: true, messageId: r.messageId, target: to }));
7687
+ } else {
7688
+ render.ok(`sent to ${to}`, r.messageId ? dim(r.messageId.slice(0, 8)) : undefined);
7689
+ }
7690
+ return;
7526
7691
  }
7692
+ if (flags.json) {
7693
+ console.log(JSON.stringify({ ok: false, error: bridged.error }));
7694
+ } else {
7695
+ render.err(`send failed: ${bridged.error}`);
7696
+ }
7697
+ process.exit(1);
7527
7698
  }
7528
- if (ws.messages.length > 0) {
7529
- process.stdout.write(dim(`
7530
- messages
7531
- `));
7532
- const slugWidth = Math.max(...ws.messages.map((m) => m.meshSlug.length), 6);
7533
- for (const m of ws.messages) {
7534
- const slug = dim(m.meshSlug.padEnd(slugWidth));
7535
- const topic = cyan(`#${m.topicName}`);
7536
- const sender = m.senderName;
7537
- const ago = formatRelativeTime(m.createdAt);
7538
- const snippet = m.snippet ?? (m.bodyVersion === 2 ? dim("[encrypted — open the topic to decrypt]") : dim("[empty]"));
7539
- const highlighted = m.snippet ? highlightMatch(snippet, flags.query) : snippet;
7540
- process.stdout.write(` ${slug} ${topic} ${dim(sender + " ·")} ${dim(ago)}
7541
- ` + ` ${highlighted}
7542
- `);
7699
+ }
7700
+ await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
7701
+ let targetSpec = to;
7702
+ if (to.startsWith("#") && !/^#[0-9a-z_-]{20,}$/i.test(to)) {
7703
+ const name = to.slice(1);
7704
+ const topics = await client.topicList();
7705
+ const match = topics.find((t) => t.name === name);
7706
+ if (!match) {
7707
+ const names = topics.map((t) => "#" + t.name).join(", ");
7708
+ render.err(`Topic "${to}" not found.`, `topics: ${names || "(none)"}`);
7709
+ process.exit(1);
7543
7710
  }
7711
+ targetSpec = "#" + match.id;
7712
+ } else if (!to.startsWith("@") && !to.startsWith("#") && to !== "*" && !/^[0-9a-f]{64}$/i.test(to)) {
7713
+ const peers = await client.listPeers();
7714
+ const match = peers.find((p) => p.displayName.toLowerCase() === to.toLowerCase());
7715
+ if (!match) {
7716
+ const names = peers.map((p) => p.displayName).join(", ");
7717
+ render.err(`Peer "${to}" not found.`, `online: ${names || "(none)"}`);
7718
+ process.exit(1);
7719
+ }
7720
+ targetSpec = match.pubkey;
7721
+ }
7722
+ const result = await client.send(targetSpec, message, priority);
7723
+ if (result.ok) {
7724
+ if (flags.json) {
7725
+ console.log(JSON.stringify({ ok: true, messageId: result.messageId, target: to }));
7726
+ } else {
7727
+ render.ok(`sent to ${to}`, result.messageId ? dim(result.messageId.slice(0, 8)) : undefined);
7728
+ }
7729
+ } else {
7730
+ if (flags.json) {
7731
+ console.log(JSON.stringify({ ok: false, error: result.error ?? "unknown" }));
7732
+ } else {
7733
+ render.err(`send failed: ${result.error ?? "unknown error"}`);
7734
+ }
7735
+ process.exit(1);
7544
7736
  }
7545
- return EXIT.SUCCESS;
7546
7737
  });
7547
7738
  }
7548
- function highlightMatch(text, query) {
7549
- if (!query)
7550
- return text;
7551
- const idx = text.toLowerCase().indexOf(query.toLowerCase());
7552
- if (idx === -1)
7553
- return text;
7554
- const before = text.slice(0, idx);
7555
- const match = text.slice(idx, idx + query.length);
7556
- const after = text.slice(idx + query.length);
7557
- return `${before}${yellow(match)}${after}`;
7739
+ var init_send = __esm(() => {
7740
+ init_connect();
7741
+ init_facade();
7742
+ init_client3();
7743
+ init_daemon_route();
7744
+ init_render();
7745
+ init_styles();
7746
+ });
7747
+
7748
+ // src/commands/inbox.ts
7749
+ var exports_inbox = {};
7750
+ __export(exports_inbox, {
7751
+ runInbox: () => runInbox
7752
+ });
7753
+ function formatMessage(msg) {
7754
+ const text = msg.plaintext ?? `[encrypted: ${msg.ciphertext.slice(0, 32)}…]`;
7755
+ const from = msg.senderPubkey.slice(0, 8);
7756
+ const time = new Date(msg.createdAt).toLocaleTimeString();
7757
+ const kindTag = msg.kind === "direct" ? "→ direct" : msg.kind;
7758
+ return ` ${bold(from)} ${dim(`[${kindTag}] ${time}`)}
7759
+ ${text}`;
7558
7760
  }
7559
- async function runMeTasks(flags) {
7560
- return withRestKey({
7561
- meshSlug: resolveMeshForMint(flags.mesh),
7562
- purpose: "workspace-tasks",
7563
- capabilities: ["read"]
7564
- }, async ({ secret }) => {
7565
- const params = new URLSearchParams;
7566
- if (flags.status)
7567
- params.set("status", flags.status);
7568
- const path = "/api/v1/me/tasks" + (params.toString() ? `?${params.toString()}` : "");
7569
- const ws = await request({
7570
- path,
7571
- token: secret
7572
- });
7761
+ async function runInbox(flags) {
7762
+ const waitMs = (flags.wait ?? 1) * 1000;
7763
+ await withMesh({ meshSlug: flags.mesh ?? null }, async (client, mesh) => {
7764
+ await new Promise((resolve) => setTimeout(resolve, waitMs));
7765
+ const messages = client.drainPushBuffer();
7573
7766
  if (flags.json) {
7574
- console.log(JSON.stringify(ws, null, 2));
7575
- return EXIT.SUCCESS;
7767
+ process.stdout.write(JSON.stringify(messages, null, 2) + `
7768
+ `);
7769
+ return;
7576
7770
  }
7577
- render.section(`${clay("tasks")} — ${dim(`${ws.totals.open} open · ${ws.totals.claimed} in-flight · ${ws.totals.completed} done`)}`);
7578
- if (ws.tasks.length === 0) {
7579
- process.stdout.write(dim(` no tasks in window
7580
- `));
7581
- return EXIT.SUCCESS;
7771
+ if (messages.length === 0) {
7772
+ render.info(dim(`No messages on mesh "${mesh.slug}".`));
7773
+ return;
7582
7774
  }
7583
- const slugWidth = Math.max(...ws.tasks.map((t) => t.meshSlug.length), 6);
7584
- for (const t of ws.tasks) {
7585
- const slug = dim(t.meshSlug.padEnd(slugWidth));
7586
- const status = t.status === "open" ? yellow("open ") : t.status === "claimed" ? cyan("working ") : green("done ");
7587
- const prio = t.priority === "urgent" ? yellow("!") : t.priority === "low" ? dim("·") : " ";
7588
- const claimer = t.claimedByName ? dim(` ← ${t.claimedByName}`) : "";
7589
- process.stdout.write(` ${slug} ${prio} ${status} ${t.title}${claimer}
7775
+ render.section(`inbox ${mesh.slug} (${messages.length} message${messages.length === 1 ? "" : "s"})`);
7776
+ for (const msg of messages) {
7777
+ process.stdout.write(formatMessage(msg) + `
7778
+
7590
7779
  `);
7591
7780
  }
7592
- return EXIT.SUCCESS;
7593
7781
  });
7594
7782
  }
7595
- async function runMeState(flags) {
7596
- return withRestKey({
7597
- meshSlug: resolveMeshForMint(flags.mesh),
7598
- purpose: "workspace-state",
7599
- capabilities: ["read"]
7600
- }, async ({ secret }) => {
7601
- const params = new URLSearchParams;
7602
- if (flags.key)
7603
- params.set("key", flags.key);
7604
- const path = "/api/v1/me/state" + (params.toString() ? `?${params.toString()}` : "");
7605
- const ws = await request({
7606
- path,
7607
- token: secret
7608
- });
7783
+ var init_inbox = __esm(() => {
7784
+ init_connect();
7785
+ init_render();
7786
+ init_styles();
7787
+ });
7788
+
7789
+ // src/commands/state.ts
7790
+ var exports_state = {};
7791
+ __export(exports_state, {
7792
+ runStateSet: () => runStateSet,
7793
+ runStateList: () => runStateList,
7794
+ runStateGet: () => runStateGet
7795
+ });
7796
+ async function runStateGet(flags, key) {
7797
+ const daemonEntry = await tryGetStateViaDaemon(key, flags.mesh);
7798
+ if (daemonEntry !== null) {
7799
+ if (!daemonEntry) {
7800
+ render.info(dim("(not set)"));
7801
+ return;
7802
+ }
7609
7803
  if (flags.json) {
7610
- console.log(JSON.stringify(ws, null, 2));
7611
- return EXIT.SUCCESS;
7804
+ console.log(JSON.stringify(daemonEntry, null, 2));
7805
+ return;
7612
7806
  }
7613
- render.section(`${clay("state")} ${ws.totals.entries} entr${ws.totals.entries === 1 ? "y" : "ies"} ${dim(`across ${ws.totals.meshes} mesh${ws.totals.meshes === 1 ? "" : "es"}`)}`);
7614
- if (ws.entries.length === 0) {
7615
- process.stdout.write(dim(` no state entries
7616
- `));
7617
- return EXIT.SUCCESS;
7807
+ const val = typeof daemonEntry.value === "string" ? daemonEntry.value : JSON.stringify(daemonEntry.value);
7808
+ render.info(val);
7809
+ render.info(dim(` set by ${daemonEntry.updatedBy} at ${new Date(daemonEntry.updatedAt).toLocaleString()}`));
7810
+ return;
7811
+ }
7812
+ await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
7813
+ const entry = await client.getState(key);
7814
+ if (!entry) {
7815
+ render.info(dim("(not set)"));
7816
+ return;
7618
7817
  }
7619
- const slugWidth = Math.max(...ws.entries.map((e) => e.meshSlug.length), 6);
7620
- const keyWidth = Math.max(...ws.entries.map((e) => e.key.length), 8);
7621
- for (const e of ws.entries) {
7622
- const slug = dim(e.meshSlug.padEnd(slugWidth));
7623
- const key = cyan(e.key.padEnd(keyWidth));
7624
- const valueStr = typeof e.value === "string" ? e.value : JSON.stringify(e.value);
7625
- const trimmed = valueStr.length > 80 ? valueStr.slice(0, 80) + "…" : valueStr;
7626
- const ago = dim(formatRelativeTime(e.updatedAt));
7627
- process.stdout.write(` ${slug} ${key} ${trimmed} ${ago}
7628
- `);
7818
+ if (flags.json) {
7819
+ console.log(JSON.stringify(entry, null, 2));
7820
+ return;
7629
7821
  }
7630
- return EXIT.SUCCESS;
7822
+ const val = typeof entry.value === "string" ? entry.value : JSON.stringify(entry.value);
7823
+ render.info(val);
7824
+ render.info(dim(` set by ${entry.updatedBy} at ${new Date(entry.updatedAt).toLocaleString()}`));
7631
7825
  });
7632
7826
  }
7633
- async function runMeMemory(flags) {
7634
- return withRestKey({
7635
- meshSlug: resolveMeshForMint(flags.mesh),
7636
- purpose: "workspace-memory",
7637
- capabilities: ["read"]
7638
- }, async ({ secret }) => {
7639
- const params = new URLSearchParams;
7640
- if (flags.query)
7641
- params.set("q", flags.query);
7642
- const path = "/api/v1/me/memory" + (params.toString() ? `?${params.toString()}` : "");
7643
- const ws = await request({
7644
- path,
7645
- token: secret
7646
- });
7827
+ async function runStateSet(flags, key, value) {
7828
+ let parsed;
7829
+ try {
7830
+ parsed = JSON.parse(value);
7831
+ } catch {
7832
+ parsed = value;
7833
+ }
7834
+ const daemonOk = await trySetStateViaDaemon(key, parsed, flags.mesh);
7835
+ if (daemonOk) {
7836
+ render.ok(`${bold(key)} = ${JSON.stringify(parsed)}`);
7837
+ return;
7838
+ }
7839
+ await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
7840
+ await client.setState(key, parsed);
7841
+ render.ok(`${bold(key)} = ${JSON.stringify(parsed)}`);
7842
+ });
7843
+ }
7844
+ async function runStateList(flags) {
7845
+ const daemonRows = await tryListStateViaDaemon(flags.mesh);
7846
+ if (daemonRows !== null) {
7647
7847
  if (flags.json) {
7648
- console.log(JSON.stringify(ws, null, 2));
7649
- return EXIT.SUCCESS;
7848
+ console.log(JSON.stringify(daemonRows, null, 2));
7849
+ return;
7650
7850
  }
7651
- const headerLabel = flags.query ? `recall — "${flags.query}"` : "recall — last 30 days";
7652
- render.section(`${clay(headerLabel)} ${dim(`${ws.totals.entries} match${ws.totals.entries === 1 ? "" : "es"}`)}`);
7653
- if (ws.memories.length === 0) {
7654
- process.stdout.write(dim(` no memories
7655
- `));
7656
- return EXIT.SUCCESS;
7851
+ if (daemonRows.length === 0) {
7852
+ render.info(dim("(no state)"));
7853
+ return;
7657
7854
  }
7658
- const slugWidth = Math.max(...ws.memories.map((m) => m.meshSlug.length), 6);
7659
- for (const m of ws.memories) {
7660
- const slug = dim(m.meshSlug.padEnd(slugWidth));
7661
- const ago = dim(formatRelativeTime(m.rememberedAt));
7662
- const tags = m.tags.length > 0 ? " " + dim("[" + m.tags.join(", ") + "]") : "";
7663
- const content = m.content.length > 240 ? m.content.slice(0, 240) + "" : m.content;
7664
- process.stdout.write(` ${slug} ${ago}${tags}
7665
- ${content}
7855
+ render.section(`state (${daemonRows.length})`);
7856
+ for (const e of daemonRows) {
7857
+ const val = typeof e.value === "string" ? e.value : JSON.stringify(e.value);
7858
+ process.stdout.write(` ${bold(e.key)}: ${val}
7859
+ `);
7860
+ process.stdout.write(` ${dim(e.updatedBy + " · " + new Date(e.updatedAt).toLocaleString())}
7666
7861
  `);
7667
7862
  }
7668
- return EXIT.SUCCESS;
7669
- });
7670
- }
7671
- function formatRelativeTime(iso) {
7672
- const then = new Date(iso).getTime();
7673
- const now = Date.now();
7674
- const sec = Math.max(0, Math.floor((now - then) / 1000));
7675
- if (sec < 60)
7676
- return `${sec}s ago`;
7677
- if (sec < 3600)
7678
- return `${Math.floor(sec / 60)}m ago`;
7679
- if (sec < 86400)
7680
- return `${Math.floor(sec / 3600)}h ago`;
7681
- if (sec < 86400 * 30)
7682
- return `${Math.floor(sec / 86400)}d ago`;
7683
- if (sec < 86400 * 365)
7684
- return `${Math.floor(sec / (86400 * 30))}mo ago`;
7685
- return `${Math.floor(sec / (86400 * 365))}y ago`;
7686
- }
7687
- var init_me = __esm(() => {
7688
- init_with_rest_key();
7689
- init_client();
7690
- init_facade();
7691
- init_render();
7692
- init_styles();
7693
- init_exit_codes();
7694
- });
7695
-
7696
- // src/commands/info.ts
7697
- var exports_info = {};
7698
- __export(exports_info, {
7699
- runInfo: () => runInfo
7700
- });
7701
- async function runInfo(flags) {
7702
- const config = readConfig();
7863
+ return;
7864
+ }
7703
7865
  await withMesh({ meshSlug: flags.mesh ?? null }, async (client, mesh) => {
7704
- const [brokerInfo, peers, state] = await Promise.all([
7705
- client.meshInfo(),
7706
- client.listPeers(),
7707
- client.listState()
7708
- ]);
7709
- const output = {
7710
- slug: mesh.slug,
7711
- meshId: mesh.meshId,
7712
- memberId: mesh.memberId,
7713
- brokerUrl: mesh.brokerUrl,
7714
- displayName: config.displayName ?? null,
7715
- peerCount: peers.length,
7716
- stateCount: state.length,
7717
- ...brokerInfo ?? {}
7718
- };
7866
+ const entries = await client.listState();
7719
7867
  if (flags.json) {
7720
- process.stdout.write(JSON.stringify(output, null, 2) + `
7721
- `);
7868
+ console.log(JSON.stringify(entries, null, 2));
7722
7869
  return;
7723
7870
  }
7724
- render.section(`${mesh.slug} · ${mesh.brokerUrl}`);
7725
- render.kv([
7726
- ["mesh", mesh.meshId],
7727
- ["member", mesh.memberId],
7728
- ["peers", `${peers.length} connected`],
7729
- ["state", `${state.length} keys`]
7730
- ]);
7731
- if (brokerInfo && typeof brokerInfo === "object") {
7732
- const extras = [];
7733
- for (const [k, v] of Object.entries(brokerInfo)) {
7734
- if (["slug", "meshId", "brokerUrl"].includes(k))
7735
- continue;
7736
- extras.push([k, JSON.stringify(v)]);
7737
- }
7738
- if (extras.length)
7739
- render.kv(extras);
7871
+ if (entries.length === 0) {
7872
+ render.info(dim(`No state on mesh "${mesh.slug}".`));
7873
+ return;
7874
+ }
7875
+ render.section(`state (${entries.length})`);
7876
+ for (const e of entries) {
7877
+ const val = typeof e.value === "string" ? e.value : JSON.stringify(e.value);
7878
+ process.stdout.write(` ${bold(e.key)}: ${val}
7879
+ `);
7880
+ process.stdout.write(` ${dim(e.updatedBy + " · " + new Date(e.updatedAt).toLocaleString())}
7881
+ `);
7740
7882
  }
7741
7883
  });
7742
7884
  }
7743
- var init_info2 = __esm(() => {
7885
+ var init_state = __esm(() => {
7744
7886
  init_connect();
7745
- init_facade();
7887
+ init_daemon_route();
7746
7888
  init_render();
7889
+ init_styles();
7747
7890
  });
7748
7891
 
7749
7892
  // src/commands/remember.ts
@@ -7757,6 +7900,15 @@ async function remember(content, opts = {}) {
7757
7900
  return EXIT.INVALID_ARGS;
7758
7901
  }
7759
7902
  const tags = opts.tags?.split(",").map((t) => t.trim()).filter(Boolean);
7903
+ const daemonRes = await tryRememberViaDaemon(content, tags, opts.mesh);
7904
+ if (daemonRes) {
7905
+ if (opts.json) {
7906
+ console.log(JSON.stringify({ id: daemonRes.id, content, tags, mesh: daemonRes.mesh }));
7907
+ return EXIT.SUCCESS;
7908
+ }
7909
+ render.ok("remembered", dim(daemonRes.id.slice(0, 8)));
7910
+ return EXIT.SUCCESS;
7911
+ }
7760
7912
  return await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
7761
7913
  const id = await client.remember(content, tags);
7762
7914
  if (opts.json) {
@@ -7773,6 +7925,7 @@ async function remember(content, opts = {}) {
7773
7925
  }
7774
7926
  var init_remember = __esm(() => {
7775
7927
  init_connect();
7928
+ init_daemon_route();
7776
7929
  init_render();
7777
7930
  init_styles();
7778
7931
  init_exit_codes();
@@ -7788,6 +7941,29 @@ async function recall(query, opts = {}) {
7788
7941
  render.err("Usage: claudemesh recall <query>");
7789
7942
  return EXIT.INVALID_ARGS;
7790
7943
  }
7944
+ const daemonMatches = await tryRecallViaDaemon(query, opts.mesh);
7945
+ if (daemonMatches !== null) {
7946
+ if (opts.json) {
7947
+ console.log(JSON.stringify(daemonMatches, null, 2));
7948
+ return EXIT.SUCCESS;
7949
+ }
7950
+ if (daemonMatches.length === 0) {
7951
+ render.info(dim("no memories found."));
7952
+ return EXIT.SUCCESS;
7953
+ }
7954
+ render.section(`memories (${daemonMatches.length})`);
7955
+ for (const m of daemonMatches) {
7956
+ const tags = m.tags.length ? dim(` [${m.tags.map((t) => clay(t)).join(dim(", "))}]`) : "";
7957
+ process.stdout.write(` ${bold(m.id.slice(0, 8))}${tags}
7958
+ `);
7959
+ process.stdout.write(` ${m.content}
7960
+ `);
7961
+ process.stdout.write(` ${dim(m.rememberedBy + " · " + new Date(m.rememberedAt).toLocaleString())}
7962
+
7963
+ `);
7964
+ }
7965
+ return EXIT.SUCCESS;
7966
+ }
7791
7967
  return await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
7792
7968
  const memories = await client.recall(query);
7793
7969
  if (opts.json) {
@@ -7814,6 +7990,7 @@ async function recall(query, opts = {}) {
7814
7990
  }
7815
7991
  var init_recall = __esm(() => {
7816
7992
  init_connect();
7993
+ init_daemon_route();
7817
7994
  init_render();
7818
7995
  init_styles();
7819
7996
  init_exit_codes();
@@ -8026,6 +8203,14 @@ async function runForget(id, opts) {
8026
8203
  render.err("Usage: claudemesh forget <memory-id>");
8027
8204
  return EXIT.INVALID_ARGS;
8028
8205
  }
8206
+ if (await tryForgetViaDaemon(id, opts.mesh)) {
8207
+ if (opts.json) {
8208
+ console.log(JSON.stringify({ id, forgotten: true }));
8209
+ return EXIT.SUCCESS;
8210
+ }
8211
+ render.ok(`forgot ${dim(id.slice(0, 8))}`);
8212
+ return EXIT.SUCCESS;
8213
+ }
8029
8214
  await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
8030
8215
  await client.forget(id);
8031
8216
  });
@@ -8203,6 +8388,7 @@ var init_broker_actions = __esm(() => {
8203
8388
  init_connect();
8204
8389
  init_facade();
8205
8390
  init_client3();
8391
+ init_daemon_route();
8206
8392
  init_render();
8207
8393
  init_styles();
8208
8394
  init_exit_codes();
@@ -8908,10 +9094,9 @@ function startIpcServer(opts) {
8908
9094
  outboxDb: opts.outboxDb,
8909
9095
  inboxDb: opts.inboxDb,
8910
9096
  bus: opts.bus,
8911
- broker: opts.broker,
8912
- onPendingInserted: opts.onPendingInserted,
8913
- meshSecretKey: opts.meshSecretKey,
8914
- meshSlug: opts.meshSlug
9097
+ brokers: opts.brokers,
9098
+ meshConfigs: opts.meshConfigs,
9099
+ onPendingInserted: opts.onPendingInserted
8915
9100
  });
8916
9101
  if (existsSync9(DAEMON_PATHS.SOCK_FILE)) {
8917
9102
  try {
@@ -9003,7 +9188,7 @@ function makeHandler(opts) {
9003
9188
  respond(res, 200, {
9004
9189
  daemon_version: VERSION,
9005
9190
  ipc_api: "v1",
9006
- ipc_features: ["version", "health", "send", "inbox", "events", "peers", "profile", "skills"],
9191
+ ipc_features: ["version", "health", "send", "inbox", "events", "peers", "profile", "skills", "state", "memory"],
9007
9192
  schema_version: 1
9008
9193
  });
9009
9194
  return;
@@ -9021,34 +9206,209 @@ function makeHandler(opts) {
9021
9206
  return;
9022
9207
  }
9023
9208
  if (req.method === "GET" && url.pathname === "/v1/peers") {
9024
- if (!opts.broker) {
9209
+ if (!opts.brokers || opts.brokers.size === 0) {
9210
+ respond(res, 503, { error: "broker not initialised" });
9211
+ return;
9212
+ }
9213
+ const filterMesh = url.searchParams.get("mesh") ?? undefined;
9214
+ try {
9215
+ const all = [];
9216
+ for (const [slug, b] of opts.brokers.entries()) {
9217
+ if (filterMesh && filterMesh !== slug)
9218
+ continue;
9219
+ try {
9220
+ const peers = await b.listPeers();
9221
+ for (const p of peers)
9222
+ all.push({ ...p, mesh: slug });
9223
+ } catch (e) {
9224
+ opts.log("warn", "ipc_peers_broker_failed", { mesh: slug, err: String(e) });
9225
+ }
9226
+ }
9227
+ respond(res, 200, { peers: all });
9228
+ } catch (e) {
9229
+ respond(res, 502, { error: "broker_unreachable", detail: String(e) });
9230
+ }
9231
+ return;
9232
+ }
9233
+ if (req.method === "GET" && url.pathname === "/v1/state") {
9234
+ if (!opts.brokers || opts.brokers.size === 0) {
9235
+ respond(res, 503, { error: "broker not initialised" });
9236
+ return;
9237
+ }
9238
+ const filterMesh = url.searchParams.get("mesh") ?? undefined;
9239
+ const key = url.searchParams.get("key");
9240
+ try {
9241
+ if (key) {
9242
+ for (const [slug, b] of opts.brokers.entries()) {
9243
+ if (filterMesh && filterMesh !== slug)
9244
+ continue;
9245
+ const row = await b.getState(key).catch(() => null);
9246
+ if (row) {
9247
+ respond(res, 200, { state: { ...row, mesh: slug } });
9248
+ return;
9249
+ }
9250
+ }
9251
+ respond(res, 404, { error: "state_not_found", key });
9252
+ return;
9253
+ }
9254
+ const all = [];
9255
+ for (const [slug, b] of opts.brokers.entries()) {
9256
+ if (filterMesh && filterMesh !== slug)
9257
+ continue;
9258
+ const rows = await b.listState().catch(() => []);
9259
+ for (const r of rows)
9260
+ all.push({ ...r, mesh: slug });
9261
+ }
9262
+ respond(res, 200, { entries: all });
9263
+ } catch (e) {
9264
+ respond(res, 502, { error: "broker_unreachable", detail: String(e) });
9265
+ }
9266
+ return;
9267
+ }
9268
+ if (req.method === "POST" && url.pathname === "/v1/state") {
9269
+ if (!opts.brokers || opts.brokers.size === 0) {
9270
+ respond(res, 503, { error: "broker not initialised" });
9271
+ return;
9272
+ }
9273
+ try {
9274
+ const body = await readJsonBody(req, 256 * 1024);
9275
+ if (!body || typeof body.key !== "string") {
9276
+ respond(res, 400, { error: "missing 'key' (string)" });
9277
+ return;
9278
+ }
9279
+ const requested = (typeof body.mesh === "string" ? body.mesh : null) || null;
9280
+ let chosen = requested;
9281
+ if (!chosen && opts.brokers.size === 1)
9282
+ chosen = opts.brokers.keys().next().value;
9283
+ if (!chosen) {
9284
+ respond(res, 400, { error: "mesh_required", attached: [...opts.brokers.keys()] });
9285
+ return;
9286
+ }
9287
+ const broker = opts.brokers.get(chosen);
9288
+ if (!broker) {
9289
+ respond(res, 404, { error: "mesh_not_attached", mesh: chosen });
9290
+ return;
9291
+ }
9292
+ broker.setState(body.key, body.value);
9293
+ respond(res, 200, { ok: true, key: body.key, mesh: chosen });
9294
+ } catch (e) {
9295
+ respond(res, 400, { error: String(e) });
9296
+ }
9297
+ return;
9298
+ }
9299
+ if (req.method === "GET" && url.pathname === "/v1/memory") {
9300
+ if (!opts.brokers || opts.brokers.size === 0) {
9025
9301
  respond(res, 503, { error: "broker not initialised" });
9026
9302
  return;
9027
9303
  }
9304
+ const query = url.searchParams.get("q") ?? "";
9305
+ const filterMesh = url.searchParams.get("mesh") ?? undefined;
9028
9306
  try {
9029
- const peers = await opts.broker.listPeers();
9030
- respond(res, 200, { peers });
9307
+ const all = [];
9308
+ for (const [slug, b] of opts.brokers.entries()) {
9309
+ if (filterMesh && filterMesh !== slug)
9310
+ continue;
9311
+ const rows = await b.recall(query).catch(() => []);
9312
+ for (const r of rows)
9313
+ all.push({ ...r, mesh: slug });
9314
+ }
9315
+ respond(res, 200, { matches: all });
9031
9316
  } catch (e) {
9032
9317
  respond(res, 502, { error: "broker_unreachable", detail: String(e) });
9033
9318
  }
9034
9319
  return;
9035
9320
  }
9321
+ if (req.method === "POST" && url.pathname === "/v1/memory") {
9322
+ if (!opts.brokers || opts.brokers.size === 0) {
9323
+ respond(res, 503, { error: "broker not initialised" });
9324
+ return;
9325
+ }
9326
+ try {
9327
+ const body = await readJsonBody(req, 256 * 1024);
9328
+ if (!body || typeof body.content !== "string") {
9329
+ respond(res, 400, { error: "missing 'content' (string)" });
9330
+ return;
9331
+ }
9332
+ const requested = (typeof body.mesh === "string" ? body.mesh : null) || null;
9333
+ let chosen = requested;
9334
+ if (!chosen && opts.brokers.size === 1)
9335
+ chosen = opts.brokers.keys().next().value;
9336
+ if (!chosen) {
9337
+ respond(res, 400, { error: "mesh_required", attached: [...opts.brokers.keys()] });
9338
+ return;
9339
+ }
9340
+ const broker = opts.brokers.get(chosen);
9341
+ if (!broker) {
9342
+ respond(res, 404, { error: "mesh_not_attached", mesh: chosen });
9343
+ return;
9344
+ }
9345
+ const tags = Array.isArray(body.tags) ? body.tags.filter((t) => typeof t === "string") : undefined;
9346
+ const id = await broker.remember(body.content, tags);
9347
+ if (!id) {
9348
+ respond(res, 502, { error: "remember_timeout" });
9349
+ return;
9350
+ }
9351
+ respond(res, 200, { id, mesh: chosen });
9352
+ } catch (e) {
9353
+ respond(res, 400, { error: String(e) });
9354
+ }
9355
+ return;
9356
+ }
9357
+ if (req.method === "DELETE" && url.pathname.startsWith("/v1/memory/")) {
9358
+ if (!opts.brokers || opts.brokers.size === 0) {
9359
+ respond(res, 503, { error: "broker not initialised" });
9360
+ return;
9361
+ }
9362
+ const id = decodeURIComponent(url.pathname.slice("/v1/memory/".length));
9363
+ if (!id) {
9364
+ respond(res, 400, { error: "missing memory id" });
9365
+ return;
9366
+ }
9367
+ const requested = url.searchParams.get("mesh");
9368
+ let chosen = requested;
9369
+ if (!chosen && opts.brokers.size === 1)
9370
+ chosen = opts.brokers.keys().next().value;
9371
+ if (!chosen) {
9372
+ respond(res, 400, { error: "mesh_required", attached: [...opts.brokers.keys()] });
9373
+ return;
9374
+ }
9375
+ const broker = opts.brokers.get(chosen);
9376
+ if (!broker) {
9377
+ respond(res, 404, { error: "mesh_not_attached", mesh: chosen });
9378
+ return;
9379
+ }
9380
+ broker.forget(id);
9381
+ respond(res, 200, { ok: true, id, mesh: chosen });
9382
+ return;
9383
+ }
9036
9384
  if (req.method === "GET" && url.pathname === "/v1/skills") {
9037
- if (!opts.broker) {
9385
+ if (!opts.brokers || opts.brokers.size === 0) {
9038
9386
  respond(res, 503, { error: "broker not initialised" });
9039
9387
  return;
9040
9388
  }
9041
9389
  const query = url.searchParams.get("query") ?? undefined;
9390
+ const filterMesh = url.searchParams.get("mesh") ?? undefined;
9042
9391
  try {
9043
- const skills = await opts.broker.listSkills(query);
9044
- respond(res, 200, { skills });
9392
+ const all = [];
9393
+ for (const [slug, b] of opts.brokers.entries()) {
9394
+ if (filterMesh && filterMesh !== slug)
9395
+ continue;
9396
+ try {
9397
+ const skills = await b.listSkills(query);
9398
+ for (const s of skills)
9399
+ all.push({ ...s, mesh: slug });
9400
+ } catch (e) {
9401
+ opts.log("warn", "ipc_skills_broker_failed", { mesh: slug, err: String(e) });
9402
+ }
9403
+ }
9404
+ respond(res, 200, { skills: all });
9045
9405
  } catch (e) {
9046
9406
  respond(res, 502, { error: "broker_unreachable", detail: String(e) });
9047
9407
  }
9048
9408
  return;
9049
9409
  }
9050
9410
  if (req.method === "GET" && url.pathname.startsWith("/v1/skills/")) {
9051
- if (!opts.broker) {
9411
+ if (!opts.brokers || opts.brokers.size === 0) {
9052
9412
  respond(res, 503, { error: "broker not initialised" });
9053
9413
  return;
9054
9414
  }
@@ -9057,20 +9417,25 @@ function makeHandler(opts) {
9057
9417
  respond(res, 400, { error: "missing skill name" });
9058
9418
  return;
9059
9419
  }
9420
+ const filterMesh = url.searchParams.get("mesh") ?? undefined;
9060
9421
  try {
9061
- const skill = await opts.broker.getSkill(name);
9062
- if (!skill) {
9063
- respond(res, 404, { error: "skill_not_found", name });
9064
- return;
9422
+ for (const [slug, b] of opts.brokers.entries()) {
9423
+ if (filterMesh && filterMesh !== slug)
9424
+ continue;
9425
+ const skill = await b.getSkill(name).catch(() => null);
9426
+ if (skill) {
9427
+ respond(res, 200, { skill: { ...skill, mesh: slug } });
9428
+ return;
9429
+ }
9065
9430
  }
9066
- respond(res, 200, { skill });
9431
+ respond(res, 404, { error: "skill_not_found", name });
9067
9432
  } catch (e) {
9068
9433
  respond(res, 502, { error: "broker_unreachable", detail: String(e) });
9069
9434
  }
9070
9435
  return;
9071
9436
  }
9072
9437
  if (req.method === "POST" && url.pathname === "/v1/profile") {
9073
- if (!opts.broker) {
9438
+ if (!opts.brokers || opts.brokers.size === 0) {
9074
9439
  respond(res, 503, { error: "broker not initialised" });
9075
9440
  return;
9076
9441
  }
@@ -9080,26 +9445,34 @@ function makeHandler(opts) {
9080
9445
  respond(res, 400, { error: "expected JSON object" });
9081
9446
  return;
9082
9447
  }
9448
+ const requested = (typeof body.mesh === "string" ? body.mesh : url.searchParams.get("mesh")) || null;
9449
+ const targets = requested ? [opts.brokers.get(requested)].filter(Boolean) : [...opts.brokers.values()];
9450
+ if (targets.length === 0) {
9451
+ respond(res, 404, { error: "mesh_not_attached", mesh: requested });
9452
+ return;
9453
+ }
9083
9454
  const updates = {};
9084
- if (typeof body.summary === "string")
9085
- opts.broker.setSummary(body.summary);
9086
- if (body.status === "idle" || body.status === "working" || body.status === "dnd")
9087
- opts.broker.setStatus(body.status);
9088
- if (typeof body.visible === "boolean")
9089
- opts.broker.setVisible(body.visible);
9090
- const profile = {};
9091
- if (typeof body.avatar === "string")
9092
- profile.avatar = body.avatar;
9093
- if (typeof body.title === "string")
9094
- profile.title = body.title;
9095
- if (typeof body.bio === "string")
9096
- profile.bio = body.bio;
9097
- if (Array.isArray(body.capabilities))
9098
- profile.capabilities = body.capabilities.filter((c) => typeof c === "string");
9099
- if (Object.keys(profile).length > 0)
9100
- opts.broker.setProfile(profile);
9455
+ for (const b of targets) {
9456
+ if (typeof body.summary === "string")
9457
+ b.setSummary(body.summary);
9458
+ if (body.status === "idle" || body.status === "working" || body.status === "dnd")
9459
+ b.setStatus(body.status);
9460
+ if (typeof body.visible === "boolean")
9461
+ b.setVisible(body.visible);
9462
+ const profile = {};
9463
+ if (typeof body.avatar === "string")
9464
+ profile.avatar = body.avatar;
9465
+ if (typeof body.title === "string")
9466
+ profile.title = body.title;
9467
+ if (typeof body.bio === "string")
9468
+ profile.bio = body.bio;
9469
+ if (Array.isArray(body.capabilities))
9470
+ profile.capabilities = body.capabilities.filter((c) => typeof c === "string");
9471
+ if (Object.keys(profile).length > 0)
9472
+ b.setProfile(profile);
9473
+ }
9101
9474
  Object.assign(updates, body);
9102
- respond(res, 200, { ok: true, applied: Object.keys(updates) });
9475
+ respond(res, 200, { ok: true, applied: Object.keys(updates), meshes: requested ? [requested] : [...opts.brokers.keys()] });
9103
9476
  } catch (e) {
9104
9477
  respond(res, 400, { error: String(e) });
9105
9478
  }
@@ -9217,9 +9590,27 @@ function makeHandler(opts) {
9217
9590
  respond(res, 400, { error: parsed.error });
9218
9591
  return;
9219
9592
  }
9220
- if (opts.broker && opts.meshSecretKey) {
9593
+ if (opts.brokers && opts.brokers.size > 0 && opts.meshConfigs) {
9594
+ let chosenSlug = parsed.req.mesh ?? null;
9595
+ if (!chosenSlug && opts.brokers.size === 1) {
9596
+ chosenSlug = opts.brokers.keys().next().value;
9597
+ }
9598
+ if (!chosenSlug) {
9599
+ respond(res, 400, {
9600
+ error: "mesh_required",
9601
+ detail: `daemon attached to ${opts.brokers.size} meshes; pass 'mesh' in request body`,
9602
+ attached: [...opts.brokers.keys()]
9603
+ });
9604
+ return;
9605
+ }
9606
+ const broker = opts.brokers.get(chosenSlug);
9607
+ const meshCfg = opts.meshConfigs.get(chosenSlug);
9608
+ if (!broker || !meshCfg) {
9609
+ respond(res, 404, { error: "mesh_not_attached", mesh: chosenSlug });
9610
+ return;
9611
+ }
9221
9612
  try {
9222
- const routed = await resolveAndEncrypt(parsed.req, opts.broker, opts.meshSecretKey, opts.meshSlug ?? null);
9613
+ const routed = await resolveAndEncrypt(parsed.req, broker, meshCfg.secretKey, chosenSlug);
9223
9614
  parsed.req.target_spec = routed.target_spec;
9224
9615
  parsed.req.ciphertext = routed.ciphertext;
9225
9616
  parsed.req.nonce = routed.nonce;
@@ -9321,6 +9712,7 @@ function parseSendRequest(body, idempotencyHeader) {
9321
9712
  const headerId = Array.isArray(idempotencyHeader) ? idempotencyHeader[0] : idempotencyHeader;
9322
9713
  const client_message_id = typeof b.client_message_id === "string" && b.client_message_id.trim() ? b.client_message_id.trim() : typeof headerId === "string" && headerId.trim() ? headerId.trim() : undefined;
9323
9714
  const reply_to_id = typeof b.reply_to_id === "string" ? b.reply_to_id : undefined;
9715
+ const mesh = typeof b.mesh === "string" ? b.mesh.trim() : undefined;
9324
9716
  return {
9325
9717
  req: {
9326
9718
  to,
@@ -9330,7 +9722,8 @@ function parseSendRequest(body, idempotencyHeader) {
9330
9722
  reply_to_id,
9331
9723
  client_message_id,
9332
9724
  destination_kind,
9333
- destination_ref
9725
+ destination_ref,
9726
+ mesh
9334
9727
  }
9335
9728
  };
9336
9729
  }
@@ -9405,6 +9798,10 @@ class DaemonBrokerClient {
9405
9798
  peerListResolvers = new Map;
9406
9799
  skillListResolvers = new Map;
9407
9800
  skillDataResolvers = new Map;
9801
+ stateGetResolvers = new Map;
9802
+ stateListResolvers = new Map;
9803
+ memoryStoreResolvers = new Map;
9804
+ memoryRecallResolvers = new Map;
9408
9805
  sessionPubkey = null;
9409
9806
  sessionSecretKey = null;
9410
9807
  opens = [];
@@ -9545,6 +9942,46 @@ class DaemonBrokerClient {
9545
9942
  }
9546
9943
  return;
9547
9944
  }
9945
+ if (msg.type === "state_value" || msg.type === "state_data") {
9946
+ const reqId = String(msg._reqId ?? "");
9947
+ const pending = this.stateGetResolvers.get(reqId);
9948
+ if (pending) {
9949
+ this.stateGetResolvers.delete(reqId);
9950
+ clearTimeout(pending.timer);
9951
+ pending.resolve(msg.state ?? msg.row ?? null);
9952
+ }
9953
+ return;
9954
+ }
9955
+ if (msg.type === "state_list") {
9956
+ const reqId = String(msg._reqId ?? "");
9957
+ const pending = this.stateListResolvers.get(reqId);
9958
+ if (pending) {
9959
+ this.stateListResolvers.delete(reqId);
9960
+ clearTimeout(pending.timer);
9961
+ pending.resolve(Array.isArray(msg.entries) ? msg.entries : []);
9962
+ }
9963
+ return;
9964
+ }
9965
+ if (msg.type === "memory_stored") {
9966
+ const reqId = String(msg._reqId ?? "");
9967
+ const pending = this.memoryStoreResolvers.get(reqId);
9968
+ if (pending) {
9969
+ this.memoryStoreResolvers.delete(reqId);
9970
+ clearTimeout(pending.timer);
9971
+ pending.resolve(typeof msg.memoryId === "string" ? msg.memoryId : null);
9972
+ }
9973
+ return;
9974
+ }
9975
+ if (msg.type === "memory_recall_result") {
9976
+ const reqId = String(msg._reqId ?? "");
9977
+ const pending = this.memoryRecallResolvers.get(reqId);
9978
+ if (pending) {
9979
+ this.memoryRecallResolvers.delete(reqId);
9980
+ clearTimeout(pending.timer);
9981
+ pending.resolve(Array.isArray(msg.matches) ? msg.matches : []);
9982
+ }
9983
+ return;
9984
+ }
9548
9985
  if (msg.type === "push" || msg.type === "inbound") {
9549
9986
  this.opts.onPush?.(msg);
9550
9987
  return;
@@ -9665,6 +10102,96 @@ class DaemonBrokerClient {
9665
10102
  }
9666
10103
  });
9667
10104
  }
10105
+ async getState(key, timeoutMs = 5000) {
10106
+ if (this._status !== "open" || !this.ws)
10107
+ return null;
10108
+ return new Promise((resolve) => {
10109
+ const reqId = `sg-${++this.reqCounter}`;
10110
+ const timer = setTimeout(() => {
10111
+ if (this.stateGetResolvers.delete(reqId))
10112
+ resolve(null);
10113
+ }, timeoutMs);
10114
+ this.stateGetResolvers.set(reqId, { resolve, timer });
10115
+ try {
10116
+ this.ws.send(JSON.stringify({ type: "get_state", key, _reqId: reqId }));
10117
+ } catch {
10118
+ this.stateGetResolvers.delete(reqId);
10119
+ clearTimeout(timer);
10120
+ resolve(null);
10121
+ }
10122
+ });
10123
+ }
10124
+ async listState(timeoutMs = 5000) {
10125
+ if (this._status !== "open" || !this.ws)
10126
+ return [];
10127
+ return new Promise((resolve) => {
10128
+ const reqId = `sl-${++this.reqCounter}`;
10129
+ const timer = setTimeout(() => {
10130
+ if (this.stateListResolvers.delete(reqId))
10131
+ resolve([]);
10132
+ }, timeoutMs);
10133
+ this.stateListResolvers.set(reqId, { resolve, timer });
10134
+ try {
10135
+ this.ws.send(JSON.stringify({ type: "list_state", _reqId: reqId }));
10136
+ } catch {
10137
+ this.stateListResolvers.delete(reqId);
10138
+ clearTimeout(timer);
10139
+ resolve([]);
10140
+ }
10141
+ });
10142
+ }
10143
+ setState(key, value) {
10144
+ if (this._status !== "open" || !this.ws)
10145
+ return;
10146
+ try {
10147
+ this.ws.send(JSON.stringify({ type: "set_state", key, value }));
10148
+ } catch {}
10149
+ }
10150
+ async remember(content, tags, timeoutMs = 5000) {
10151
+ if (this._status !== "open" || !this.ws)
10152
+ return null;
10153
+ return new Promise((resolve) => {
10154
+ const reqId = `mr-${++this.reqCounter}`;
10155
+ const timer = setTimeout(() => {
10156
+ if (this.memoryStoreResolvers.delete(reqId))
10157
+ resolve(null);
10158
+ }, timeoutMs);
10159
+ this.memoryStoreResolvers.set(reqId, { resolve, timer });
10160
+ try {
10161
+ this.ws.send(JSON.stringify({ type: "remember", content, tags, _reqId: reqId }));
10162
+ } catch {
10163
+ this.memoryStoreResolvers.delete(reqId);
10164
+ clearTimeout(timer);
10165
+ resolve(null);
10166
+ }
10167
+ });
10168
+ }
10169
+ async recall(query, timeoutMs = 5000) {
10170
+ if (this._status !== "open" || !this.ws)
10171
+ return [];
10172
+ return new Promise((resolve) => {
10173
+ const reqId = `mc-${++this.reqCounter}`;
10174
+ const timer = setTimeout(() => {
10175
+ if (this.memoryRecallResolvers.delete(reqId))
10176
+ resolve([]);
10177
+ }, timeoutMs);
10178
+ this.memoryRecallResolvers.set(reqId, { resolve, timer });
10179
+ try {
10180
+ this.ws.send(JSON.stringify({ type: "recall", query, _reqId: reqId }));
10181
+ } catch {
10182
+ this.memoryRecallResolvers.delete(reqId);
10183
+ clearTimeout(timer);
10184
+ resolve([]);
10185
+ }
10186
+ });
10187
+ }
10188
+ forget(memoryId) {
10189
+ if (this._status !== "open" || !this.ws)
10190
+ return;
10191
+ try {
10192
+ this.ws.send(JSON.stringify({ type: "forget", memoryId }));
10193
+ } catch {}
10194
+ }
9668
10195
  setProfile(profile) {
9669
10196
  if (this._status !== "open" || !this.ws)
9670
10197
  return;
@@ -9796,6 +10323,17 @@ async function drainOnce(opts, log2) {
9796
10323
  if (markInflight(opts.db, row.id, now) === 0)
9797
10324
  continue;
9798
10325
  const fpHex = bufferToHex(row.request_fingerprint);
10326
+ let broker;
10327
+ if (row.mesh) {
10328
+ broker = opts.brokers.get(row.mesh);
10329
+ } else if (opts.brokers.size === 1) {
10330
+ broker = opts.brokers.values().next().value;
10331
+ }
10332
+ if (!broker) {
10333
+ log2("warn", "drain_no_broker_for_mesh", { id: row.id, mesh: row.mesh ?? "(null)" });
10334
+ markDead(opts.db, row.id, `no_broker_for_mesh:${row.mesh ?? "null"}`);
10335
+ continue;
10336
+ }
9799
10337
  let targetSpec;
9800
10338
  let nonce;
9801
10339
  let ciphertext;
@@ -9813,7 +10351,7 @@ async function drainOnce(opts, log2) {
9813
10351
  }
9814
10352
  let res;
9815
10353
  try {
9816
- res = await opts.broker.send({
10354
+ res = await broker.send({
9817
10355
  targetSpec,
9818
10356
  priority,
9819
10357
  nonce,
@@ -10159,10 +10697,10 @@ async function runDaemon(opts = {}) {
10159
10697
  }
10160
10698
  const bus = new EventBus;
10161
10699
  const cfg = readConfig();
10162
- let mesh = null;
10700
+ let meshes;
10163
10701
  if (opts.mesh) {
10164
- mesh = cfg.meshes.find((m) => m.slug === opts.mesh) ?? null;
10165
- if (!mesh) {
10702
+ const found = cfg.meshes.find((m) => m.slug === opts.mesh);
10703
+ if (!found) {
10166
10704
  process.stderr.write(`mesh not found: ${opts.mesh}
10167
10705
  `);
10168
10706
  process.stderr.write(`joined meshes: ${cfg.meshes.map((m) => m.slug).join(", ") || "(none)"}
@@ -10173,8 +10711,7 @@ async function runDaemon(opts = {}) {
10173
10711
  } catch {}
10174
10712
  return 2;
10175
10713
  }
10176
- } else if (cfg.meshes.length === 1) {
10177
- mesh = cfg.meshes[0];
10714
+ meshes = [found];
10178
10715
  } else if (cfg.meshes.length === 0) {
10179
10716
  process.stderr.write(`no mesh joined; run \`claudemesh join <invite-url>\` first
10180
10717
  `);
@@ -10184,43 +10721,41 @@ async function runDaemon(opts = {}) {
10184
10721
  } catch {}
10185
10722
  return 2;
10186
10723
  } else {
10187
- process.stderr.write(`multiple meshes joined; pass --mesh <slug>
10188
- `);
10189
- process.stderr.write(`available: ${cfg.meshes.map((m) => m.slug).join(", ")}
10190
- `);
10191
- releaseSingletonLock();
10192
- try {
10193
- outboxDb.close();
10194
- } catch {}
10195
- return 2;
10196
- }
10197
- const broker = new DaemonBrokerClient(mesh, {
10198
- displayName: opts.displayName,
10199
- onStatusChange: (s) => {
10200
- process.stdout.write(JSON.stringify({
10201
- msg: "broker_status",
10202
- status: s,
10203
- mesh: mesh.slug,
10204
- ts: new Date().toISOString()
10205
- }) + `
10724
+ meshes = cfg.meshes;
10725
+ }
10726
+ const brokers = new Map;
10727
+ const meshConfigs = new Map;
10728
+ for (const mesh of meshes) {
10729
+ meshConfigs.set(mesh.slug, mesh);
10730
+ const broker = new DaemonBrokerClient(mesh, {
10731
+ displayName: opts.displayName,
10732
+ onStatusChange: (s) => {
10733
+ process.stdout.write(JSON.stringify({
10734
+ msg: "broker_status",
10735
+ status: s,
10736
+ mesh: mesh.slug,
10737
+ ts: new Date().toISOString()
10738
+ }) + `
10206
10739
  `);
10207
- bus.publish("broker_status", { mesh: mesh.slug, status: s });
10208
- },
10209
- onPush: (m) => {
10210
- const sessionKeys = broker.getSessionKeys();
10211
- handleBrokerPush(m, {
10212
- db: inboxDb,
10213
- bus,
10214
- meshSlug: mesh.slug,
10215
- recipientSecretKeyHex: mesh.secretKey,
10216
- sessionSecretKeyHex: sessionKeys?.sessionSecretKey
10217
- });
10218
- }
10219
- });
10220
- broker.connect().catch((err) => process.stderr.write(`broker connect failed: ${String(err)}
10740
+ bus.publish("broker_status", { mesh: mesh.slug, status: s });
10741
+ },
10742
+ onPush: (m) => {
10743
+ const sessionKeys = broker.getSessionKeys();
10744
+ handleBrokerPush(m, {
10745
+ db: inboxDb,
10746
+ bus,
10747
+ meshSlug: mesh.slug,
10748
+ recipientSecretKeyHex: mesh.secretKey,
10749
+ sessionSecretKeyHex: sessionKeys?.sessionSecretKey
10750
+ });
10751
+ }
10752
+ });
10753
+ broker.connect().catch((err) => process.stderr.write(`broker connect failed for ${mesh.slug}: ${String(err)}
10221
10754
  `));
10755
+ brokers.set(mesh.slug, broker);
10756
+ }
10222
10757
  let drain = null;
10223
- drain = startDrainWorker({ db: outboxDb, broker });
10758
+ drain = startDrainWorker({ db: outboxDb, brokers });
10224
10759
  const ipc2 = startIpcServer({
10225
10760
  localToken,
10226
10761
  tcpEnabled,
@@ -10228,10 +10763,9 @@ async function runDaemon(opts = {}) {
10228
10763
  outboxDb,
10229
10764
  inboxDb,
10230
10765
  bus,
10231
- broker,
10232
- onPendingInserted: () => drain?.wake(),
10233
- meshSecretKey: mesh.secretKey,
10234
- meshSlug: mesh.slug
10766
+ brokers,
10767
+ meshConfigs,
10768
+ onPendingInserted: () => drain?.wake()
10235
10769
  });
10236
10770
  try {
10237
10771
  await ipc2.ready;
@@ -10246,7 +10780,7 @@ async function runDaemon(opts = {}) {
10246
10780
  pid: process.pid,
10247
10781
  sock: DAEMON_PATHS.SOCK_FILE,
10248
10782
  tcp: tcpEnabled ? `127.0.0.1:47823` : null,
10249
- mesh: mesh.slug,
10783
+ meshes: meshes.map((m) => m.slug),
10250
10784
  ts: new Date().toISOString()
10251
10785
  }) + `
10252
10786
  `);
@@ -10259,7 +10793,11 @@ async function runDaemon(opts = {}) {
10259
10793
  `);
10260
10794
  if (drain)
10261
10795
  await drain.close();
10262
- await broker.close();
10796
+ for (const b of brokers.values()) {
10797
+ try {
10798
+ await b.close();
10799
+ } catch {}
10800
+ }
10263
10801
  await ipc2.close();
10264
10802
  try {
10265
10803
  outboxDb.close();
@@ -17475,7 +18013,7 @@ USAGE
17475
18013
  claudemesh <invite-url> join a mesh, then launch
17476
18014
  claudemesh launch --name <n> --join <url> join + launch in one step
17477
18015
 
17478
- Mesh
18016
+ Mesh (alias: "workspace" — claudemesh workspace <verb> mirrors each)
17479
18017
  claudemesh create <name> create a new mesh
17480
18018
  claudemesh join <url> join a mesh (accepts short /i/ or long /join/ link)
17481
18019
  claudemesh launch [slug] launch Claude Code on a mesh (alias: connect)
@@ -17749,6 +18287,47 @@ async function main() {
17749
18287
  process.exit(await invite2(positionals[0], { mesh: flags.mesh, json: !!flags.json }));
17750
18288
  break;
17751
18289
  }
18290
+ case "workspace": {
18291
+ const sub = positionals[0];
18292
+ if (!sub || sub === "launch" || sub === "connect" || sub === "open") {
18293
+ const { runLaunch: runLaunch2 } = await Promise.resolve().then(() => (init_launch(), exports_launch));
18294
+ await runLaunch2({
18295
+ mesh: positionals[1] ?? flags.mesh,
18296
+ name: flags.name,
18297
+ join: flags.join,
18298
+ yes: !!flags.y || !!flags.yes,
18299
+ resume: flags.resume
18300
+ }, process.argv.slice(2));
18301
+ } else if (sub === "list" || sub === "ls") {
18302
+ const { runList: runList2 } = await Promise.resolve().then(() => (init_list2(), exports_list));
18303
+ await runList2();
18304
+ } else if (sub === "info") {
18305
+ const { runInfo: runInfo2 } = await Promise.resolve().then(() => (init_info2(), exports_info));
18306
+ await runInfo2({});
18307
+ } else if (sub === "create" || sub === "new") {
18308
+ const { newMesh: newMesh2 } = await Promise.resolve().then(() => (init_new(), exports_new));
18309
+ process.exit(await newMesh2(positionals[1] ?? "", { json: !!flags.json }));
18310
+ } else if (sub === "join" || sub === "add") {
18311
+ const { runJoin: runJoin2 } = await Promise.resolve().then(() => (init_join2(), exports_join));
18312
+ await runJoin2(positionals.slice(1));
18313
+ } else if (sub === "delete" || sub === "rm") {
18314
+ const { deleteMesh: deleteMesh2 } = await Promise.resolve().then(() => (init_delete_mesh(), exports_delete_mesh));
18315
+ process.exit(await deleteMesh2(positionals[1] ?? "", { yes: !!flags.y || !!flags.yes }));
18316
+ } else if (sub === "rename") {
18317
+ const { rename: rename2 } = await Promise.resolve().then(() => (init_rename2(), exports_rename));
18318
+ process.exit(await rename2(positionals[1] ?? "", positionals[2] ?? ""));
18319
+ } else if (sub === "share" || sub === "invite") {
18320
+ const { invite: invite2 } = await Promise.resolve().then(() => (init_invite(), exports_invite));
18321
+ process.exit(await invite2(positionals[1], { mesh: flags.mesh, json: !!flags.json }));
18322
+ } else if (sub === "overview") {
18323
+ const { runMe: runMe2 } = await Promise.resolve().then(() => (init_me(), exports_me));
18324
+ process.exit(await runMe2({ mesh: flags.mesh, json: !!flags.json }));
18325
+ } else {
18326
+ console.error("Usage: claudemesh workspace <list|info|create|join|delete|rename|share|launch|overview>");
18327
+ process.exit(EXIT.INVALID_ARGS);
18328
+ }
18329
+ break;
18330
+ }
17752
18331
  case "disconnect": {
17753
18332
  const { runDisconnect: runDisconnect2 } = await Promise.resolve().then(() => (init_kick(), exports_kick));
17754
18333
  process.exit(await runDisconnect2(positionals[0], { mesh: flags.mesh, stale: flags.stale, all: !!flags.all }));
@@ -18594,4 +19173,4 @@ main().catch((err) => {
18594
19173
  process.exit(EXIT.INTERNAL_ERROR);
18595
19174
  });
18596
19175
 
18597
- //# debugId=EC115E4068A7B5F664756E2164756E21
19176
+ //# debugId=5CE8252722B33A8D64756E2164756E21