claudemesh-cli 1.31.5 → 1.32.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.
@@ -104,7 +104,7 @@ __export(exports_urls, {
104
104
  VERSION: () => VERSION,
105
105
  URLS: () => URLS
106
106
  });
107
- var URLS, VERSION = "1.31.5", env;
107
+ var URLS, VERSION = "1.32.0", env;
108
108
  var init_urls = __esm(() => {
109
109
  URLS = {
110
110
  BROKER: process.env.CLAUDEMESH_BROKER_URL ?? "wss://ic.claudemesh.com/ws",
@@ -3966,6 +3966,252 @@ var init_lifecycle = __esm(() => {
3966
3966
  init_paths2();
3967
3967
  });
3968
3968
 
3969
+ // src/ui/warnings.ts
3970
+ function warnDaemonState(res, opts = {}) {
3971
+ if (alreadyWarned)
3972
+ return false;
3973
+ if (opts.quiet || opts.json)
3974
+ return false;
3975
+ if (res.state === "up")
3976
+ return false;
3977
+ if (getDaemonPolicy().mode === "strict" && res.state !== "started")
3978
+ return false;
3979
+ alreadyWarned = true;
3980
+ const tag = (label) => `[claudemesh] ${label}`;
3981
+ const hint = (s) => dim(s);
3982
+ switch (res.state) {
3983
+ case "started":
3984
+ process.stderr.write(`${tag("info")} daemon restarted automatically ${hint(`(took ${res.durationMs}ms)`)}
3985
+ `);
3986
+ return true;
3987
+ case "down":
3988
+ process.stderr.write(`${tag("info")} daemon not running — using cold path ${hint("(slower; run `claudemesh daemon up` for warm path)")}
3989
+ `);
3990
+ return true;
3991
+ case "spawn-suppressed":
3992
+ process.stderr.write(`${tag("warn")} ${res.reason ?? "daemon failed to start recently"} — using cold path ${hint("(run `claudemesh doctor`)")}
3993
+ `);
3994
+ return true;
3995
+ case "spawn-failed":
3996
+ process.stderr.write(`${tag("warn")} daemon spawn failed${res.reason ? `: ${res.reason}` : ""} — using cold path ${hint("(check ~/.claudemesh/daemon/daemon.log)")}
3997
+ `);
3998
+ return true;
3999
+ case "service-not-ready":
4000
+ process.stderr.write(`${tag("warn")} ${res.reason ?? "service-managed daemon not responding"} — using cold path ${hint("(check ~/.claudemesh/daemon/daemon.log)")}
4001
+ `);
4002
+ return true;
4003
+ }
4004
+ return false;
4005
+ }
4006
+ var alreadyWarned = false;
4007
+ var init_warnings = __esm(() => {
4008
+ init_policy();
4009
+ init_styles();
4010
+ });
4011
+
4012
+ // src/services/bridge/daemon-route.ts
4013
+ var exports_daemon_route = {};
4014
+ __export(exports_daemon_route, {
4015
+ trySetStateViaDaemon: () => trySetStateViaDaemon,
4016
+ trySendViaDaemon: () => trySendViaDaemon,
4017
+ tryRememberViaDaemon: () => tryRememberViaDaemon,
4018
+ tryRecallViaDaemon: () => tryRecallViaDaemon,
4019
+ tryListStateViaDaemon: () => tryListStateViaDaemon,
4020
+ tryListSkillsViaDaemon: () => tryListSkillsViaDaemon,
4021
+ tryListPeersViaDaemon: () => tryListPeersViaDaemon,
4022
+ tryGetStateViaDaemon: () => tryGetStateViaDaemon,
4023
+ tryGetSkillViaDaemon: () => tryGetSkillViaDaemon,
4024
+ tryForgetViaDaemon: () => tryForgetViaDaemon
4025
+ });
4026
+ function meshQuery(mesh) {
4027
+ return mesh ? `?mesh=${encodeURIComponent(mesh)}` : "";
4028
+ }
4029
+ async function daemonReachable() {
4030
+ const policy2 = getDaemonPolicy();
4031
+ if (policy2.mode === "no-daemon")
4032
+ return false;
4033
+ const res = await ensureDaemonReady({ noAutoSpawn: false });
4034
+ warnDaemonState(res, {});
4035
+ return res.state === "up" || res.state === "started";
4036
+ }
4037
+ async function tryListPeersViaDaemon(mesh) {
4038
+ if (!await daemonReachable())
4039
+ return null;
4040
+ try {
4041
+ const res = await ipc({ path: `/v1/peers${meshQuery(mesh)}`, timeoutMs: 3000 });
4042
+ if (res.status !== 200)
4043
+ return null;
4044
+ return Array.isArray(res.body.peers) ? res.body.peers : [];
4045
+ } catch (err) {
4046
+ const msg = String(err);
4047
+ if (/ENOENT|ECONNREFUSED|ipc_timeout/.test(msg))
4048
+ return null;
4049
+ return null;
4050
+ }
4051
+ }
4052
+ async function tryListSkillsViaDaemon(mesh) {
4053
+ if (!await daemonReachable())
4054
+ return null;
4055
+ try {
4056
+ const res = await ipc({ path: `/v1/skills${meshQuery(mesh)}`, timeoutMs: 3000 });
4057
+ if (res.status !== 200)
4058
+ return null;
4059
+ return Array.isArray(res.body.skills) ? res.body.skills : [];
4060
+ } catch (err) {
4061
+ const msg = String(err);
4062
+ if (/ENOENT|ECONNREFUSED|ipc_timeout/.test(msg))
4063
+ return null;
4064
+ return null;
4065
+ }
4066
+ }
4067
+ async function tryGetSkillViaDaemon(name, mesh) {
4068
+ if (!await daemonReachable())
4069
+ return null;
4070
+ try {
4071
+ const res = await ipc({
4072
+ path: `/v1/skills/${encodeURIComponent(name)}${meshQuery(mesh)}`,
4073
+ timeoutMs: 3000
4074
+ });
4075
+ if (res.status === 404)
4076
+ return null;
4077
+ if (res.status !== 200)
4078
+ return null;
4079
+ return res.body.skill ?? null;
4080
+ } catch {
4081
+ return null;
4082
+ }
4083
+ }
4084
+ async function tryGetStateViaDaemon(key, mesh) {
4085
+ if (!await daemonReachable())
4086
+ return null;
4087
+ try {
4088
+ const path = `/v1/state?key=${encodeURIComponent(key)}${mesh ? `&mesh=${encodeURIComponent(mesh)}` : ""}`;
4089
+ const res = await ipc({ path, timeoutMs: 3000 });
4090
+ if (res.status === 404)
4091
+ return;
4092
+ if (res.status !== 200)
4093
+ return null;
4094
+ return res.body.state ?? undefined;
4095
+ } catch (err) {
4096
+ const msg = String(err);
4097
+ if (/ENOENT|ECONNREFUSED|ipc_timeout/.test(msg))
4098
+ return null;
4099
+ return null;
4100
+ }
4101
+ }
4102
+ async function tryListStateViaDaemon(mesh) {
4103
+ if (!await daemonReachable())
4104
+ return null;
4105
+ try {
4106
+ const res = await ipc({ path: `/v1/state${meshQuery(mesh)}`, timeoutMs: 3000 });
4107
+ if (res.status !== 200)
4108
+ return null;
4109
+ return Array.isArray(res.body.entries) ? res.body.entries : [];
4110
+ } catch (err) {
4111
+ const msg = String(err);
4112
+ if (/ENOENT|ECONNREFUSED|ipc_timeout/.test(msg))
4113
+ return null;
4114
+ return null;
4115
+ }
4116
+ }
4117
+ async function trySetStateViaDaemon(key, value, mesh) {
4118
+ if (!await daemonReachable())
4119
+ return false;
4120
+ try {
4121
+ const res = await ipc({
4122
+ method: "POST",
4123
+ path: "/v1/state",
4124
+ timeoutMs: 3000,
4125
+ body: { key, value, ...mesh ? { mesh } : {} }
4126
+ });
4127
+ return res.status === 200 && res.body.ok === true;
4128
+ } catch {
4129
+ return false;
4130
+ }
4131
+ }
4132
+ async function tryRememberViaDaemon(content, tags, mesh) {
4133
+ if (!await daemonReachable())
4134
+ return null;
4135
+ try {
4136
+ const res = await ipc({
4137
+ method: "POST",
4138
+ path: "/v1/memory",
4139
+ timeoutMs: 5000,
4140
+ body: { content, ...tags?.length ? { tags } : {}, ...mesh ? { mesh } : {} }
4141
+ });
4142
+ if (res.status !== 200 || !res.body.id)
4143
+ return null;
4144
+ return { id: res.body.id, mesh: res.body.mesh };
4145
+ } catch {
4146
+ return null;
4147
+ }
4148
+ }
4149
+ async function tryRecallViaDaemon(query, mesh) {
4150
+ if (!await daemonReachable())
4151
+ return null;
4152
+ try {
4153
+ const path = `/v1/memory?q=${encodeURIComponent(query)}${mesh ? `&mesh=${encodeURIComponent(mesh)}` : ""}`;
4154
+ const res = await ipc({ path, timeoutMs: 5000 });
4155
+ if (res.status !== 200)
4156
+ return null;
4157
+ return Array.isArray(res.body.matches) ? res.body.matches : [];
4158
+ } catch (err) {
4159
+ const msg = String(err);
4160
+ if (/ENOENT|ECONNREFUSED|ipc_timeout/.test(msg))
4161
+ return null;
4162
+ return null;
4163
+ }
4164
+ }
4165
+ async function tryForgetViaDaemon(id, mesh) {
4166
+ if (!await daemonReachable())
4167
+ return false;
4168
+ try {
4169
+ const path = `/v1/memory/${encodeURIComponent(id)}${meshQuery(mesh)}`;
4170
+ const res = await ipc({ method: "DELETE", path, timeoutMs: 3000 });
4171
+ return res.status === 200 && res.body.ok === true;
4172
+ } catch {
4173
+ return false;
4174
+ }
4175
+ }
4176
+ async function trySendViaDaemon(args) {
4177
+ if (!await daemonReachable())
4178
+ return null;
4179
+ try {
4180
+ const res = await ipc({
4181
+ method: "POST",
4182
+ path: "/v1/send",
4183
+ timeoutMs: 3000,
4184
+ body: {
4185
+ to: args.to,
4186
+ message: args.message,
4187
+ priority: args.priority,
4188
+ ...args.idempotencyKey ? { client_message_id: args.idempotencyKey } : {},
4189
+ ...args.expectedMesh ? { mesh: args.expectedMesh } : {}
4190
+ }
4191
+ });
4192
+ if (res.status === 202 || res.status === 200) {
4193
+ return {
4194
+ ok: true,
4195
+ messageId: res.body.broker_message_id ?? res.body.client_message_id ?? "",
4196
+ duplicate: res.body.duplicate,
4197
+ status: res.body.status
4198
+ };
4199
+ }
4200
+ return { ok: false, error: res.body.error ?? `daemon http ${res.status}` };
4201
+ } catch (err) {
4202
+ const msg = String(err);
4203
+ if (/ENOENT|ECONNREFUSED|ipc_timeout/.test(msg))
4204
+ return null;
4205
+ return { ok: false, error: msg };
4206
+ }
4207
+ }
4208
+ var init_daemon_route = __esm(() => {
4209
+ init_client3();
4210
+ init_lifecycle();
4211
+ init_policy();
4212
+ init_warnings();
4213
+ });
4214
+
3969
4215
  // src/services/broker/session-hello-sig.ts
3970
4216
  var exports_session_hello_sig = {};
3971
4217
  __export(exports_session_hello_sig, {
@@ -4166,18 +4412,61 @@ async function runLaunchWizard(opts) {
4166
4412
  exitFullScreen();
4167
4413
  return { mesh, role, groups, messageMode, skipPermissions };
4168
4414
  }
4169
- function printBanner(name, meshSlug, role, groups, messageMode) {
4415
+ async function printBrokerWelcome(meshSlug) {
4170
4416
  const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
4171
4417
  const dim2 = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
4172
- const bold2 = (s) => useColor ? `\x1B[1m${s}\x1B[22m` : s;
4173
- const roleSuffix = role ? ` (${role})` : "";
4174
- const groupTags = groups.length ? " [" + groups.map((g) => `@${g.name}${g.role ? `:${g.role}` : ""}`).join(", ") + "]" : "";
4175
- const rule = "─".repeat(60);
4176
- console.log(bold2(`claudemesh launch`) + dim2(` as ${name}${roleSuffix} on ${meshSlug}${groupTags} [${messageMode}]`));
4177
- console.log(rule);
4178
- if (messageMode === "push") {
4179
- console.log("Peer messages arrive as <channel> reminders in real-time.");
4180
- } else if (messageMode === "inbox") {
4418
+ const green3 = (s) => useColor ? `\x1B[32m${s}\x1B[22m` : s;
4419
+ const yellow2 = (s) => useColor ? `\x1B[33m${s}\x1B[22m` : s;
4420
+ let brokerState = "unknown";
4421
+ try {
4422
+ const { ipc: ipc2 } = await Promise.resolve().then(() => (init_client3(), exports_client));
4423
+ const res = await ipc2({
4424
+ path: "/v1/health",
4425
+ timeoutMs: 1500
4426
+ });
4427
+ if (res.status === 200 && res.body?.brokers) {
4428
+ brokerState = res.body.brokers[meshSlug] ?? "unknown";
4429
+ }
4430
+ } catch {}
4431
+ let peerCount = -1;
4432
+ try {
4433
+ const { tryListPeersViaDaemon: tryListPeersViaDaemon2 } = await Promise.resolve().then(() => (init_daemon_route(), exports_daemon_route));
4434
+ const peers = await tryListPeersViaDaemon2() ?? [];
4435
+ peerCount = peers.filter((p) => p.channel !== "claudemesh-daemon").length;
4436
+ } catch {}
4437
+ let unread = -1;
4438
+ try {
4439
+ const { ipc: ipc2 } = await Promise.resolve().then(() => (init_client3(), exports_client));
4440
+ const res = await ipc2({
4441
+ path: "/v1/inbox",
4442
+ timeoutMs: 1500
4443
+ });
4444
+ if (res.status === 200 && Array.isArray(res.body?.messages)) {
4445
+ unread = res.body.messages.length;
4446
+ }
4447
+ } catch {}
4448
+ const dot = brokerState === "open" ? green3("●") : yellow2("●");
4449
+ const parts = [];
4450
+ parts.push(`broker ${brokerState === "open" ? "connected" : brokerState}`);
4451
+ if (peerCount >= 0)
4452
+ parts.push(`${peerCount} peer${peerCount === 1 ? "" : "s"} online`);
4453
+ if (unread >= 0)
4454
+ parts.push(`${unread} unread`);
4455
+ console.log(`${dot} ${parts.join(dim2(" · "))}`);
4456
+ console.log("");
4457
+ }
4458
+ function printBanner(name, meshSlug, role, groups, messageMode) {
4459
+ const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
4460
+ const dim2 = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
4461
+ const bold2 = (s) => useColor ? `\x1B[1m${s}\x1B[22m` : s;
4462
+ const roleSuffix = role ? ` (${role})` : "";
4463
+ const groupTags = groups.length ? " [" + groups.map((g) => `@${g.name}${g.role ? `:${g.role}` : ""}`).join(", ") + "]" : "";
4464
+ const rule = "─".repeat(60);
4465
+ console.log(bold2(`claudemesh launch`) + dim2(` — as ${name}${roleSuffix} on ${meshSlug}${groupTags} [${messageMode}]`));
4466
+ console.log(rule);
4467
+ if (messageMode === "push") {
4468
+ console.log("Peer messages arrive as <channel> reminders in real-time.");
4469
+ } else if (messageMode === "inbox") {
4181
4470
  console.log("Peer messages held in inbox. Use check_messages to read.");
4182
4471
  } else {
4183
4472
  console.log("Messages off. Use check_messages to poll manually.");
@@ -4456,6 +4745,7 @@ async function runLaunch(flags, rawArgs) {
4456
4745
  } catch {}
4457
4746
  if (!args.quiet) {
4458
4747
  printBanner(displayName, mesh.slug, role, parsedGroups, messageMode);
4748
+ await printBrokerWelcome(mesh.slug);
4459
4749
  }
4460
4750
  const meshMcpEntries = [];
4461
4751
  if (serviceCatalog.length > 0) {
@@ -7426,400 +7716,154 @@ function parseStaleMs(input) {
7426
7716
  return null;
7427
7717
  }
7428
7718
  function buildPayload(kind, target, opts) {
7429
- if (opts.all)
7430
- return { type: kind, all: true };
7431
- if (opts.stale) {
7432
- const ms = parseStaleMs(opts.stale);
7433
- if (!ms)
7434
- return { error: `Invalid stale duration: "${opts.stale}". Use e.g. 30m, 1h, 300s.` };
7435
- return { type: kind, stale: ms };
7436
- }
7437
- if (target)
7438
- return { type: kind, target };
7439
- return { error: `Usage: claudemesh ${kind} <peer> | --stale 30m | --all` };
7440
- }
7441
- async function runDisconnect(target, opts = {}) {
7442
- const config = readConfig();
7443
- const meshSlug = opts.mesh ?? config.meshes[0]?.slug;
7444
- if (!meshSlug) {
7445
- render.err("No mesh joined.");
7446
- return EXIT.NOT_FOUND;
7447
- }
7448
- const built = buildPayload("disconnect", target, opts);
7449
- if ("error" in built) {
7450
- render.err(String(built.error));
7451
- return EXIT.INVALID_ARGS;
7452
- }
7453
- return await withMesh({ meshSlug }, async (client) => {
7454
- const result = await client.sendAndWait(built);
7455
- const peers = result?.affected ?? result?.kicked ?? [];
7456
- if (peers.length === 0)
7457
- render.info("No peers matched.");
7458
- else {
7459
- render.ok(`Disconnected ${peers.length} peer(s): ${peers.join(", ")}`);
7460
- render.hint("They will auto-reconnect within seconds. For a session-ending kick, use `claudemesh kick`.");
7461
- }
7462
- return EXIT.SUCCESS;
7463
- });
7464
- }
7465
- async function runKick(target, opts = {}) {
7466
- const config = readConfig();
7467
- const meshSlug = opts.mesh ?? config.meshes[0]?.slug;
7468
- if (!meshSlug) {
7469
- render.err("No mesh joined.");
7470
- return EXIT.NOT_FOUND;
7471
- }
7472
- const built = buildPayload("kick", target, opts);
7473
- if ("error" in built) {
7474
- render.err(String(built.error));
7475
- return EXIT.INVALID_ARGS;
7476
- }
7477
- return await withMesh({ meshSlug }, async (client) => {
7478
- const result = await client.sendAndWait(built);
7479
- const peers = result?.affected ?? result?.kicked ?? [];
7480
- if (peers.length === 0)
7481
- render.info("No peers matched.");
7482
- else {
7483
- render.ok(`Kicked ${peers.length} peer(s): ${peers.join(", ")}`);
7484
- render.hint("Their Claude Code session ended. They can rejoin anytime by running `claudemesh`.");
7485
- }
7486
- return EXIT.SUCCESS;
7487
- });
7488
- }
7489
- var init_kick = __esm(() => {
7490
- init_connect();
7491
- init_facade();
7492
- init_render();
7493
- init_exit_codes();
7494
- });
7495
-
7496
- // src/commands/ban.ts
7497
- var exports_ban = {};
7498
- __export(exports_ban, {
7499
- runUnban: () => runUnban,
7500
- runBans: () => runBans,
7501
- runBan: () => runBan
7502
- });
7503
- async function runBan(target, opts = {}) {
7504
- if (!target) {
7505
- render.err("Usage: claudemesh ban <peer-name-or-pubkey>");
7506
- return EXIT.INVALID_ARGS;
7507
- }
7508
- const config = readConfig();
7509
- const meshSlug = opts.mesh ?? config.meshes[0]?.slug;
7510
- if (!meshSlug) {
7511
- render.err("No mesh joined.");
7512
- return EXIT.NOT_FOUND;
7513
- }
7514
- return await withMesh({ meshSlug }, async (client) => {
7515
- const result = await client.sendAndWait({ type: "ban", target });
7516
- if (result?.banned) {
7517
- render.ok(`Banned ${result.banned} from ${meshSlug}. They cannot reconnect until unbanned.`);
7518
- render.hint(`Undo: claudemesh unban ${result.banned} --mesh ${meshSlug}`);
7519
- } else {
7520
- render.err(result?.message ?? result?.error ?? result?.code ?? "ban failed");
7521
- }
7522
- return result?.banned ? EXIT.SUCCESS : EXIT.INTERNAL_ERROR;
7523
- });
7524
- }
7525
- async function runUnban(target, opts = {}) {
7526
- if (!target) {
7527
- render.err("Usage: claudemesh unban <peer-name-or-pubkey>");
7528
- return EXIT.INVALID_ARGS;
7529
- }
7530
- const config = readConfig();
7531
- const meshSlug = opts.mesh ?? config.meshes[0]?.slug;
7532
- if (!meshSlug) {
7533
- render.err("No mesh joined.");
7534
- return EXIT.NOT_FOUND;
7535
- }
7536
- return await withMesh({ meshSlug }, async (client) => {
7537
- const result = await client.sendAndWait({ type: "unban", target });
7538
- if (result?.unbanned) {
7539
- render.ok(`Unbanned ${result.unbanned} from ${meshSlug}. They can rejoin.`);
7540
- } else {
7541
- render.err(result?.message ?? result?.error ?? result?.code ?? "unban failed");
7542
- }
7543
- return result?.unbanned ? EXIT.SUCCESS : EXIT.INTERNAL_ERROR;
7544
- });
7545
- }
7546
- async function runBans(opts = {}) {
7547
- const config = readConfig();
7548
- const meshSlug = opts.mesh ?? config.meshes[0]?.slug;
7549
- if (!meshSlug) {
7550
- render.err("No mesh joined.");
7551
- return EXIT.NOT_FOUND;
7552
- }
7553
- return await withMesh({ meshSlug }, async (client) => {
7554
- const result = await client.sendAndWait({ type: "list_bans" });
7555
- const bans = result?.bans ?? [];
7556
- if (opts.json) {
7557
- process.stdout.write(JSON.stringify(bans, null, 2) + `
7558
- `);
7559
- return EXIT.SUCCESS;
7560
- }
7561
- if (bans.length === 0) {
7562
- render.info("No banned members.");
7563
- return EXIT.SUCCESS;
7564
- }
7565
- render.section(`banned members on ${meshSlug}`);
7566
- for (const b of bans) {
7567
- render.kv([[b.name, `${b.pubkey.slice(0, 16)}… · banned ${new Date(b.revokedAt).toLocaleDateString()}`]]);
7568
- }
7569
- return EXIT.SUCCESS;
7570
- });
7571
- }
7572
- var init_ban = __esm(() => {
7573
- init_connect();
7574
- init_facade();
7575
- init_render();
7576
- init_exit_codes();
7577
- });
7578
-
7579
- // src/ui/warnings.ts
7580
- function warnDaemonState(res, opts = {}) {
7581
- if (alreadyWarned)
7582
- return false;
7583
- if (opts.quiet || opts.json)
7584
- return false;
7585
- if (res.state === "up")
7586
- return false;
7587
- if (getDaemonPolicy().mode === "strict" && res.state !== "started")
7588
- return false;
7589
- alreadyWarned = true;
7590
- const tag = (label) => `[claudemesh] ${label}`;
7591
- const hint = (s) => dim(s);
7592
- switch (res.state) {
7593
- case "started":
7594
- process.stderr.write(`${tag("info")} daemon restarted automatically ${hint(`(took ${res.durationMs}ms)`)}
7595
- `);
7596
- return true;
7597
- case "down":
7598
- process.stderr.write(`${tag("info")} daemon not running — using cold path ${hint("(slower; run `claudemesh daemon up` for warm path)")}
7599
- `);
7600
- return true;
7601
- case "spawn-suppressed":
7602
- process.stderr.write(`${tag("warn")} ${res.reason ?? "daemon failed to start recently"} — using cold path ${hint("(run `claudemesh doctor`)")}
7603
- `);
7604
- return true;
7605
- case "spawn-failed":
7606
- process.stderr.write(`${tag("warn")} daemon spawn failed${res.reason ? `: ${res.reason}` : ""} — using cold path ${hint("(check ~/.claudemesh/daemon/daemon.log)")}
7607
- `);
7608
- return true;
7609
- case "service-not-ready":
7610
- process.stderr.write(`${tag("warn")} ${res.reason ?? "service-managed daemon not responding"} — using cold path ${hint("(check ~/.claudemesh/daemon/daemon.log)")}
7611
- `);
7612
- return true;
7613
- }
7614
- return false;
7615
- }
7616
- var alreadyWarned = false;
7617
- var init_warnings = __esm(() => {
7618
- init_policy();
7619
- init_styles();
7620
- });
7621
-
7622
- // src/services/bridge/daemon-route.ts
7623
- var exports_daemon_route = {};
7624
- __export(exports_daemon_route, {
7625
- trySetStateViaDaemon: () => trySetStateViaDaemon,
7626
- trySendViaDaemon: () => trySendViaDaemon,
7627
- tryRememberViaDaemon: () => tryRememberViaDaemon,
7628
- tryRecallViaDaemon: () => tryRecallViaDaemon,
7629
- tryListStateViaDaemon: () => tryListStateViaDaemon,
7630
- tryListSkillsViaDaemon: () => tryListSkillsViaDaemon,
7631
- tryListPeersViaDaemon: () => tryListPeersViaDaemon,
7632
- tryGetStateViaDaemon: () => tryGetStateViaDaemon,
7633
- tryGetSkillViaDaemon: () => tryGetSkillViaDaemon,
7634
- tryForgetViaDaemon: () => tryForgetViaDaemon
7635
- });
7636
- function meshQuery(mesh) {
7637
- return mesh ? `?mesh=${encodeURIComponent(mesh)}` : "";
7638
- }
7639
- async function daemonReachable() {
7640
- const policy2 = getDaemonPolicy();
7641
- if (policy2.mode === "no-daemon")
7642
- return false;
7643
- const res = await ensureDaemonReady({ noAutoSpawn: false });
7644
- warnDaemonState(res, {});
7645
- return res.state === "up" || res.state === "started";
7646
- }
7647
- async function tryListPeersViaDaemon(mesh) {
7648
- if (!await daemonReachable())
7649
- return null;
7650
- try {
7651
- const res = await ipc({ path: `/v1/peers${meshQuery(mesh)}`, timeoutMs: 3000 });
7652
- if (res.status !== 200)
7653
- return null;
7654
- return Array.isArray(res.body.peers) ? res.body.peers : [];
7655
- } catch (err) {
7656
- const msg = String(err);
7657
- if (/ENOENT|ECONNREFUSED|ipc_timeout/.test(msg))
7658
- return null;
7659
- return null;
7660
- }
7661
- }
7662
- async function tryListSkillsViaDaemon(mesh) {
7663
- if (!await daemonReachable())
7664
- return null;
7665
- try {
7666
- const res = await ipc({ path: `/v1/skills${meshQuery(mesh)}`, timeoutMs: 3000 });
7667
- if (res.status !== 200)
7668
- return null;
7669
- return Array.isArray(res.body.skills) ? res.body.skills : [];
7670
- } catch (err) {
7671
- const msg = String(err);
7672
- if (/ENOENT|ECONNREFUSED|ipc_timeout/.test(msg))
7673
- return null;
7674
- return null;
7675
- }
7676
- }
7677
- async function tryGetSkillViaDaemon(name, mesh) {
7678
- if (!await daemonReachable())
7679
- return null;
7680
- try {
7681
- const res = await ipc({
7682
- path: `/v1/skills/${encodeURIComponent(name)}${meshQuery(mesh)}`,
7683
- timeoutMs: 3000
7684
- });
7685
- if (res.status === 404)
7686
- return null;
7687
- if (res.status !== 200)
7688
- return null;
7689
- return res.body.skill ?? null;
7690
- } catch {
7691
- return null;
7692
- }
7693
- }
7694
- async function tryGetStateViaDaemon(key, mesh) {
7695
- if (!await daemonReachable())
7696
- return null;
7697
- try {
7698
- const path = `/v1/state?key=${encodeURIComponent(key)}${mesh ? `&mesh=${encodeURIComponent(mesh)}` : ""}`;
7699
- const res = await ipc({ path, timeoutMs: 3000 });
7700
- if (res.status === 404)
7701
- return;
7702
- if (res.status !== 200)
7703
- return null;
7704
- return res.body.state ?? undefined;
7705
- } catch (err) {
7706
- const msg = String(err);
7707
- if (/ENOENT|ECONNREFUSED|ipc_timeout/.test(msg))
7708
- return null;
7709
- return null;
7710
- }
7711
- }
7712
- async function tryListStateViaDaemon(mesh) {
7713
- if (!await daemonReachable())
7714
- return null;
7715
- try {
7716
- const res = await ipc({ path: `/v1/state${meshQuery(mesh)}`, timeoutMs: 3000 });
7717
- if (res.status !== 200)
7718
- return null;
7719
- return Array.isArray(res.body.entries) ? res.body.entries : [];
7720
- } catch (err) {
7721
- const msg = String(err);
7722
- if (/ENOENT|ECONNREFUSED|ipc_timeout/.test(msg))
7723
- return null;
7724
- return null;
7725
- }
7726
- }
7727
- async function trySetStateViaDaemon(key, value, mesh) {
7728
- if (!await daemonReachable())
7729
- return false;
7730
- try {
7731
- const res = await ipc({
7732
- method: "POST",
7733
- path: "/v1/state",
7734
- timeoutMs: 3000,
7735
- body: { key, value, ...mesh ? { mesh } : {} }
7736
- });
7737
- return res.status === 200 && res.body.ok === true;
7738
- } catch {
7739
- return false;
7719
+ if (opts.all)
7720
+ return { type: kind, all: true };
7721
+ if (opts.stale) {
7722
+ const ms = parseStaleMs(opts.stale);
7723
+ if (!ms)
7724
+ return { error: `Invalid stale duration: "${opts.stale}". Use e.g. 30m, 1h, 300s.` };
7725
+ return { type: kind, stale: ms };
7740
7726
  }
7727
+ if (target)
7728
+ return { type: kind, target };
7729
+ return { error: `Usage: claudemesh ${kind} <peer> | --stale 30m | --all` };
7741
7730
  }
7742
- async function tryRememberViaDaemon(content, tags, mesh) {
7743
- if (!await daemonReachable())
7744
- return null;
7745
- try {
7746
- const res = await ipc({
7747
- method: "POST",
7748
- path: "/v1/memory",
7749
- timeoutMs: 5000,
7750
- body: { content, ...tags?.length ? { tags } : {}, ...mesh ? { mesh } : {} }
7751
- });
7752
- if (res.status !== 200 || !res.body.id)
7753
- return null;
7754
- return { id: res.body.id, mesh: res.body.mesh };
7755
- } catch {
7756
- return null;
7731
+ async function runDisconnect(target, opts = {}) {
7732
+ const config = readConfig();
7733
+ const meshSlug = opts.mesh ?? config.meshes[0]?.slug;
7734
+ if (!meshSlug) {
7735
+ render.err("No mesh joined.");
7736
+ return EXIT.NOT_FOUND;
7737
+ }
7738
+ const built = buildPayload("disconnect", target, opts);
7739
+ if ("error" in built) {
7740
+ render.err(String(built.error));
7741
+ return EXIT.INVALID_ARGS;
7757
7742
  }
7743
+ return await withMesh({ meshSlug }, async (client) => {
7744
+ const result = await client.sendAndWait(built);
7745
+ const peers = result?.affected ?? result?.kicked ?? [];
7746
+ if (peers.length === 0)
7747
+ render.info("No peers matched.");
7748
+ else {
7749
+ render.ok(`Disconnected ${peers.length} peer(s): ${peers.join(", ")}`);
7750
+ render.hint("They will auto-reconnect within seconds. For a session-ending kick, use `claudemesh kick`.");
7751
+ }
7752
+ return EXIT.SUCCESS;
7753
+ });
7758
7754
  }
7759
- async function tryRecallViaDaemon(query, mesh) {
7760
- if (!await daemonReachable())
7761
- return null;
7762
- try {
7763
- const path = `/v1/memory?q=${encodeURIComponent(query)}${mesh ? `&mesh=${encodeURIComponent(mesh)}` : ""}`;
7764
- const res = await ipc({ path, timeoutMs: 5000 });
7765
- if (res.status !== 200)
7766
- return null;
7767
- return Array.isArray(res.body.matches) ? res.body.matches : [];
7768
- } catch (err) {
7769
- const msg = String(err);
7770
- if (/ENOENT|ECONNREFUSED|ipc_timeout/.test(msg))
7771
- return null;
7772
- return null;
7755
+ async function runKick(target, opts = {}) {
7756
+ const config = readConfig();
7757
+ const meshSlug = opts.mesh ?? config.meshes[0]?.slug;
7758
+ if (!meshSlug) {
7759
+ render.err("No mesh joined.");
7760
+ return EXIT.NOT_FOUND;
7761
+ }
7762
+ const built = buildPayload("kick", target, opts);
7763
+ if ("error" in built) {
7764
+ render.err(String(built.error));
7765
+ return EXIT.INVALID_ARGS;
7773
7766
  }
7767
+ return await withMesh({ meshSlug }, async (client) => {
7768
+ const result = await client.sendAndWait(built);
7769
+ const peers = result?.affected ?? result?.kicked ?? [];
7770
+ if (peers.length === 0)
7771
+ render.info("No peers matched.");
7772
+ else {
7773
+ render.ok(`Kicked ${peers.length} peer(s): ${peers.join(", ")}`);
7774
+ render.hint("Their Claude Code session ended. They can rejoin anytime by running `claudemesh`.");
7775
+ }
7776
+ return EXIT.SUCCESS;
7777
+ });
7774
7778
  }
7775
- async function tryForgetViaDaemon(id, mesh) {
7776
- if (!await daemonReachable())
7777
- return false;
7778
- try {
7779
- const path = `/v1/memory/${encodeURIComponent(id)}${meshQuery(mesh)}`;
7780
- const res = await ipc({ method: "DELETE", path, timeoutMs: 3000 });
7781
- return res.status === 200 && res.body.ok === true;
7782
- } catch {
7783
- return false;
7779
+ var init_kick = __esm(() => {
7780
+ init_connect();
7781
+ init_facade();
7782
+ init_render();
7783
+ init_exit_codes();
7784
+ });
7785
+
7786
+ // src/commands/ban.ts
7787
+ var exports_ban = {};
7788
+ __export(exports_ban, {
7789
+ runUnban: () => runUnban,
7790
+ runBans: () => runBans,
7791
+ runBan: () => runBan
7792
+ });
7793
+ async function runBan(target, opts = {}) {
7794
+ if (!target) {
7795
+ render.err("Usage: claudemesh ban <peer-name-or-pubkey>");
7796
+ return EXIT.INVALID_ARGS;
7797
+ }
7798
+ const config = readConfig();
7799
+ const meshSlug = opts.mesh ?? config.meshes[0]?.slug;
7800
+ if (!meshSlug) {
7801
+ render.err("No mesh joined.");
7802
+ return EXIT.NOT_FOUND;
7784
7803
  }
7804
+ return await withMesh({ meshSlug }, async (client) => {
7805
+ const result = await client.sendAndWait({ type: "ban", target });
7806
+ if (result?.banned) {
7807
+ render.ok(`Banned ${result.banned} from ${meshSlug}. They cannot reconnect until unbanned.`);
7808
+ render.hint(`Undo: claudemesh unban ${result.banned} --mesh ${meshSlug}`);
7809
+ } else {
7810
+ render.err(result?.message ?? result?.error ?? result?.code ?? "ban failed");
7811
+ }
7812
+ return result?.banned ? EXIT.SUCCESS : EXIT.INTERNAL_ERROR;
7813
+ });
7785
7814
  }
7786
- async function trySendViaDaemon(args) {
7787
- if (!await daemonReachable())
7788
- return null;
7789
- try {
7790
- const res = await ipc({
7791
- method: "POST",
7792
- path: "/v1/send",
7793
- timeoutMs: 3000,
7794
- body: {
7795
- to: args.to,
7796
- message: args.message,
7797
- priority: args.priority,
7798
- ...args.idempotencyKey ? { client_message_id: args.idempotencyKey } : {},
7799
- ...args.expectedMesh ? { mesh: args.expectedMesh } : {}
7800
- }
7801
- });
7802
- if (res.status === 202 || res.status === 200) {
7803
- return {
7804
- ok: true,
7805
- messageId: res.body.broker_message_id ?? res.body.client_message_id ?? "",
7806
- duplicate: res.body.duplicate,
7807
- status: res.body.status
7808
- };
7815
+ async function runUnban(target, opts = {}) {
7816
+ if (!target) {
7817
+ render.err("Usage: claudemesh unban <peer-name-or-pubkey>");
7818
+ return EXIT.INVALID_ARGS;
7819
+ }
7820
+ const config = readConfig();
7821
+ const meshSlug = opts.mesh ?? config.meshes[0]?.slug;
7822
+ if (!meshSlug) {
7823
+ render.err("No mesh joined.");
7824
+ return EXIT.NOT_FOUND;
7825
+ }
7826
+ return await withMesh({ meshSlug }, async (client) => {
7827
+ const result = await client.sendAndWait({ type: "unban", target });
7828
+ if (result?.unbanned) {
7829
+ render.ok(`Unbanned ${result.unbanned} from ${meshSlug}. They can rejoin.`);
7830
+ } else {
7831
+ render.err(result?.message ?? result?.error ?? result?.code ?? "unban failed");
7809
7832
  }
7810
- return { ok: false, error: res.body.error ?? `daemon http ${res.status}` };
7811
- } catch (err) {
7812
- const msg = String(err);
7813
- if (/ENOENT|ECONNREFUSED|ipc_timeout/.test(msg))
7814
- return null;
7815
- return { ok: false, error: msg };
7833
+ return result?.unbanned ? EXIT.SUCCESS : EXIT.INTERNAL_ERROR;
7834
+ });
7835
+ }
7836
+ async function runBans(opts = {}) {
7837
+ const config = readConfig();
7838
+ const meshSlug = opts.mesh ?? config.meshes[0]?.slug;
7839
+ if (!meshSlug) {
7840
+ render.err("No mesh joined.");
7841
+ return EXIT.NOT_FOUND;
7816
7842
  }
7843
+ return await withMesh({ meshSlug }, async (client) => {
7844
+ const result = await client.sendAndWait({ type: "list_bans" });
7845
+ const bans = result?.bans ?? [];
7846
+ if (opts.json) {
7847
+ process.stdout.write(JSON.stringify(bans, null, 2) + `
7848
+ `);
7849
+ return EXIT.SUCCESS;
7850
+ }
7851
+ if (bans.length === 0) {
7852
+ render.info("No banned members.");
7853
+ return EXIT.SUCCESS;
7854
+ }
7855
+ render.section(`banned members on ${meshSlug}`);
7856
+ for (const b of bans) {
7857
+ render.kv([[b.name, `${b.pubkey.slice(0, 16)}… · banned ${new Date(b.revokedAt).toLocaleDateString()}`]]);
7858
+ }
7859
+ return EXIT.SUCCESS;
7860
+ });
7817
7861
  }
7818
- var init_daemon_route = __esm(() => {
7819
- init_client3();
7820
- init_lifecycle();
7821
- init_policy();
7822
- init_warnings();
7862
+ var init_ban = __esm(() => {
7863
+ init_connect();
7864
+ init_facade();
7865
+ init_render();
7866
+ init_exit_codes();
7823
7867
  });
7824
7868
 
7825
7869
  // src/services/session/resolve.ts
@@ -7878,18 +7922,26 @@ async function listPeersForMesh(slug) {
7878
7922
  const config = readConfig();
7879
7923
  const joined = config.meshes.find((m) => m.slug === slug);
7880
7924
  const selfMemberPubkey = joined?.pubkey ?? null;
7925
+ let selfSessionPubkey = null;
7926
+ try {
7927
+ const { getSessionInfo: getSessionInfo2 } = await Promise.resolve().then(() => (init_resolve(), exports_resolve));
7928
+ const sess = await getSessionInfo2();
7929
+ if (sess && sess.mesh === slug && sess.presence?.sessionPubkey) {
7930
+ selfSessionPubkey = sess.presence.sessionPubkey;
7931
+ }
7932
+ } catch {}
7881
7933
  try {
7882
7934
  const { tryListPeersViaDaemon: tryListPeersViaDaemon2 } = await Promise.resolve().then(() => (init_daemon_route(), exports_daemon_route));
7883
7935
  const dr = await tryListPeersViaDaemon2();
7884
7936
  if (dr !== null) {
7885
- return dr.map((p) => annotateSelf(p, selfMemberPubkey, null));
7937
+ return dr.map((p) => annotateSelf(p, selfMemberPubkey, selfSessionPubkey));
7886
7938
  }
7887
7939
  } catch {}
7888
7940
  let result = [];
7889
7941
  await withMesh({ meshSlug: slug }, async (client) => {
7890
7942
  const all = await client.listPeers();
7891
- const selfSessionPubkey = client.getSessionPubkey();
7892
- result = all.map((p) => annotateSelf(p, selfMemberPubkey, selfSessionPubkey));
7943
+ const selfSessionPubkey2 = client.getSessionPubkey();
7944
+ result = all.map((p) => annotateSelf(p, selfMemberPubkey, selfSessionPubkey2));
7893
7945
  });
7894
7946
  return result;
7895
7947
  }
@@ -7925,12 +7977,19 @@ async function runPeers(flags) {
7925
7977
  allJson.push({ mesh: slug, peers: projected });
7926
7978
  continue;
7927
7979
  }
7928
- render.section(`peers on ${slug} (${peers.length})`);
7929
- if (peers.length === 0) {
7980
+ const visible = flags.all ? peers : peers.filter((p) => p.channel !== "claudemesh-daemon");
7981
+ const sorted = visible.slice().sort((a, b) => {
7982
+ const score = (p) => p.isThisSession ? 0 : p.isSelf ? 1 : 2;
7983
+ return score(a) - score(b);
7984
+ });
7985
+ const hiddenDaemons = peers.length - visible.length;
7986
+ const header = hiddenDaemons > 0 ? `peers on ${slug} (${sorted.length}, ${hiddenDaemons} daemon hidden — use --all)` : `peers on ${slug} (${sorted.length})`;
7987
+ render.section(header);
7988
+ if (sorted.length === 0) {
7930
7989
  render.info(dim(" (no peers connected)"));
7931
7990
  continue;
7932
7991
  }
7933
- for (const p of peers) {
7992
+ for (const p of sorted) {
7934
7993
  const statusDot = p.status === "working" ? yellow("●") : green("●");
7935
7994
  const name = bold(p.displayName);
7936
7995
  const meta = [];
@@ -7943,6 +8002,7 @@ async function runPeers(flags) {
7943
8002
  const metaStr = meta.length ? dim(` (${meta.join(", ")})`) : "";
7944
8003
  const summary = p.summary ? dim(` — ${p.summary}`) : "";
7945
8004
  const pubkeyTag = dim(` · ${p.pubkey.slice(0, 16)}…`);
8005
+ const sidTag = p.sessionId ? dim(` · sid:${p.sessionId.slice(0, 8)}`) : "";
7946
8006
  const selfTag = p.isThisSession ? dim(" ") + yellow("(this session)") : p.isSelf ? dim(" ") + yellow("(your other session)") : "";
7947
8007
  const inlineTags = [];
7948
8008
  const peerRole = p.profile?.role?.trim();
@@ -7952,7 +8012,7 @@ async function runPeers(flags) {
7952
8012
  inlineTags.push(...p.groups.map((g) => `@${g.name}${g.role ? `:${g.role}` : ""}`));
7953
8013
  }
7954
8014
  const tagsStr = inlineTags.length ? " [" + inlineTags.join(", ") + "]" : "";
7955
- render.info(`${statusDot} ${name}${selfTag}${tagsStr}${metaStr}${pubkeyTag}${summary}`);
8015
+ render.info(`${statusDot} ${name}${selfTag}${tagsStr}${metaStr}${pubkeyTag}${sidTag}${summary}`);
7956
8016
  if (p.cwd)
7957
8017
  render.info(dim(` cwd: ${p.cwd}`));
7958
8018
  if (!peerRole && p.groups.length === 0) {
@@ -7992,13 +8052,104 @@ async function runSend(flags, to, message) {
7992
8052
  const priority = flags.priority === "now" ? "now" : flags.priority === "low" ? "low" : "next";
7993
8053
  const config = readConfig();
7994
8054
  const meshSlug = flags.mesh ?? (config.meshes.length === 1 ? config.meshes[0].slug : null);
7995
- if (!flags.self && meshSlug) {
8055
+ if (!to.startsWith("@") && !to.startsWith("#") && to !== "*" && /^[0-9a-f]{4,63}$/i.test(to)) {
8056
+ try {
8057
+ const { tryListPeersViaDaemon: tryListPeersViaDaemon2 } = await Promise.resolve().then(() => (init_daemon_route(), exports_daemon_route));
8058
+ const peers = await tryListPeersViaDaemon2() ?? [];
8059
+ const lower = to.toLowerCase();
8060
+ const matches2 = peers.filter((p) => {
8061
+ const pk = p.pubkey ?? "";
8062
+ const mpk = p.memberPubkey ?? "";
8063
+ return pk.toLowerCase().startsWith(lower) || mpk.toLowerCase().startsWith(lower);
8064
+ });
8065
+ if (matches2.length === 0) {
8066
+ render.err(`No peer matches hex prefix "${to}".`);
8067
+ const names = peers.map((p) => p.displayName).filter(Boolean).join(", ");
8068
+ if (names)
8069
+ render.hint(`online: ${names}`);
8070
+ process.exit(1);
8071
+ }
8072
+ if (matches2.length > 1) {
8073
+ const candidates = matches2.map((p) => {
8074
+ const pk = p.pubkey ?? "";
8075
+ const dn = p.displayName ?? "?";
8076
+ return `${dn} ${pk.slice(0, 16)}…`;
8077
+ }).join(", ");
8078
+ render.err(`Ambiguous hex prefix "${to}" — matches ${matches2.length} peers.`);
8079
+ render.hint(`candidates: ${candidates}`);
8080
+ render.hint("Use a longer prefix or paste the full 64-char pubkey.");
8081
+ process.exit(1);
8082
+ }
8083
+ to = matches2[0].pubkey ?? to;
8084
+ } catch {}
8085
+ }
8086
+ if (meshSlug) {
7996
8087
  const joined = config.meshes.find((m) => m.slug === meshSlug);
7997
- if (joined && /^[0-9a-f]{64}$/i.test(to) && to.toLowerCase() === joined.pubkey.toLowerCase()) {
8088
+ const isOwnMemberKey = joined && /^[0-9a-f]{64}$/i.test(to) && to.toLowerCase() === joined.pubkey.toLowerCase();
8089
+ if (isOwnMemberKey && !flags.self) {
7998
8090
  render.err(`Target "${to.slice(0, 16)}…" is your own member pubkey on mesh "${meshSlug}".`);
7999
8091
  render.hint("Pass --self to message a sibling session of your own member, or pick a different peer's pubkey.");
8000
8092
  process.exit(1);
8001
8093
  }
8094
+ if (isOwnMemberKey && flags.self) {
8095
+ try {
8096
+ const { tryListPeersViaDaemon: tryListPeersViaDaemon2 } = await Promise.resolve().then(() => (init_daemon_route(), exports_daemon_route));
8097
+ const { getSessionInfo: getSessionInfo2 } = await Promise.resolve().then(() => (init_resolve(), exports_resolve));
8098
+ const peers = await tryListPeersViaDaemon2() ?? [];
8099
+ const session = await getSessionInfo2();
8100
+ const ownSessionPk = session?.presence?.sessionPubkey?.toLowerCase();
8101
+ const siblings = peers.filter((p) => {
8102
+ const r = p;
8103
+ if (!r.pubkey)
8104
+ return false;
8105
+ if (ownSessionPk && r.pubkey.toLowerCase() === ownSessionPk)
8106
+ return false;
8107
+ if (r.channel === "claudemesh-daemon")
8108
+ return false;
8109
+ return r.memberPubkey?.toLowerCase() === to.toLowerCase();
8110
+ });
8111
+ if (siblings.length === 0) {
8112
+ render.err(`--self fan-out: no other sibling sessions of your member online.`);
8113
+ process.exit(1);
8114
+ }
8115
+ const results = [];
8116
+ for (const peer of siblings) {
8117
+ const pk = peer.pubkey;
8118
+ const dr = await trySendViaDaemon({ to: pk, message, priority, expectedMesh: meshSlug ?? undefined });
8119
+ if (dr === null) {
8120
+ results.push({ pubkey: pk, ok: false, error: "daemon path unavailable" });
8121
+ continue;
8122
+ }
8123
+ if (dr.ok) {
8124
+ results.push({
8125
+ pubkey: pk,
8126
+ ok: true,
8127
+ ...dr.messageId ? { messageId: dr.messageId } : {}
8128
+ });
8129
+ } else {
8130
+ results.push({ pubkey: pk, ok: false, error: dr.error });
8131
+ }
8132
+ }
8133
+ const okCount = results.filter((r) => r.ok).length;
8134
+ if (flags.json) {
8135
+ console.log(JSON.stringify({ ok: okCount > 0, fanout: results, via: "daemon" }));
8136
+ } else if (okCount === results.length) {
8137
+ render.ok(`fanned out to ${okCount} sibling session${okCount === 1 ? "" : "s"} (daemon)`);
8138
+ for (const r of results)
8139
+ render.info(dim(` → ${r.pubkey.slice(0, 16)}… ${r.messageId ? dim(r.messageId.slice(0, 8)) : ""}`));
8140
+ } else {
8141
+ render.warn(`fanned out: ${okCount}/${results.length} delivered`);
8142
+ for (const r of results) {
8143
+ const tag = r.ok ? "✔" : "✘";
8144
+ render.info(` ${tag} ${r.pubkey.slice(0, 16)}… ${r.error ? dim(`— ${r.error}`) : ""}`);
8145
+ }
8146
+ }
8147
+ return;
8148
+ } catch (e) {
8149
+ render.err(`--self fan-out failed: ${e instanceof Error ? e.message : String(e)}`);
8150
+ process.exit(1);
8151
+ }
8152
+ }
8002
8153
  }
8003
8154
  {
8004
8155
  const dr = await trySendViaDaemon({ to, message, priority, expectedMesh: meshSlug ?? undefined });
@@ -8848,15 +8999,30 @@ __export(exports_whoami, {
8848
8999
  });
8849
9000
  async function whoami(opts) {
8850
9001
  const result = await whoAmI();
9002
+ const session = await getSessionInfo();
8851
9003
  if (opts.json) {
8852
- console.log(JSON.stringify({ schema_version: "1.0", ...result }, null, 2));
8853
- return result.signed_in || result.local ? EXIT.SUCCESS : EXIT.AUTH_FAILED;
9004
+ console.log(JSON.stringify({ schema_version: "1.0", ...result, session }, null, 2));
9005
+ return result.signed_in || result.local || session ? EXIT.SUCCESS : EXIT.AUTH_FAILED;
8854
9006
  }
8855
- if (!result.signed_in && !result.local) {
9007
+ if (!result.signed_in && !result.local && !session) {
8856
9008
  render.err("Not signed in", "Run `claudemesh login` to sign in or `claudemesh <invite>` to join.");
8857
9009
  return EXIT.AUTH_FAILED;
8858
9010
  }
8859
9011
  render.section("whoami");
9012
+ if (session) {
9013
+ const sessionPk = session.presence?.sessionPubkey;
9014
+ const groups = (session.groups ?? []).join(", ") || dim("(none)");
9015
+ render.kv([
9016
+ ["this session", `${yellow(session.displayName)} on ${bold(session.mesh)}`],
9017
+ ["session id", dim(session.sessionId)],
9018
+ ...sessionPk ? [["session pubkey", dim(`${sessionPk.slice(0, 16)}… (full: ${sessionPk})`)]] : [],
9019
+ ...session.role ? [["role", session.role]] : [],
9020
+ ["groups", groups],
9021
+ ...session.cwd ? [["cwd", dim(session.cwd)]] : [],
9022
+ ["pid", String(session.pid)]
9023
+ ]);
9024
+ render.blank();
9025
+ }
8860
9026
  if (result.signed_in) {
8861
9027
  render.kv([
8862
9028
  ["user", `${bold(result.user.display_name)} ${dim(`(${result.user.email})`)}`],
@@ -8882,6 +9048,7 @@ async function whoami(opts) {
8882
9048
  }
8883
9049
  var init_whoami = __esm(() => {
8884
9050
  init_facade6();
9051
+ init_resolve();
8885
9052
  init_render();
8886
9053
  init_styles();
8887
9054
  init_exit_codes();
@@ -18308,6 +18475,28 @@ var init_seed_test_mesh = __esm(() => {
18308
18475
  });
18309
18476
 
18310
18477
  // src/cli/argv.ts
18478
+ var BOOLEAN_FLAGS = new Set([
18479
+ "self",
18480
+ "json",
18481
+ "all",
18482
+ "yes",
18483
+ "y",
18484
+ "help",
18485
+ "h",
18486
+ "version",
18487
+ "v",
18488
+ "quiet",
18489
+ "strict",
18490
+ "continue",
18491
+ "no-daemon",
18492
+ "no-color",
18493
+ "debug",
18494
+ "allow-ci-persistent",
18495
+ "force",
18496
+ "dry-run",
18497
+ "verbose",
18498
+ "skip-service"
18499
+ ]);
18311
18500
  function parseArgv(argv) {
18312
18501
  const args = argv.slice(2);
18313
18502
  const flags = {};
@@ -18315,18 +18504,32 @@ function parseArgv(argv) {
18315
18504
  let command = "";
18316
18505
  for (let i = 0;i < args.length; i++) {
18317
18506
  const arg = args[i];
18507
+ if (arg.startsWith("--") && arg.includes("=")) {
18508
+ const eq = arg.indexOf("=");
18509
+ const key = arg.slice(2, eq);
18510
+ flags[key] = arg.slice(eq + 1);
18511
+ continue;
18512
+ }
18318
18513
  if (arg.startsWith("--")) {
18319
18514
  const key = arg.slice(2);
18515
+ if (BOOLEAN_FLAGS.has(key)) {
18516
+ flags[key] = true;
18517
+ continue;
18518
+ }
18320
18519
  const next = args[i + 1];
18321
- if (next && !next.startsWith("-")) {
18520
+ if (next !== undefined && !next.startsWith("-")) {
18322
18521
  flags[key] = next;
18323
18522
  i++;
18324
18523
  } else
18325
18524
  flags[key] = true;
18326
18525
  } else if (arg.startsWith("-") && arg.length === 2) {
18327
18526
  const key = arg.slice(1);
18527
+ if (BOOLEAN_FLAGS.has(key)) {
18528
+ flags[key] = true;
18529
+ continue;
18530
+ }
18328
18531
  const next = args[i + 1];
18329
- if (next && !next.startsWith("-")) {
18532
+ if (next !== undefined && !next.startsWith("-")) {
18330
18533
  flags[key] = next;
18331
18534
  i++;
18332
18535
  } else
@@ -18900,6 +19103,10 @@ Peer (resource form, recommended)
18900
19103
 
18901
19104
  Message (resource form)
18902
19105
  claudemesh message send <to> <m> send a message (alias: send)
19106
+ flags: [--priority now|next|low] [--mesh <slug>]
19107
+ [--self] (allow targeting your own member/session pubkey;
19108
+ fans out to every sibling session of your member)
19109
+ [--json] (machine-readable result)
18903
19110
  claudemesh message inbox drain pending (alias: inbox)
18904
19111
  claudemesh message status <id> delivery status (alias: msg-status)
18905
19112
 
@@ -19248,7 +19455,7 @@ async function main() {
19248
19455
  }
19249
19456
  case "peers": {
19250
19457
  const { runPeers: runPeers2 } = await Promise.resolve().then(() => (init_peers(), exports_peers));
19251
- await runPeers2({ mesh: flags.mesh, json: flags.json });
19458
+ await runPeers2({ mesh: flags.mesh, json: flags.json, all: !!flags.all });
19252
19459
  break;
19253
19460
  }
19254
19461
  case "send": {
@@ -19477,7 +19684,7 @@ async function main() {
19477
19684
  }
19478
19685
  case "peer": {
19479
19686
  const sub = positionals[0];
19480
- const f = { mesh: flags.mesh, json: flags.json };
19687
+ const f = { mesh: flags.mesh, json: flags.json, all: !!flags.all };
19481
19688
  const id = positionals[1] ?? "";
19482
19689
  if (sub === "list") {
19483
19690
  const { runPeers: runPeers2 } = await Promise.resolve().then(() => (init_peers(), exports_peers));
@@ -19510,7 +19717,7 @@ async function main() {
19510
19717
  const sub = positionals[0];
19511
19718
  if (sub === "send") {
19512
19719
  const { runSend: runSend2 } = await Promise.resolve().then(() => (init_send(), exports_send));
19513
- await runSend2({ mesh: flags.mesh, priority: flags.priority, json: !!flags.json }, positionals[1] ?? "", positionals.slice(2).join(" "));
19720
+ await runSend2({ mesh: flags.mesh, priority: flags.priority, json: !!flags.json, self: !!flags.self }, positionals[1] ?? "", positionals.slice(2).join(" "));
19514
19721
  } else if (sub === "inbox") {
19515
19722
  const { runInbox: runInbox2 } = await Promise.resolve().then(() => (init_inbox(), exports_inbox));
19516
19723
  await runInbox2({ json: !!flags.json });
@@ -20066,4 +20273,4 @@ main().catch((err) => {
20066
20273
  process.exit(EXIT.INTERNAL_ERROR);
20067
20274
  });
20068
20275
 
20069
- //# debugId=23383F2CEE3C764664756E2164756E21
20276
+ //# debugId=9045DA825D3D7C3164756E2164756E21