claudemesh-cli 1.26.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.26.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,1227 +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
- ...args.expectedMesh ? { mesh: args.expectedMesh } : {}
6949
- }
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
6950
6940
  });
6951
- if (res.status === 202 || res.status === 200) {
6952
- return {
6953
- ok: true,
6954
- messageId: res.body.broker_message_id ?? res.body.client_message_id ?? "",
6955
- duplicate: res.body.duplicate,
6956
- status: res.body.status
6957
- };
6941
+ if (flags.json) {
6942
+ console.log(JSON.stringify(ws, null, 2));
6943
+ return EXIT.SUCCESS;
6958
6944
  }
6959
- return { ok: false, error: res.body.error ?? `daemon http ${res.status}` };
6960
- } catch (err) {
6961
- const msg = String(err);
6962
- if (/ENOENT|ECONNREFUSED|ipc_timeout/.test(msg))
6963
- return null;
6964
- return { ok: false, error: msg };
6965
- }
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
+ });
6966
6964
  }
6967
- var init_daemon_route = __esm(() => {
6968
- init_client4();
6969
- 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();
6970
6988
  });
6971
6989
 
6972
- // src/commands/peers.ts
6973
- var exports_peers = {};
6974
- __export(exports_peers, {
6975
- runPeers: () => runPeers
6990
+ // src/commands/kick.ts
6991
+ var exports_kick = {};
6992
+ __export(exports_kick, {
6993
+ runKick: () => runKick,
6994
+ runDisconnect: () => runDisconnect
6976
6995
  });
6977
- function projectFields(record, fields) {
6978
- const out = {};
6979
- for (const f of fields) {
6980
- const sourceKey = FIELD_ALIAS[f] ?? f;
6981
- out[f] = record[sourceKey];
6982
- }
6983
- return out;
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;
6984
7009
  }
6985
- async function listPeersForMesh(slug) {
6986
- const config = readConfig();
6987
- const joined = config.meshes.find((m) => m.slug === slug);
6988
- const selfMemberPubkey = joined?.pubkey ?? null;
6989
- try {
6990
- const { tryListPeersViaDaemon: tryListPeersViaDaemon2 } = await Promise.resolve().then(() => (init_daemon_route(), exports_daemon_route));
6991
- const dr = await tryListPeersViaDaemon2();
6992
- if (dr !== null) {
6993
- return dr.map((p) => annotateSelf(p, selfMemberPubkey, null));
6994
- }
6995
- } catch {}
6996
- const bridged = await tryBridge(slug, "peers");
6997
- if (bridged && bridged.ok) {
6998
- const peers = bridged.result;
6999
- return peers.map((p) => annotateSelf(p, selfMemberPubkey, null));
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 };
7000
7018
  }
7001
- let result = [];
7002
- await withMesh({ meshSlug: slug }, async (client) => {
7003
- const all = await client.listPeers();
7004
- const selfSessionPubkey = client.getSessionPubkey();
7005
- result = all.map((p) => annotateSelf(p, selfMemberPubkey, selfSessionPubkey));
7006
- });
7007
- return result;
7008
- }
7009
- function annotateSelf(peer, selfMemberPubkey, selfSessionPubkey) {
7010
- const isSelf = !!(selfMemberPubkey && peer.memberPubkey && peer.memberPubkey === selfMemberPubkey);
7011
- const isThisSession = !!(isSelf && selfSessionPubkey && peer.pubkey === selfSessionPubkey);
7012
- return { ...peer, isSelf, isThisSession };
7019
+ if (target)
7020
+ return { type: kind, target };
7021
+ return { error: `Usage: claudemesh ${kind} <peer> | --stale 30m | --all` };
7013
7022
  }
7014
- async function runPeers(flags) {
7023
+ async function runDisconnect(target, opts = {}) {
7015
7024
  const config = readConfig();
7016
- const slugs = flags.mesh ? [flags.mesh] : config.meshes.map((m) => m.slug);
7017
- if (slugs.length === 0) {
7018
- render.err("No meshes joined.");
7019
- render.hint("claudemesh <invite-url> # join + launch");
7020
- process.exit(1);
7025
+ const meshSlug = opts.mesh ?? config.meshes[0]?.slug;
7026
+ if (!meshSlug) {
7027
+ render.err("No mesh joined.");
7028
+ return EXIT.NOT_FOUND;
7021
7029
  }
7022
- const fieldList = typeof flags.json === "string" && flags.json.length > 0 ? flags.json.split(",").map((s) => s.trim()).filter(Boolean) : null;
7023
- const wantsJson = flags.json !== undefined && flags.json !== false;
7024
- const allJson = [];
7025
- for (const slug of slugs) {
7026
- try {
7027
- const peers = await listPeersForMesh(slug);
7028
- if (wantsJson) {
7029
- const projected = fieldList ? peers.map((p) => projectFields(p, fieldList)) : peers;
7030
- allJson.push({ mesh: slug, peers: projected });
7031
- continue;
7032
- }
7033
- render.section(`peers on ${slug} (${peers.length})`);
7034
- if (peers.length === 0) {
7035
- render.info(dim(" (no peers connected)"));
7036
- continue;
7037
- }
7038
- for (const p of peers) {
7039
- const groups = p.groups.length ? " [" + p.groups.map((g) => `@${g.name}${g.role ? `:${g.role}` : ""}`).join(", ") + "]" : "";
7040
- const statusDot = p.status === "working" ? yellow("●") : green("●");
7041
- const name = bold(p.displayName);
7042
- const meta = [];
7043
- if (p.peerType)
7044
- meta.push(p.peerType);
7045
- if (p.channel)
7046
- meta.push(p.channel);
7047
- if (p.model)
7048
- meta.push(p.model);
7049
- const metaStr = meta.length ? dim(` (${meta.join(", ")})`) : "";
7050
- const summary = p.summary ? dim(` — ${p.summary}`) : "";
7051
- const pubkeyTag = dim(` · ${p.pubkey.slice(0, 16)}…`);
7052
- const selfTag = p.isThisSession ? dim(" ") + yellow("(this session)") : p.isSelf ? dim(" ") + yellow("(your other session)") : "";
7053
- render.info(`${statusDot} ${name}${selfTag}${groups}${metaStr}${pubkeyTag}${summary}`);
7054
- if (p.cwd)
7055
- render.info(dim(` cwd: ${p.cwd}`));
7056
- }
7057
- } catch (e) {
7058
- render.err(`${slug}: ${e instanceof Error ? e.message : String(e)}`);
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`.");
7059
7043
  }
7044
+ return EXIT.SUCCESS;
7045
+ });
7046
+ }
7047
+ async function runKick(target, opts = {}) {
7048
+ const config = readConfig();
7049
+ const meshSlug = opts.mesh ?? config.meshes[0]?.slug;
7050
+ if (!meshSlug) {
7051
+ render.err("No mesh joined.");
7052
+ return EXIT.NOT_FOUND;
7060
7053
  }
7061
- if (wantsJson) {
7062
- process.stdout.write(JSON.stringify(slugs.length === 1 ? allJson[0]?.peers : allJson, null, 2) + `
7063
- `);
7054
+ const built = buildPayload("kick", target, opts);
7055
+ if ("error" in built) {
7056
+ render.err(String(built.error));
7057
+ return EXIT.INVALID_ARGS;
7064
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`.");
7067
+ }
7068
+ return EXIT.SUCCESS;
7069
+ });
7065
7070
  }
7066
- var FIELD_ALIAS;
7067
- var init_peers = __esm(() => {
7071
+ var init_kick = __esm(() => {
7068
7072
  init_connect();
7069
7073
  init_facade();
7070
- init_client3();
7071
7074
  init_render();
7072
- init_styles();
7073
- FIELD_ALIAS = {
7074
- name: "displayName"
7075
- };
7075
+ init_exit_codes();
7076
7076
  });
7077
7077
 
7078
- // src/commands/send.ts
7079
- var exports_send = {};
7080
- __export(exports_send, {
7081
- runSend: () => runSend
7078
+ // src/commands/ban.ts
7079
+ var exports_ban = {};
7080
+ __export(exports_ban, {
7081
+ runUnban: () => runUnban,
7082
+ runBans: () => runBans,
7083
+ runBan: () => runBan
7082
7084
  });
7083
- async function runSend(flags, to, message) {
7084
- if (!to || !message) {
7085
- render.err("Usage: claudemesh send <to> <message>");
7086
- 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;
7087
7089
  }
7088
- const priority = flags.priority === "now" ? "now" : flags.priority === "low" ? "low" : "next";
7089
7090
  const config = readConfig();
7090
- const meshSlug = flags.mesh ?? (config.meshes.length === 1 ? config.meshes[0].slug : null);
7091
- if (!flags.self && meshSlug) {
7092
- const joined = config.meshes.find((m) => m.slug === meshSlug);
7093
- if (joined && /^[0-9a-f]{64}$/i.test(to) && to.toLowerCase() === joined.pubkey.toLowerCase()) {
7094
- render.err(`Target "${to.slice(0, 16)}…" is your own member pubkey on mesh "${meshSlug}".`);
7095
- render.hint("Pass --self to message a sibling session of your own member, or pick a different peer's pubkey.");
7096
- process.exit(1);
7097
- }
7091
+ const meshSlug = opts.mesh ?? config.meshes[0]?.slug;
7092
+ if (!meshSlug) {
7093
+ render.err("No mesh joined.");
7094
+ return EXIT.NOT_FOUND;
7098
7095
  }
7099
- {
7100
- const dr = await trySendViaDaemon({ to, message, priority, expectedMesh: meshSlug ?? undefined });
7101
- if (dr !== null) {
7102
- if (dr.ok) {
7103
- if (flags.json)
7104
- console.log(JSON.stringify({ ok: true, messageId: dr.messageId, target: to, via: "daemon", duplicate: !!dr.duplicate }));
7105
- else
7106
- render.ok(`sent to ${to} (daemon)`, dr.messageId ? dim(dr.messageId.slice(0, 8)) : undefined);
7107
- return;
7108
- }
7109
- if (flags.json)
7110
- console.log(JSON.stringify({ ok: false, error: dr.error, via: "daemon" }));
7111
- else
7112
- render.err(`send failed (daemon): ${dr.error}`);
7113
- process.exit(1);
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");
7114
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;
7115
7111
  }
7116
- if (meshSlug) {
7117
- const bridged = await tryBridge(meshSlug, "send", { to, message, priority });
7118
- if (bridged !== null) {
7119
- if (bridged.ok) {
7120
- const r = bridged.result;
7121
- if (flags.json) {
7122
- console.log(JSON.stringify({ ok: true, messageId: r.messageId, target: to }));
7123
- } else {
7124
- render.ok(`sent to ${to}`, r.messageId ? dim(r.messageId.slice(0, 8)) : undefined);
7125
- }
7126
- return;
7127
- }
7128
- if (flags.json) {
7129
- console.log(JSON.stringify({ ok: false, error: bridged.error }));
7130
- } else {
7131
- render.err(`send failed: ${bridged.error}`);
7132
- }
7133
- 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");
7134
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;
7135
7134
  }
7136
- await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
7137
- let targetSpec = to;
7138
- if (to.startsWith("#") && !/^#[0-9a-z_-]{20,}$/i.test(to)) {
7139
- const name = to.slice(1);
7140
- const topics = await client.topicList();
7141
- const match = topics.find((t) => t.name === name);
7142
- if (!match) {
7143
- const names = topics.map((t) => "#" + t.name).join(", ");
7144
- render.err(`Topic "${to}" not found.`, `topics: ${names || "(none)"}`);
7145
- process.exit(1);
7146
- }
7147
- targetSpec = "#" + match.id;
7148
- } else if (!to.startsWith("@") && !to.startsWith("#") && to !== "*" && !/^[0-9a-f]{64}$/i.test(to)) {
7149
- const peers = await client.listPeers();
7150
- const match = peers.find((p) => p.displayName.toLowerCase() === to.toLowerCase());
7151
- if (!match) {
7152
- const names = peers.map((p) => p.displayName).join(", ");
7153
- render.err(`Peer "${to}" not found.`, `online: ${names || "(none)"}`);
7154
- process.exit(1);
7155
- }
7156
- targetSpec = match.pubkey;
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;
7157
7142
  }
7158
- const result = await client.send(targetSpec, message, priority);
7159
- if (result.ok) {
7160
- if (flags.json) {
7161
- console.log(JSON.stringify({ ok: true, messageId: result.messageId, target: to }));
7162
- } else {
7163
- render.ok(`sent to ${to}`, result.messageId ? dim(result.messageId.slice(0, 8)) : undefined);
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
+ `);
7184
+ }
7185
+ return lines;
7186
+ }
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);
7164
7222
  }
7165
- } else {
7166
- if (flags.json) {
7167
- console.log(JSON.stringify({ ok: false, error: result.error ?? "unknown" }));
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;
7242
+ }
7243
+ });
7244
+ socket.on("error", (err) => {
7245
+ const code = err.code;
7246
+ if (code === "ECONNREFUSED" || code === "ENOENT" || code === "EPERM") {
7247
+ finish(null);
7168
7248
  } else {
7169
- render.err(`send failed: ${result.error ?? "unknown error"}`);
7249
+ finish(null);
7170
7250
  }
7171
- process.exit(1);
7172
- }
7251
+ });
7252
+ socket.on("close", () => {
7253
+ finish(null);
7254
+ });
7173
7255
  });
7174
7256
  }
7175
- var init_send = __esm(() => {
7176
- init_connect();
7177
- init_facade();
7178
- init_client3();
7179
- init_daemon_route();
7180
- init_render();
7181
- init_styles();
7257
+ var DEFAULT_TIMEOUT_MS = 5000;
7258
+ var init_client3 = __esm(() => {
7259
+ init_protocol();
7182
7260
  });
7183
7261
 
7184
- // src/commands/inbox.ts
7185
- var exports_inbox = {};
7186
- __export(exports_inbox, {
7187
- runInbox: () => runInbox
7188
- });
7189
- function formatMessage(msg) {
7190
- const text = msg.plaintext ?? `[encrypted: ${msg.ciphertext.slice(0, 32)}…]`;
7191
- const from = msg.senderPubkey.slice(0, 8);
7192
- const time = new Date(msg.createdAt).toLocaleTimeString();
7193
- const kindTag = msg.kind === "direct" ? "→ direct" : msg.kind;
7194
- return ` ${bold(from)} ${dim(`[${kindTag}] ${time}`)}
7195
- ${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
+ }
7196
7272
  }
7197
- async function runInbox(flags) {
7198
- const waitMs = (flags.wait ?? 1) * 1000;
7199
- await withMesh({ meshSlug: flags.mesh ?? null }, async (client, mesh) => {
7200
- await new Promise((resolve) => setTimeout(resolve, waitMs));
7201
- const messages = client.drainPushBuffer();
7202
- if (flags.json) {
7203
- process.stdout.write(JSON.stringify(messages, null, 2) + `
7204
- `);
7205
- return;
7206
- }
7207
- if (messages.length === 0) {
7208
- render.info(dim(`No messages on mesh "${mesh.slug}".`));
7209
- return;
7210
- }
7211
- render.section(`inbox — ${mesh.slug} (${messages.length} message${messages.length === 1 ? "" : "s"})`);
7212
- for (const msg of messages) {
7213
- 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
+ });
7214
7286
 
7215
- `);
7216
- }
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();
7217
7325
  });
7218
7326
  }
7219
- var init_inbox = __esm(() => {
7220
- init_connect();
7221
- init_render();
7222
- 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
+ };
7223
7340
  });
7224
7341
 
7225
- // src/commands/state.ts
7226
- var exports_state = {};
7227
- __export(exports_state, {
7228
- runStateSet: () => runStateSet,
7229
- runStateList: () => runStateList,
7230
- 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
7231
7355
  });
7232
- async function runStateGet(flags, key) {
7233
- await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
7234
- const entry = await client.getState(key);
7235
- if (!entry) {
7236
- render.info(dim("(not set)"));
7237
- return;
7238
- }
7239
- if (flags.json) {
7240
- console.log(JSON.stringify(entry, null, 2));
7356
+ import { existsSync as existsSync7 } from "node:fs";
7357
+ function meshQuery(mesh) {
7358
+ return mesh ? `?mesh=${encodeURIComponent(mesh)}` : "";
7359
+ }
7360
+ async function tryListPeersViaDaemon(mesh) {
7361
+ if (!existsSync7(DAEMON_PATHS.SOCK_FILE))
7362
+ return null;
7363
+ try {
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;
7373
+ }
7374
+ }
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
+ }
7389
+ }
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
7397
+ });
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)
7241
7414
  return;
7242
- }
7243
- const val = typeof entry.value === "string" ? entry.value : JSON.stringify(entry.value);
7244
- render.info(val);
7245
- render.info(dim(` set by ${entry.updatedBy} at ${new Date(entry.updatedAt).toLocaleString()}`));
7246
- });
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
+ }
7424
+ }
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
+ }
7439
+ }
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 } : {} }
7449
+ });
7450
+ return res.status === 200 && res.body.ok === true;
7451
+ } catch {
7452
+ return false;
7453
+ }
7247
7454
  }
7248
- async function runStateSet(flags, key, value) {
7249
- let parsed;
7455
+ async function tryRememberViaDaemon(content, tags, mesh) {
7456
+ if (!existsSync7(DAEMON_PATHS.SOCK_FILE))
7457
+ return null;
7250
7458
  try {
7251
- parsed = JSON.parse(value);
7459
+ const res = await ipc({
7460
+ method: "POST",
7461
+ path: "/v1/memory",
7462
+ timeoutMs: 5000,
7463
+ body: { content, ...tags?.length ? { tags } : {}, ...mesh ? { mesh } : {} }
7464
+ });
7465
+ if (res.status !== 200 || !res.body.id)
7466
+ return null;
7467
+ return { id: res.body.id, mesh: res.body.mesh };
7252
7468
  } catch {
7253
- parsed = value;
7469
+ return null;
7254
7470
  }
7255
- await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
7256
- await client.setState(key, parsed);
7257
- render.ok(`${bold(key)} = ${JSON.stringify(parsed)}`);
7258
- });
7259
7471
  }
7260
- async function runStateList(flags) {
7261
- await withMesh({ meshSlug: flags.mesh ?? null }, async (client, mesh) => {
7262
- const entries = await client.listState();
7263
- if (flags.json) {
7264
- console.log(JSON.stringify(entries, null, 2));
7265
- return;
7266
- }
7267
- if (entries.length === 0) {
7268
- render.info(dim(`No state on mesh "${mesh.slug}".`));
7269
- return;
7270
- }
7271
- render.section(`state (${entries.length})`);
7272
- for (const e of entries) {
7273
- const val = typeof e.value === "string" ? e.value : JSON.stringify(e.value);
7274
- process.stdout.write(` ${bold(e.key)}: ${val}
7275
- `);
7276
- process.stdout.write(` ${dim(e.updatedBy + " · " + new Date(e.updatedAt).toLocaleString())}
7277
- `);
7278
- }
7279
- });
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
+ }
7280
7487
  }
7281
- var init_state = __esm(() => {
7282
- init_connect();
7283
- init_render();
7284
- init_styles();
7285
- });
7286
-
7287
- // src/services/api/with-rest-key.ts
7288
- async function withRestKey(opts, fn) {
7289
- return withMesh({ meshSlug: opts.meshSlug ?? null }, async (client, mesh) => {
7290
- const result = await client.apiKeyCreate({
7291
- label: `cli-${opts.purpose ?? "rest"}-${process.pid}`,
7292
- capabilities: opts.capabilities ?? ["read"],
7293
- topicScopes: opts.topicScopes ?? undefined,
7294
- expiresAt: new Date(Date.now() + 5 * 60 * 1000).toISOString()
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
+ }
7295
7514
  });
7296
- if (!result || !result.secret) {
7297
- throw new Error("apikey mint failed — broker did not return a secret");
7298
- }
7299
- try {
7300
- return await fn({
7301
- secret: result.secret,
7302
- meshId: mesh.meshId,
7303
- meshSlug: mesh.slug,
7304
- client,
7305
- mesh
7306
- });
7307
- } finally {
7308
- try {
7309
- await client.apiKeyRevoke(result.id);
7310
- } catch {}
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
+ };
7311
7522
  }
7312
- });
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
+ }
7313
7530
  }
7314
- var init_with_rest_key = __esm(() => {
7315
- init_connect();
7531
+ var init_daemon_route = __esm(() => {
7532
+ init_client4();
7533
+ init_paths2();
7316
7534
  });
7317
7535
 
7318
- // src/commands/me.ts
7319
- var exports_me = {};
7320
- __export(exports_me, {
7321
- runMeTopics: () => runMeTopics,
7322
- runMeTasks: () => runMeTasks,
7323
- runMeState: () => runMeState,
7324
- runMeSearch: () => runMeSearch,
7325
- runMeNotifications: () => runMeNotifications,
7326
- runMeMemory: () => runMeMemory,
7327
- runMeActivity: () => runMeActivity,
7328
- runMe: () => runMe
7536
+ // src/commands/peers.ts
7537
+ var exports_peers = {};
7538
+ __export(exports_peers, {
7539
+ runPeers: () => runPeers
7329
7540
  });
7330
- function resolveMeshForMint(explicit) {
7331
- if (explicit)
7332
- return explicit;
7333
- const cfg = readConfig();
7334
- return cfg.meshes[0]?.slug ?? null;
7335
- }
7336
- async function runMe(flags) {
7337
- return withRestKey({
7338
- meshSlug: resolveMeshForMint(flags.mesh),
7339
- purpose: "workspace-overview",
7340
- capabilities: ["read"]
7341
- }, async ({ secret }) => {
7342
- const ws = await request({
7343
- path: "/api/v1/me/workspace",
7344
- token: secret
7345
- });
7346
- if (flags.json) {
7347
- console.log(JSON.stringify(ws, null, 2));
7348
- return EXIT.SUCCESS;
7349
- }
7350
- render.section(`${clay("workspace")} — ${bold(ws.userId.slice(0, 8))} ${dim(`· ${ws.totals.meshes} mesh${ws.totals.meshes === 1 ? "" : "es"}`)}`);
7351
- const totalsLine = [
7352
- `${green(String(ws.totals.online))}/${ws.totals.peers} online`,
7353
- `${ws.totals.topics} topic${ws.totals.topics === 1 ? "" : "s"}`,
7354
- ws.totals.unreadMentions > 0 ? yellow(`${ws.totals.unreadMentions} unread @you`) : dim("0 unread @you")
7355
- ].join(dim(" · "));
7356
- process.stdout.write(" " + totalsLine + `
7357
-
7358
- `);
7359
- if (ws.meshes.length === 0) {
7360
- process.stdout.write(dim(" no meshes joined — run `claudemesh new` or accept an invite\n"));
7361
- return EXIT.SUCCESS;
7362
- }
7363
- const slugWidth = Math.max(...ws.meshes.map((m) => m.slug.length), 8);
7364
- for (const m of ws.meshes) {
7365
- const slug = cyan(m.slug.padEnd(slugWidth));
7366
- const peers = `${m.online}/${m.peers}`;
7367
- const role = dim(m.myRole);
7368
- const unread = m.unreadMentions > 0 ? " " + yellow(`${m.unreadMentions} @you`) : "";
7369
- process.stdout.write(` ${slug} ${peers.padStart(5)} online ${dim(String(m.topics).padStart(2) + " topics")} ${role}${unread}
7370
- `);
7371
- }
7372
- return EXIT.SUCCESS;
7373
- });
7374
- }
7375
- async function runMeTopics(flags) {
7376
- return withRestKey({
7377
- meshSlug: resolveMeshForMint(flags.mesh),
7378
- purpose: "workspace-topics",
7379
- capabilities: ["read"]
7380
- }, async ({ secret }) => {
7381
- const ws = await request({
7382
- path: "/api/v1/me/topics",
7383
- token: secret
7384
- });
7385
- const visible = flags.unread ? ws.topics.filter((t) => t.unread > 0) : ws.topics;
7386
- if (flags.json) {
7387
- console.log(JSON.stringify({ topics: visible, totals: ws.totals }, null, 2));
7388
- return EXIT.SUCCESS;
7389
- }
7390
- render.section(`${clay("topics")} — ${ws.totals.topics} across all meshes ${dim(ws.totals.unread > 0 ? `· ${ws.totals.unread} unread` : "· all read")}`);
7391
- if (visible.length === 0) {
7392
- process.stdout.write(dim(flags.unread ? ` no unread topics
7393
- ` : " no topics — run `claudemesh topic create #general`\n"));
7394
- return EXIT.SUCCESS;
7395
- }
7396
- const slugWidth = Math.max(...visible.map((t) => t.meshSlug.length), 6);
7397
- const nameWidth = Math.max(...visible.map((t) => t.name.length), 8);
7398
- for (const t of visible) {
7399
- const slug = dim(t.meshSlug.padEnd(slugWidth));
7400
- const name = cyan(t.name.padEnd(nameWidth));
7401
- const unread = t.unread > 0 ? yellow(`${t.unread} unread`.padStart(10)) : dim("·".padStart(10));
7402
- const last = t.lastMessageAt ? dim(formatRelativeTime(t.lastMessageAt)) : dim("never");
7403
- process.stdout.write(` ${slug} ${name} ${unread} ${last}
7404
- `);
7405
- }
7406
- return EXIT.SUCCESS;
7407
- });
7408
- }
7409
- async function runMeNotifications(flags) {
7410
- return withRestKey({
7411
- meshSlug: resolveMeshForMint(flags.mesh),
7412
- purpose: "workspace-notifications",
7413
- capabilities: ["read"]
7414
- }, async ({ secret }) => {
7415
- const params = new URLSearchParams;
7416
- if (flags.all)
7417
- params.set("include", "all");
7418
- if (flags.since)
7419
- params.set("since", flags.since);
7420
- const path = "/api/v1/me/notifications" + (params.toString() ? `?${params.toString()}` : "");
7421
- const ws = await request({
7422
- path,
7423
- token: secret
7424
- });
7425
- if (flags.json) {
7426
- console.log(JSON.stringify(ws, null, 2));
7427
- return EXIT.SUCCESS;
7428
- }
7429
- const headerLabel = flags.all ? "@-mentions (all)" : "@-mentions (unread)";
7430
- render.section(`${clay(headerLabel)} — ${ws.totals.total} ${dim(ws.totals.unread > 0 ? `· ${ws.totals.unread} unread` : "· nothing pending")}`);
7431
- if (ws.notifications.length === 0) {
7432
- process.stdout.write(dim(flags.all ? ` no @-mentions in window
7433
- ` : ` inbox zero — nothing waiting
7434
- `));
7435
- return EXIT.SUCCESS;
7436
- }
7437
- const slugWidth = Math.max(...ws.notifications.map((n) => n.meshSlug.length), 6);
7438
- for (const n of ws.notifications) {
7439
- const slug = dim(n.meshSlug.padEnd(slugWidth));
7440
- const topic = cyan(`#${n.topicName}`);
7441
- const sender = n.senderName ? `from ${n.senderName}` : "from ?";
7442
- const ago = formatRelativeTime(n.createdAt);
7443
- const dot = n.read ? dim("·") : yellow("●");
7444
- const snippet = n.snippet ?? (n.ciphertext ? dim("[encrypted]") : dim("[empty]"));
7445
- process.stdout.write(` ${dot} ${slug} ${topic} ${dim(sender)} ${dim(ago)}
7446
- ` + ` ${snippet.length > 200 ? snippet.slice(0, 200) + "…" : snippet}
7447
- `);
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));
7448
7558
  }
7449
- 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));
7450
7570
  });
7571
+ return result;
7451
7572
  }
7452
- async function runMeActivity(flags) {
7453
- return withRestKey({
7454
- meshSlug: resolveMeshForMint(flags.mesh),
7455
- purpose: "workspace-activity",
7456
- capabilities: ["read"]
7457
- }, async ({ secret }) => {
7458
- const params = new URLSearchParams;
7459
- if (flags.since)
7460
- params.set("since", flags.since);
7461
- const path = "/api/v1/me/activity" + (params.toString() ? `?${params.toString()}` : "");
7462
- const ws = await request({
7463
- path,
7464
- token: secret
7465
- });
7466
- if (flags.json) {
7467
- console.log(JSON.stringify(ws, null, 2));
7468
- return EXIT.SUCCESS;
7469
- }
7470
- render.section(`${clay("activity")} ${ws.totals.events} ${dim(flags.since ? `since ${flags.since}` : "in the last 24h")}`);
7471
- if (ws.activity.length === 0) {
7472
- process.stdout.write(dim(` quiet no activity in window
7473
- `));
7474
- return EXIT.SUCCESS;
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)}`);
7475
7623
  }
7476
- const slugWidth = Math.max(...ws.activity.map((a) => a.meshSlug.length), 6);
7477
- for (const a of ws.activity) {
7478
- const slug = dim(a.meshSlug.padEnd(slugWidth));
7479
- const topic = cyan(`#${a.topicName}`);
7480
- const sender = a.senderName ?? "?";
7481
- const ago = formatRelativeTime(a.createdAt);
7482
- const snippet = a.snippet ?? (a.ciphertext ? dim("[encrypted]") : dim("[empty]"));
7483
- process.stdout.write(` ${slug} ${topic} ${dim(sender + " ·")} ${dim(ago)}
7484
- ` + ` ${snippet.length > 200 ? snippet.slice(0, 200) + "…" : snippet}
7624
+ }
7625
+ if (wantsJson) {
7626
+ process.stdout.write(JSON.stringify(slugs.length === 1 ? allJson[0]?.peers : allJson, null, 2) + `
7485
7627
  `);
7486
- }
7487
- return EXIT.SUCCESS;
7488
- });
7628
+ }
7489
7629
  }
7490
- async function runMeSearch(flags) {
7491
- if (!flags.query || flags.query.length < 2) {
7492
- process.stderr.write(`Usage: claudemesh me search <query> (min 2 chars)
7493
- `);
7494
- return EXIT.INVALID_ARGS;
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);
7495
7651
  }
7496
- return withRestKey({
7497
- meshSlug: resolveMeshForMint(flags.mesh),
7498
- purpose: "workspace-search",
7499
- capabilities: ["read"]
7500
- }, async ({ secret }) => {
7501
- const params = new URLSearchParams({ q: flags.query });
7502
- const ws = await request({
7503
- path: `/api/v1/me/search?${params.toString()}`,
7504
- token: secret
7505
- });
7506
- if (flags.json) {
7507
- console.log(JSON.stringify(ws, null, 2));
7508
- return EXIT.SUCCESS;
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);
7509
7661
  }
7510
- 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"}`)}`);
7511
- if (ws.topics.length === 0 && ws.messages.length === 0) {
7512
- process.stdout.write(dim(` no matches
7513
- `));
7514
- 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);
7515
7678
  }
7516
- if (ws.topics.length > 0) {
7517
- process.stdout.write(dim(`
7518
- topics
7519
- `));
7520
- const slugWidth = Math.max(...ws.topics.map((t) => t.meshSlug.length), 6);
7521
- for (const t of ws.topics) {
7522
- const slug = dim(t.meshSlug.padEnd(slugWidth));
7523
- const name = cyan(`#${t.name}`);
7524
- const desc = t.description ? dim(` — ${t.description}`) : "";
7525
- process.stdout.write(` ${slug} ${name}${desc}
7526
- `);
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;
7691
+ }
7692
+ if (flags.json) {
7693
+ console.log(JSON.stringify({ ok: false, error: bridged.error }));
7694
+ } else {
7695
+ render.err(`send failed: ${bridged.error}`);
7527
7696
  }
7697
+ process.exit(1);
7528
7698
  }
7529
- if (ws.messages.length > 0) {
7530
- process.stdout.write(dim(`
7531
- messages
7532
- `));
7533
- const slugWidth = Math.max(...ws.messages.map((m) => m.meshSlug.length), 6);
7534
- for (const m of ws.messages) {
7535
- const slug = dim(m.meshSlug.padEnd(slugWidth));
7536
- const topic = cyan(`#${m.topicName}`);
7537
- const sender = m.senderName;
7538
- const ago = formatRelativeTime(m.createdAt);
7539
- const snippet = m.snippet ?? (m.bodyVersion === 2 ? dim("[encrypted — open the topic to decrypt]") : dim("[empty]"));
7540
- const highlighted = m.snippet ? highlightMatch(snippet, flags.query) : snippet;
7541
- process.stdout.write(` ${slug} ${topic} ${dim(sender + " ·")} ${dim(ago)}
7542
- ` + ` ${highlighted}
7543
- `);
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);
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"}`);
7544
7734
  }
7735
+ process.exit(1);
7545
7736
  }
7546
- return EXIT.SUCCESS;
7547
7737
  });
7548
7738
  }
