claudemesh-cli 1.0.0-alpha.41 → 1.0.0-alpha.43

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.
@@ -88,7 +88,7 @@ __export(exports_urls, {
88
88
  VERSION: () => VERSION,
89
89
  URLS: () => URLS
90
90
  });
91
- var URLS, VERSION = "1.0.0-alpha.41", env;
91
+ var URLS, VERSION = "1.0.0-alpha.43", env;
92
92
  var init_urls = __esm(() => {
93
93
  URLS = {
94
94
  BROKER: process.env.CLAUDEMESH_BROKER_URL ?? "wss://ic.claudemesh.com/ws",
@@ -1150,6 +1150,7 @@ class BrokerClient {
1150
1150
  return this._serviceCatalog;
1151
1151
  }
1152
1152
  closed = false;
1153
+ terminalClose = null;
1153
1154
  reconnectAttempt = 0;
1154
1155
  helloTimer = null;
1155
1156
  reconnectTimer = null;
@@ -1277,12 +1278,22 @@ class BrokerClient {
1277
1278
  }
1278
1279
  this.handleServerMessage(msg);
1279
1280
  };
1280
- const onClose = () => {
1281
+ const onClose = (code, reasonBuf) => {
1281
1282
  if (this.helloTimer)
1282
1283
  clearTimeout(this.helloTimer);
1283
1284
  this.helloTimer = null;
1284
1285
  if (this.ws === ws)
1285
1286
  this.ws = null;
1287
+ const reason = reasonBuf?.toString("utf-8") ?? "";
1288
+ if (code === 4001 || code === 4002) {
1289
+ this.closed = true;
1290
+ this.setConnStatus("closed");
1291
+ this.terminalClose = { code, reason };
1292
+ if (this._status !== "open") {
1293
+ reject(new Error(`ws terminal close ${code}: ${reason || "session ended"}`));
1294
+ }
1295
+ return;
1296
+ }
1286
1297
  if (this._status !== "open" && this._status !== "reconnecting") {
1287
1298
  reject(new Error("ws closed before hello_ack"));
1288
1299
  }
@@ -2965,6 +2976,9 @@ class BrokerClient {
2965
2976
  }
2966
2977
  if (msg.type === "error") {
2967
2978
  this.debug(`broker error: ${msg.code} ${msg.message}`);
2979
+ if (msg.code === "revoked") {
2980
+ this.terminalClose = { code: 4002, reason: String(msg.message ?? "revoked") };
2981
+ }
2968
2982
  const id = msg.id ? String(msg.id) : null;
2969
2983
  let handledByPendingSend = false;
2970
2984
  if (id) {
@@ -6164,6 +6178,25 @@ async function withMesh(opts, fn) {
6164
6178
  await client.connect();
6165
6179
  const result = await fn(client, mesh);
6166
6180
  return result;
6181
+ } catch (e) {
6182
+ if (client.terminalClose) {
6183
+ const { code, reason } = client.terminalClose;
6184
+ if (code === 4002) {
6185
+ console.error(`
6186
+ ✘ ${reason}
6187
+ `);
6188
+ } else if (code === 4001) {
6189
+ console.error(`
6190
+ ✘ Kicked from this mesh. Run \`claudemesh\` to rejoin.
6191
+ `);
6192
+ } else {
6193
+ console.error(`
6194
+ ✘ Broker closed connection: ${reason}
6195
+ `);
6196
+ }
6197
+ process.exit(1);
6198
+ }
6199
+ throw e;
6167
6200
  } finally {
6168
6201
  client.close();
6169
6202
  }
@@ -6176,7 +6209,8 @@ var init_connect = __esm(() => {
6176
6209
  // src/commands/kick.ts
6177
6210
  var exports_kick = {};
6178
6211
  __export(exports_kick, {
6179
- runKick: () => runKick
6212
+ runKick: () => runKick,
6213
+ runDisconnect: () => runDisconnect
6180
6214
  });
6181
6215
  function parseStaleMs(input) {
6182
6216
  const m = input.match(/^(\d+)(s|m|h)$/);
@@ -6192,36 +6226,63 @@ function parseStaleMs(input) {
6192
6226
  return val * 3600000;
6193
6227
  return null;
6194
6228
  }
6195
- async function runKick(target, opts = {}) {
6229
+ function buildPayload(kind, target, opts) {
6230
+ if (opts.all)
6231
+ return { type: kind, all: true };
6232
+ if (opts.stale) {
6233
+ const ms = parseStaleMs(opts.stale);
6234
+ if (!ms)
6235
+ return { error: `Invalid stale duration: "${opts.stale}". Use e.g. 30m, 1h, 300s.` };
6236
+ return { type: kind, stale: ms };
6237
+ }
6238
+ if (target)
6239
+ return { type: kind, target };
6240
+ return { error: `Usage: claudemesh ${kind} <peer> | --stale 30m | --all` };
6241
+ }
6242
+ async function runDisconnect(target, opts = {}) {
6196
6243
  const config = readConfig();
6197
6244
  const meshSlug = opts.mesh ?? config.meshes[0]?.slug;
6198
6245
  if (!meshSlug) {
6199
6246
  render.err("No mesh joined.");
6200
6247
  return EXIT.NOT_FOUND;
6201
6248
  }
6249
+ const built = buildPayload("disconnect", target, opts);
6250
+ if ("error" in built) {
6251
+ render.err(String(built.error));
6252
+ return EXIT.INVALID_ARGS;
6253
+ }
6202
6254
  return await withMesh({ meshSlug }, async (client) => {
6203
- let payload;
6204
- if (opts.all) {
6205
- payload = { type: "kick", all: true };
6206
- } else if (opts.stale) {
6207
- const ms = parseStaleMs(opts.stale);
6208
- if (!ms) {
6209
- render.err(`Invalid stale duration: "${opts.stale}". Use e.g. 30m, 1h, 300s.`);
6210
- return EXIT.INVALID_ARGS;
6211
- }
6212
- payload = { type: "kick", stale: ms };
6213
- } else if (target) {
6214
- payload = { type: "kick", target };
6215
- } else {
6216
- render.err("Usage: claudemesh kick <peer> | --stale 30m | --all");
6217
- return EXIT.INVALID_ARGS;
6255
+ const result = await client.sendAndWait(built);
6256
+ const peers = result?.affected ?? result?.kicked ?? [];
6257
+ if (peers.length === 0)
6258
+ render.info("No peers matched.");
6259
+ else {
6260
+ render.ok(`Disconnected ${peers.length} peer(s): ${peers.join(", ")}`);
6261
+ render.hint("They will auto-reconnect within seconds. For a session-ending kick, use `claudemesh kick`.");
6218
6262
  }
6219
- const result = await client.sendAndWait(payload);
6220
- const kicked = result?.kicked ?? [];
6221
- if (kicked.length === 0) {
6263
+ return EXIT.SUCCESS;
6264
+ });
6265
+ }
6266
+ async function runKick(target, opts = {}) {
6267
+ const config = readConfig();
6268
+ const meshSlug = opts.mesh ?? config.meshes[0]?.slug;
6269
+ if (!meshSlug) {
6270
+ render.err("No mesh joined.");
6271
+ return EXIT.NOT_FOUND;
6272
+ }
6273
+ const built = buildPayload("kick", target, opts);
6274
+ if ("error" in built) {
6275
+ render.err(String(built.error));
6276
+ return EXIT.INVALID_ARGS;
6277
+ }
6278
+ return await withMesh({ meshSlug }, async (client) => {
6279
+ const result = await client.sendAndWait(built);
6280
+ const peers = result?.affected ?? result?.kicked ?? [];
6281
+ if (peers.length === 0)
6222
6282
  render.info("No peers matched.");
6223
- } else {
6224
- render.ok(`Kicked ${kicked.length} peer(s): ${kicked.join(", ")}`);
6283
+ else {
6284
+ render.ok(`Kicked ${peers.length} peer(s): ${peers.join(", ")}`);
6285
+ render.hint("Their Claude Code session ended. They can rejoin anytime by running `claudemesh`.");
6225
6286
  }
6226
6287
  return EXIT.SUCCESS;
6227
6288
  });
@@ -6358,7 +6419,8 @@ async function runPeers(flags) {
6358
6419
  meta.push(p.model);
6359
6420
  const metaStr = meta.length ? dim(` (${meta.join(", ")})`) : "";
6360
6421
  const summary = p.summary ? dim(` — ${p.summary}`) : "";
6361
- render.info(`${statusDot} ${name}${groups}${metaStr}${summary}`);
6422
+ const pubkeyTag = dim(` · ${p.pubkey.slice(0, 16)}…`);
6423
+ render.info(`${statusDot} ${name}${groups}${metaStr}${pubkeyTag}${summary}`);
6362
6424
  if (p.cwd)
6363
6425
  render.info(dim(` cwd: ${p.cwd}`));
6364
6426
  }
@@ -7584,7 +7646,7 @@ async function checkNpmLatest() {
7584
7646
  return { name: "CLI up-to-date", pass: true, detail: `npm unreachable (${res.status}) — skipped` };
7585
7647
  }
7586
7648
  const body = await res.json();
7587
- const latest = body["dist-tags"]?.alpha ?? body["dist-tags"]?.latest;
7649
+ const latest = body["dist-tags"]?.latest ?? body["dist-tags"]?.alpha;
7588
7650
  if (!latest)
7589
7651
  return { name: "CLI up-to-date", pass: true, detail: "no dist-tag — skipped" };
7590
7652
  const up = latest === VERSION;
@@ -7592,7 +7654,7 @@ async function checkNpmLatest() {
7592
7654
  name: "CLI up-to-date",
7593
7655
  pass: up,
7594
7656
  detail: up ? `latest ${latest}` : `installed ${VERSION} → latest ${latest}`,
7595
- fix: up ? undefined : "npm i -g claudemesh-cli@alpha"
7657
+ fix: up ? undefined : "npm i -g claudemesh-cli"
7596
7658
  };
7597
7659
  } catch {
7598
7660
  return { name: "CLI up-to-date", pass: true, detail: "npm check skipped" };
@@ -8700,13 +8762,13 @@ __export(exports_upgrade, {
8700
8762
  import { spawnSync as spawnSync6 } from "node:child_process";
8701
8763
  import { existsSync as existsSync16 } from "node:fs";
8702
8764
  import { dirname as dirname3, join as join8, resolve as resolve2 } from "node:path";
8703
- async function latestAlpha() {
8765
+ async function latestVersion() {
8704
8766
  try {
8705
8767
  const res = await fetch(URLS.NPM_REGISTRY, { signal: AbortSignal.timeout(8000) });
8706
8768
  if (!res.ok)
8707
8769
  return null;
8708
8770
  const body = await res.json();
8709
- return body["dist-tags"]?.alpha ?? body["dist-tags"]?.latest ?? null;
8771
+ return body["dist-tags"]?.latest ?? body["dist-tags"]?.alpha ?? null;
8710
8772
  } catch {
8711
8773
  return null;
8712
8774
  }
@@ -8731,7 +8793,7 @@ async function runUpgrade(opts = {}) {
8731
8793
  ["installed", VERSION],
8732
8794
  ["checking", "npm registry…"]
8733
8795
  ]);
8734
- const latest = await latestAlpha();
8796
+ const latest = await latestVersion();
8735
8797
  if (!latest) {
8736
8798
  render.warn("Could not reach npm registry — skipped.");
8737
8799
  return EXIT.SUCCESS;
@@ -8752,7 +8814,7 @@ async function runUpgrade(opts = {}) {
8752
8814
  const args = ["install", "-g"];
8753
8815
  if (prefix)
8754
8816
  args.push("--prefix", prefix);
8755
- args.push("claudemesh-cli@alpha");
8817
+ args.push("claudemesh-cli");
8756
8818
  render.blank();
8757
8819
  render.info(`Updating ${VERSION} → ${latest}…`);
8758
8820
  render.hint(`${npm} ${args.join(" ")}`);
@@ -8760,7 +8822,7 @@ async function runUpgrade(opts = {}) {
8760
8822
  const res = spawnSync6(npm, args, { stdio: "inherit" });
8761
8823
  if (res.status !== 0) {
8762
8824
  render.err(`npm exited with status ${res.status}`);
8763
- render.hint("Try: npm i -g claudemesh-cli@alpha");
8825
+ render.hint("Try: npm i -g claudemesh-cli");
8764
8826
  return EXIT.INTERNAL_ERROR;
8765
8827
  }
8766
8828
  render.blank();
@@ -9949,7 +10011,7 @@ async function resolveClient(to) {
9949
10011
  target = rest;
9950
10012
  }
9951
10013
  }
9952
- if (/^[0-9a-f]{64}$/.test(target) || target.startsWith("#") || target.startsWith("@") || target === "*") {
10014
+ if (target.startsWith("#") || target.startsWith("@") || target === "*") {
9953
10015
  if (targetClients.length === 1) {
9954
10016
  return { client: targetClients[0], targetSpec: target };
9955
10017
  }
@@ -9959,21 +10021,95 @@ async function resolveClient(to) {
9959
10021
  error: `multiple meshes joined; prefix target with "<mesh-slug>:" (joined: ${clients2.map((c) => c.meshSlug).join(", ")})`
9960
10022
  };
9961
10023
  }
10024
+ if (/^[0-9a-f]{8,64}$/.test(target)) {
10025
+ const hits = [];
10026
+ for (const c of targetClients) {
10027
+ const peers = await c.listPeers();
10028
+ for (const p of peers) {
10029
+ if (p.pubkey.startsWith(target)) {
10030
+ hits.push({ mesh: c, pubkey: p.pubkey, displayName: p.displayName });
10031
+ }
10032
+ }
10033
+ }
10034
+ if (hits.length === 1) {
10035
+ return { client: hits[0].mesh, targetSpec: hits[0].pubkey };
10036
+ }
10037
+ if (hits.length > 1) {
10038
+ const lines = hits.map((h) => ` - ${h.displayName} @ ${h.mesh.meshSlug} · pubkey ${h.pubkey.slice(0, 20)}…`).join(`
10039
+ `);
10040
+ return {
10041
+ client: null,
10042
+ targetSpec: target,
10043
+ error: `ambiguous pubkey prefix "${target}" matches ${hits.length} peers:
10044
+ ${lines}
10045
+ Use a longer prefix.`
10046
+ };
10047
+ }
10048
+ if (target.length === 64) {
10049
+ if (targetClients.length === 1) {
10050
+ return { client: targetClients[0], targetSpec: target };
10051
+ }
10052
+ return {
10053
+ client: null,
10054
+ targetSpec: target,
10055
+ error: `multiple meshes joined; prefix target with "<mesh-slug>:" (joined: ${clients2.map((c) => c.meshSlug).join(", ")})`
10056
+ };
10057
+ }
10058
+ return {
10059
+ client: null,
10060
+ targetSpec: target,
10061
+ error: `no online peer's pubkey starts with "${target}".`
10062
+ };
10063
+ }
9962
10064
  const nameLower = target.toLowerCase();
9963
10065
  const candidates = [];
10066
+ const exactMatches = [];
10067
+ const partialMatches = [];
9964
10068
  for (const c of targetClients) {
10069
+ const ownSession = c.getSessionPubkey();
9965
10070
  const peers = await c.listPeers();
9966
10071
  candidates.push({ mesh: c.meshSlug, peers });
9967
- const match = peers.find((p) => p.displayName.toLowerCase() === nameLower);
9968
- if (match)
9969
- return { client: c, targetSpec: match.pubkey };
9970
- const partials = peers.filter((p) => p.displayName.toLowerCase().includes(nameLower));
9971
- if (partials.length === 1) {
9972
- process.stderr.write(`[claudemesh] resolved "${target}" "${partials[0].displayName}" (partial match)
9973
- `);
9974
- return { client: c, targetSpec: partials[0].pubkey };
10072
+ for (const p of peers) {
10073
+ if (ownSession && p.pubkey === ownSession)
10074
+ continue;
10075
+ const nameLow = p.displayName.toLowerCase();
10076
+ if (nameLow === nameLower) {
10077
+ exactMatches.push({ mesh: c, pubkey: p.pubkey, displayName: p.displayName, cwd: p.cwd });
10078
+ } else if (nameLow.includes(nameLower)) {
10079
+ partialMatches.push({ mesh: c, pubkey: p.pubkey, displayName: p.displayName, cwd: p.cwd });
10080
+ }
9975
10081
  }
9976
10082
  }
10083
+ if (exactMatches.length === 1) {
10084
+ return { client: exactMatches[0].mesh, targetSpec: exactMatches[0].pubkey };
10085
+ }
10086
+ if (exactMatches.length > 1) {
10087
+ const lines = exactMatches.map((m) => ` - ${m.displayName} · pubkey ${m.pubkey.slice(0, 16)}…${m.cwd ? ` · cwd ${m.cwd}` : ""}`).join(`
10088
+ `);
10089
+ return {
10090
+ client: null,
10091
+ targetSpec: target,
10092
+ error: `"${target}" is ambiguous — ${exactMatches.length} peers share that display name:
10093
+ ${lines}
10094
+ ` + `Disambiguate by pubkey prefix (e.g. send to "${exactMatches[0].pubkey.slice(0, 12)}…").`
10095
+ };
10096
+ }
10097
+ if (partialMatches.length === 1) {
10098
+ process.stderr.write(`[claudemesh] resolved "${target}" → "${partialMatches[0].displayName}" (partial match)
10099
+ `);
10100
+ return { client: partialMatches[0].mesh, targetSpec: partialMatches[0].pubkey };
10101
+ }
10102
+ if (partialMatches.length > 1) {
10103
+ const lines = partialMatches.map((m) => ` - ${m.displayName} · pubkey ${m.pubkey.slice(0, 16)}…`).join(`
10104
+ `);
10105
+ return {
10106
+ client: null,
10107
+ targetSpec: target,
10108
+ error: `"${target}" partially matches ${partialMatches.length} peers:
10109
+ ${lines}
10110
+ Be more specific, or use a pubkey prefix.`
10111
+ };
10112
+ }
9977
10113
  const known = candidates.flatMap((c) => c.peers.map((p) => `${c.mesh}/${p.displayName}`));
9978
10114
  return {
9979
10115
  client: null,
@@ -10356,7 +10492,7 @@ No peers connected.`);
10356
10492
  const hiddenTag = p.visible === false ? " [hidden]" : "";
10357
10493
  const sameKeyCount = pubkeyCounts.get(p.pubkey) ?? 1;
10358
10494
  const sameKeyTag = sameKeyCount > 1 ? ` [shares key with ${sameKeyCount - 1} other session(s)]` : "";
10359
- return `- ${profileAvatar}**${p.displayName}**${profileTitle} [${p.status}]${localityTag}${hiddenTag}${sameKeyTag}${groupsStr}${metaStr} (${p.pubkey.slice(0, 12)}…)${cwdStr}${summary}`;
10495
+ return `- ${profileAvatar}**${p.displayName}**${profileTitle} [${p.status}]${localityTag}${hiddenTag}${sameKeyTag}${groupsStr}${metaStr} (pubkey: ${p.pubkey.slice(0, 16)}…)${cwdStr}${summary}`;
10360
10496
  });
10361
10497
  sections.push(`${header}
10362
10498
  ${peerLines.join(`
@@ -12337,9 +12473,10 @@ Mesh
12337
12473
  claudemesh delete [slug] delete a mesh (alias: rm)
12338
12474
  claudemesh rename <slug> <name> rename a mesh
12339
12475
  claudemesh share [email] share mesh (invite link / send email)
12340
- claudemesh kick <peer> disconnect a peer (can reconnect)
12341
- claudemesh kick --stale 30m disconnect idle peers (> duration)
12342
- claudemesh kick --all disconnect everyone except you
12476
+ claudemesh disconnect <peer> soft disconnect (peer auto-reconnects)
12477
+ claudemesh kick <peer> end session (peer must manually rejoin)
12478
+ claudemesh kick --stale 30m kick peers idle > duration
12479
+ claudemesh kick --all kick everyone except yourself
12343
12480
  claudemesh ban <peer> kick + permanently revoke (can't rejoin)
12344
12481
  claudemesh unban <peer> lift a ban
12345
12482
  claudemesh bans list banned members
@@ -12481,6 +12618,11 @@ async function main() {
12481
12618
  process.exit(await invite2(positionals[0], { mesh: flags.mesh, json: !!flags.json }));
12482
12619
  break;
12483
12620
  }
12621
+ case "disconnect": {
12622
+ const { runDisconnect: runDisconnect2 } = await Promise.resolve().then(() => (init_kick(), exports_kick));
12623
+ process.exit(await runDisconnect2(positionals[0], { mesh: flags.mesh, stale: flags.stale, all: !!flags.all }));
12624
+ break;
12625
+ }
12484
12626
  case "kick": {
12485
12627
  const { runKick: runKick2 } = await Promise.resolve().then(() => (init_kick(), exports_kick));
12486
12628
  process.exit(await runKick2(positionals[0], { mesh: flags.mesh, stale: flags.stale, all: !!flags.all }));
@@ -12688,4 +12830,4 @@ main().catch((err) => {
12688
12830
  process.exit(EXIT.INTERNAL_ERROR);
12689
12831
  });
12690
12832
 
12691
- //# debugId=8EADAA692E07F7AD64756E2164756E21
12833
+ //# debugId=EB94FD910CC928B864756E2164756E21