7549
- function highlightMatch(text, query) {
7550
- if (!query)
7551
- return text;
7552
- const idx = text.toLowerCase().indexOf(query.toLowerCase());
7553
- if (idx === -1)
7554
- return text;
7555
- const before = text.slice(0, idx);
7556
- const match = text.slice(idx, idx + query.length);
7557
- const after = text.slice(idx + query.length);
7558
- 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}`;
7559
7760
  }
7560
- async function runMeTasks(flags) {
7561
- return withRestKey({
7562
- meshSlug: resolveMeshForMint(flags.mesh),
7563
- purpose: "workspace-tasks",
7564
- capabilities: ["read"]
7565
- }, async ({ secret }) => {
7566
- const params = new URLSearchParams;
7567
- if (flags.status)
7568
- params.set("status", flags.status);
7569
- const path = "/api/v1/me/tasks" + (params.toString() ? `?${params.toString()}` : "");
7570
- const ws = await request({
7571
- path,
7572
- token: secret
7573
- });
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();
7574
7766
  if (flags.json) {
7575
- console.log(JSON.stringify(ws, null, 2));
7576
- return EXIT.SUCCESS;
7767
+ process.stdout.write(JSON.stringify(messages, null, 2) + `
7768
+ `);
7769
+ return;
7577
7770
  }
7578
- render.section(`${clay("tasks")} — ${dim(`${ws.totals.open} open · ${ws.totals.claimed} in-flight · ${ws.totals.completed} done`)}`);
7579
- if (ws.tasks.length === 0) {
7580
- process.stdout.write(dim(` no tasks in window
7581
- `));
7582
- return EXIT.SUCCESS;
7771
+ if (messages.length === 0) {
7772
+ render.info(dim(`No messages on mesh "${mesh.slug}".`));
7773
+ return;
7583
7774
  }
7584
- const slugWidth = Math.max(...ws.tasks.map((t) => t.meshSlug.length), 6);
7585
- for (const t of ws.tasks) {
7586
- const slug = dim(t.meshSlug.padEnd(slugWidth));
7587
- const status = t.status === "open" ? yellow("open ") : t.status === "claimed" ? cyan("working ") : green("done ");
7588
- const prio = t.priority === "urgent" ? yellow("!") : t.priority === "low" ? dim("·") : " ";
7589
- const claimer = t.claimedByName ? dim(` ← ${t.claimedByName}`) : "";
7590
- 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
+
7591
7779
  `);
7592
7780
  }
7593
- return EXIT.SUCCESS;
7594
7781
  });
7595
7782
  }
7596
- async function runMeState(flags) {
7597
- return withRestKey({
7598
- meshSlug: resolveMeshForMint(flags.mesh),
7599
- purpose: "workspace-state",
7600
- capabilities: ["read"]
7601
- }, async ({ secret }) => {
7602
- const params = new URLSearchParams;
7603
- if (flags.key)
7604
- params.set("key", flags.key);
7605
- const path = "/api/v1/me/state" + (params.toString() ? `?${params.toString()}` : "");
7606
- const ws = await request({
7607
- path,
7608
- token: secret
7609
- });
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
+ }
7610
7803
  if (flags.json) {
7611
- console.log(JSON.stringify(ws, null, 2));
7612
- return EXIT.SUCCESS;
7804
+ console.log(JSON.stringify(daemonEntry, null, 2));
7805
+ return;
7613
7806
  }
7614
- 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"}`)}`);
7615
- if (ws.entries.length === 0) {
7616
- process.stdout.write(dim(` no state entries
7617
- `));
7618
- 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;
7619
7817
  }
7620
- const slugWidth = Math.max(...ws.entries.map((e) => e.meshSlug.length), 6);
7621
- const keyWidth = Math.max(...ws.entries.map((e) => e.key.length), 8);
7622
- for (const e of ws.entries) {
7623
- const slug = dim(e.meshSlug.padEnd(slugWidth));
7624
- const key = cyan(e.key.padEnd(keyWidth));
7625
- const valueStr = typeof e.value === "string" ? e.value : JSON.stringify(e.value);
7626
- const trimmed = valueStr.length > 80 ? valueStr.slice(0, 80) + "…" : valueStr;
7627
- const ago = dim(formatRelativeTime(e.updatedAt));
7628
- process.stdout.write(` ${slug} ${key} ${trimmed} ${ago}
7629
- `);
7818
+ if (flags.json) {
7819
+ console.log(JSON.stringify(entry, null, 2));
7820
+ return;
7630
7821
  }
7631
- 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()}`));
7632
7825
  });
7633
7826
  }
7634
- async function runMeMemory(flags) {
7635
- return withRestKey({
7636
- meshSlug: resolveMeshForMint(flags.mesh),
7637
- purpose: "workspace-memory",
7638
- capabilities: ["read"]
7639
- }, async ({ secret }) => {
7640
- const params = new URLSearchParams;
7641
- if (flags.query)
7642
- params.set("q", flags.query);
7643
- const path = "/api/v1/me/memory" + (params.toString() ? `?${params.toString()}` : "");
7644
- const ws = await request({
7645
- path,
7646
- token: secret
7647
- });
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) {
7648
7847
  if (flags.json) {
7649
- console.log(JSON.stringify(ws, null, 2));
7650
- return EXIT.SUCCESS;
7848
+ console.log(JSON.stringify(daemonRows, null, 2));
7849
+ return;
7651
7850
  }
7652
- const headerLabel = flags.query ? `recall — "${flags.query}"` : "recall — last 30 days";
7653
- render.section(`${clay(headerLabel)} ${dim(`${ws.totals.entries} match${ws.totals.entries === 1 ? "" : "es"}`)}`);
7654
- if (ws.memories.length === 0) {
7655
- process.stdout.write(dim(` no memories
7656
- `));
7657
- return EXIT.SUCCESS;
7851
+ if (daemonRows.length === 0) {
7852
+ render.info(dim("(no state)"));
7853
+ return;
7658
7854
  }
7659
- const slugWidth = Math.max(...ws.memories.map((m) => m.meshSlug.length), 6);
7660
- for (const m of ws.memories) {
7661
- const slug = dim(m.meshSlug.padEnd(slugWidth));
7662
- const ago = dim(formatRelativeTime(m.rememberedAt));
7663
- const tags = m.tags.length > 0 ? " " + dim("[" + m.tags.join(", ") + "]") : "";
7664
- const content = m.content.length > 240 ? m.content.slice(0, 240) + "" : m.content;
7665
- process.stdout.write(` ${slug} ${ago}${tags}
7666
- ${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())}
7667
7861
  `);
7668
7862
  }
7669
- return EXIT.SUCCESS;
7670
- });
7671
- }
7672
- function formatRelativeTime(iso) {
7673
- const then = new Date(iso).getTime();
7674
- const now = Date.now();
7675
- const sec = Math.max(0, Math.floor((now - then) / 1000));
7676
- if (sec < 60)
7677
- return `${sec}s ago`;
7678
- if (sec < 3600)
7679
- return `${Math.floor(sec / 60)}m ago`;
7680
- if (sec < 86400)
7681
- return `${Math.floor(sec / 3600)}h ago`;
7682
- if (sec < 86400 * 30)
7683
- return `${Math.floor(sec / 86400)}d ago`;
7684
- if (sec < 86400 * 365)
7685
- return `${Math.floor(sec / (86400 * 30))}mo ago`;
7686
- return `${Math.floor(sec / (86400 * 365))}y ago`;
7687
- }
7688
- var init_me = __esm(() => {
7689
- init_with_rest_key();
7690
- init_client();
7691
- init_facade();
7692
- init_render();
7693
- init_styles();
7694
- init_exit_codes();
7695
- });
7696
-
7697
- // src/commands/info.ts
7698
- var exports_info = {};
7699
- __export(exports_info, {
7700
- runInfo: () => runInfo
7701
- });
7702
- async function runInfo(flags) {
7703
- const config = readConfig();
7863
+ return;
7864
+ }
7704
7865
  await withMesh({ meshSlug: flags.mesh ?? null }, async (client, mesh) => {
7705
- const [brokerInfo, peers, state] = await Promise.all([
7706
- client.meshInfo(),
7707
- client.listPeers(),
7708
- client.listState()
7709
- ]);
7710
- const output = {
7711
- slug: mesh.slug,
7712
- meshId: mesh.meshId,
7713
- memberId: mesh.memberId,
7714
- brokerUrl: mesh.brokerUrl,
7715
- displayName: config.displayName ?? null,
7716
- peerCount: peers.length,
7717
- stateCount: state.length,
7718
- ...brokerInfo ?? {}
7719
- };
7866
+ const entries = await client.listState();
7720
7867
  if (flags.json) {
7721
- process.stdout.write(JSON.stringify(output, null, 2) + `
7722
- `);
7868
+ console.log(JSON.stringify(entries, null, 2));
7723
7869
  return;
7724
7870
  }
7725
- render.section(`${mesh.slug} · ${mesh.brokerUrl}`);
7726
- render.kv([
7727
- ["mesh", mesh.meshId],
7728
- ["member", mesh.memberId],
7729
- ["peers", `${peers.length} connected`],
7730
- ["state", `${state.length} keys`]
7731
- ]);
7732
- if (brokerInfo && typeof brokerInfo === "object") {
7733
- const extras = [];
7734
- for (const [k, v] of Object.entries(brokerInfo)) {
7735
- if (["slug", "meshId", "brokerUrl"].includes(k))
7736
- continue;
7737
- extras.push([k, JSON.stringify(v)]);
7738
- }
7739
- if (extras.length)
7740
- 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
+ `);
7741
7882
  }
7742
7883
  });
7743
7884
  }
7744
- var init_info2 = __esm(() => {
7885
+ var init_state = __esm(() => {
7745
7886
  init_connect();
7746
- init_facade();
7887
+ init_daemon_route();
7747
7888
  init_render();
7889
+ init_styles();
7748
7890
  });
7749
7891
 
7750
7892
  // src/commands/remember.ts
@@ -7758,6 +7900,15 @@ async function remember(content, opts = {}) {
7758
7900
  return EXIT.INVALID_ARGS;
7759
7901
  }
7760
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
+ }
7761
7912
  return await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
7762
7913
  const id = await client.remember(content, tags);
7763
7914
  if (opts.json) {
@@ -7774,6 +7925,7 @@ async function remember(content, opts = {}) {
7774
7925
  }
7775
7926
  var init_remember = __esm(() => {
7776
7927
  init_connect();
7928
+ init_daemon_route();
7777
7929
  init_render();
7778
7930
  init_styles();
7779
7931
  init_exit_codes();
@@ -7789,6 +7941,29 @@ async function recall(query, opts = {}) {
7789
7941
  render.err("Usage: claudemesh recall <query>");
7790
7942
  return EXIT.INVALID_ARGS;
7791
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
+ }
7792
7967
  return await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
7793
7968
  const memories = await client.recall(query);
7794
7969
  if (opts.json) {
@@ -7815,6 +7990,7 @@ async function recall(query, opts = {}) {
7815
7990
  }
7816
7991
  var init_recall = __esm(() => {
7817
7992
  init_connect();
7993
+ init_daemon_route();
7818
7994
  init_render();
7819
7995
  init_styles();
7820
7996
  init_exit_codes();
@@ -8027,6 +8203,14 @@ async function runForget(id, opts) {
8027
8203
  render.err("Usage: claudemesh forget <memory-id>");
8028
8204
  return EXIT.INVALID_ARGS;
8029
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
+ }
8030
8214
  await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
8031
8215
  await client.forget(id);
8032
8216
  });
@@ -8204,6 +8388,7 @@ var init_broker_actions = __esm(() => {
8204
8388
  init_connect();
8205
8389
  init_facade();
8206
8390
  init_client3();
8391
+ init_daemon_route();
8207
8392
  init_render();
8208
8393
  init_styles();
8209
8394
  init_exit_codes();
@@ -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;
@@ -9045,6 +9230,157 @@ function makeHandler(opts) {
9045
9230
  }
9046
9231
  return;
9047
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) {
9301
+ respond(res, 503, { error: "broker not initialised" });
9302
+ return;
9303
+ }
9304
+ const query = url.searchParams.get("q") ?? "";
9305
+ const filterMesh = url.searchParams.get("mesh") ?? undefined;
9306
+ try {
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 });
9316
+ } catch (e) {
9317
+ respond(res, 502, { error: "broker_unreachable", detail: String(e) });
9318
+ }
9319
+ return;
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
+ }
9048
9384
  if (req.method === "GET" && url.pathname === "/v1/skills") {
9049
9385
  if (!opts.brokers || opts.brokers.size === 0) {
9050
9386
  respond(res, 503, { error: "broker not initialised" });
@@ -9462,6 +9798,10 @@ class DaemonBrokerClient {
9462
9798
  peerListResolvers = new Map;
9463
9799
  skillListResolvers = new Map;
9464
9800
  skillDataResolvers = new Map;
9801
+ stateGetResolvers = new Map;
9802
+ stateListResolvers = new Map;
9803
+ memoryStoreResolvers = new Map;
9804
+ memoryRecallResolvers = new Map;
9465
9805
  sessionPubkey = null;
9466
9806
  sessionSecretKey = null;
9467
9807
  opens = [];
@@ -9602,6 +9942,46 @@ class DaemonBrokerClient {
9602
9942
  }
9603
9943
  return;
9604
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
+ }
9605
9985
  if (msg.type === "push" || msg.type === "inbound") {
9606
9986
  this.opts.onPush?.(msg);
9607
9987
  return;
@@ -9722,6 +10102,96 @@ class DaemonBrokerClient {
9722
10102
  }
9723
10103
  });
9724
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
+ }
9725
10195
  setProfile(profile) {
9726
10196
  if (this._status !== "open" || !this.ws)
9727
10197
  return;
@@ -17543,7 +18013,7 @@ USAGE
17543
18013
  claudemesh <invite-url> join a mesh, then launch
17544
18014
  claudemesh launch --name <n> --join <url> join + launch in one step
17545
18015
 
17546
- Mesh
18016
+ Mesh (alias: "workspace" — claudemesh workspace <verb> mirrors each)
17547
18017
  claudemesh create <name> create a new mesh
17548
18018
  claudemesh join <url> join a mesh (accepts short /i/ or long /join/ link)
17549
18019
  claudemesh launch [slug] launch Claude Code on a mesh (alias: connect)
@@ -17817,6 +18287,47 @@ async function main() {
17817
18287
  process.exit(await invite2(positionals[0], { mesh: flags.mesh, json: !!flags.json }));
17818
18288
  break;
17819
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
+ }
17820
18331
  case "disconnect": {
17821
18332
  const { runDisconnect: runDisconnect2 } = await Promise.resolve().then(() => (init_kick(), exports_kick));
17822
18333
  process.exit(await runDisconnect2(positionals[0], { mesh: flags.mesh, stale: flags.stale, all: !!flags.all }));
@@ -18662,4 +19173,4 @@ main().catch((err) => {
18662
19173
  process.exit(EXIT.INTERNAL_ERROR);
18663
19174
  });
18664
19175
 
18665
- //# debugId=3BADD117052552A564756E2164756E21
19176
+ //# debugId=5CE8252722B33A8D64756E2164756E21