claudemesh-cli 1.0.0-alpha.35 → 1.0.0-alpha.37

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.35", env;
91
+ var URLS, VERSION = "1.0.0-alpha.37", env;
92
92
  var init_urls = __esm(() => {
93
93
  URLS = {
94
94
  BROKER: process.env.CLAUDEMESH_BROKER_URL ?? "wss://ic.claudemesh.com/ws",
@@ -1132,6 +1132,7 @@ class BrokerClient {
1132
1132
  outbound = [];
1133
1133
  pushHandlers = new Set;
1134
1134
  pushBuffer = [];
1135
+ pushChain = Promise.resolve();
1135
1136
  listPeersResolvers = new Map;
1136
1137
  stateResolvers = new Map;
1137
1138
  stateListResolvers = new Map;
@@ -2276,6 +2277,22 @@ class BrokerClient {
2276
2277
  return;
2277
2278
  this.ws.send(JSON.stringify(payload));
2278
2279
  }
2280
+ async sendAndWait(payload, timeoutMs = 1e4) {
2281
+ const reqId = `rw-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
2282
+ return new Promise((resolve, reject) => {
2283
+ const timer = setTimeout(() => {
2284
+ this.genericResolvers.delete(reqId);
2285
+ reject(new Error("sendAndWait timeout"));
2286
+ }, timeoutMs);
2287
+ this.genericResolvers.set(reqId, (msg) => {
2288
+ clearTimeout(timer);
2289
+ this.genericResolvers.delete(reqId);
2290
+ resolve(msg);
2291
+ });
2292
+ this.sendRaw({ ...payload, _reqId: reqId });
2293
+ });
2294
+ }
2295
+ genericResolvers = new Map;
2279
2296
  close() {
2280
2297
  this.closed = true;
2281
2298
  this.stopStatsReporting();
@@ -2476,6 +2493,11 @@ class BrokerClient {
2476
2493
  }
2477
2494
  handleServerMessage(msg) {
2478
2495
  const msgReqId = msg._reqId;
2496
+ if (msgReqId && this.genericResolvers.has(msgReqId)) {
2497
+ const resolve = this.genericResolvers.get(msgReqId);
2498
+ resolve(msg);
2499
+ return;
2500
+ }
2479
2501
  if (msg.type === "ack") {
2480
2502
  const pending = this.pendingSends.get(String(msg.id ?? ""));
2481
2503
  if (pending) {
@@ -2496,7 +2518,7 @@ class BrokerClient {
2496
2518
  const nonce = String(msg.nonce ?? "");
2497
2519
  const ciphertext = String(msg.ciphertext ?? "");
2498
2520
  const senderPubkey = String(msg.senderPubkey ?? "");
2499
- (async () => {
2521
+ this.pushChain = this.pushChain.then(async () => {
2500
2522
  const isSystem = msg.subtype === "system" || senderPubkey === "system";
2501
2523
  const kind = isSystem ? "broadcast" : senderPubkey ? "direct" : "unknown";
2502
2524
  let plaintext = null;
@@ -2573,7 +2595,9 @@ class BrokerClient {
2573
2595
  h(push);
2574
2596
  } catch {}
2575
2597
  }
2576
- })();
2598
+ }).catch((e) => {
2599
+ this.debug(`push handler chain error: ${e instanceof Error ? e.message : e}`);
2600
+ });
2577
2601
  return;
2578
2602
  }
2579
2603
  if (msg.type === "state_result") {
@@ -3019,11 +3043,17 @@ class BrokerClient {
3019
3043
  send();
3020
3044
  }
3021
3045
  scheduleReconnect() {
3046
+ if (this.reconnectTimer) {
3047
+ this.debug("reconnect already scheduled — skipping");
3048
+ return;
3049
+ }
3022
3050
  this.setConnStatus("reconnecting");
3023
- const delay = BACKOFF_CAPS[Math.min(this.reconnectAttempt, BACKOFF_CAPS.length - 1)];
3051
+ const base = BACKOFF_CAPS[Math.min(this.reconnectAttempt, BACKOFF_CAPS.length - 1)];
3052
+ const delay = Math.floor(Math.random() * base);
3024
3053
  this.reconnectAttempt += 1;
3025
- this.debug(`reconnect in ${delay}ms (attempt ${this.reconnectAttempt})`);
3054
+ this.debug(`reconnect in ${delay}ms (attempt ${this.reconnectAttempt}, base ${base}ms)`);
3026
3055
  this.reconnectTimer = setTimeout(() => {
3056
+ this.reconnectTimer = null;
3027
3057
  if (this.closed)
3028
3058
  return;
3029
3059
  this.connect().catch((e) => {
@@ -4108,19 +4138,13 @@ async function createMesh2(name, opts) {
4108
4138
  const auth = getStoredToken();
4109
4139
  if (!auth)
4110
4140
  throw new Error("Not signed in");
4111
- let userId = "";
4112
- try {
4113
- const payload = JSON.parse(Buffer.from(auth.session_token.split(".")[1], "base64url").toString());
4114
- userId = payload.sub ?? "";
4115
- } catch {}
4116
- if (!userId)
4117
- throw new Error("Invalid token — run `claudemesh login` again");
4118
4141
  const kp = await generateKeypair3();
4119
4142
  const result = await request({
4120
4143
  path: "/cli/mesh/create",
4121
4144
  method: "POST",
4122
- body: { user_id: userId, name, pubkey: kp.publicKey, ...opts },
4123
- baseUrl: BROKER_HTTP2
4145
+ body: { name, pubkey: kp.publicKey, ...opts },
4146
+ baseUrl: BROKER_HTTP2,
4147
+ token: auth.session_token
4124
4148
  });
4125
4149
  const mesh = {
4126
4150
  meshId: result.id,
@@ -4262,18 +4286,12 @@ async function generateInvite(meshSlug, opts) {
4262
4286
  const auth = getStoredToken();
4263
4287
  if (!auth)
4264
4288
  throw new Error("Not signed in");
4265
- let userId = "";
4266
- try {
4267
- const payload = JSON.parse(Buffer.from(auth.session_token.split(".")[1], "base64url").toString());
4268
- userId = payload.sub ?? "";
4269
- } catch {}
4270
- if (!userId)
4271
- throw new Error("Invalid token");
4272
4289
  return request({
4273
4290
  path: `/cli/mesh/${meshSlug}/invite`,
4274
4291
  method: "POST",
4275
- body: { user_id: userId, email: opts?.email, role: opts?.role },
4276
- baseUrl: BROKER_HTTP3
4292
+ body: { email: opts?.email, role: opts?.role },
4293
+ baseUrl: BROKER_HTTP3,
4294
+ token: auth.session_token
4277
4295
  });
4278
4296
  }
4279
4297
  var BROKER_HTTP3;
@@ -4441,9 +4459,15 @@ async function claimInviteV2(opts) {
4441
4459
  const s = await ensureSodium2();
4442
4460
  const { publicKeyB64, secretKey } = await generateX25519Keypair();
4443
4461
  const publicKeyBytes = s.from_base64(publicKeyB64, s.base64_variants.URLSAFE_NO_PADDING);
4444
- const base = opts.appBaseUrl.replace(/\/$/, "");
4445
4462
  const code = encodeURIComponent(opts.code);
4446
- const url = `${base}/api/public/invites/${code}/claim`;
4463
+ const override = process.env.CLAUDEMESH_CLAIM_URL;
4464
+ let url;
4465
+ if (override) {
4466
+ url = override.replace(/\{code\}/g, code);
4467
+ } else {
4468
+ const brokerBase = process.env.CLAUDEMESH_BROKER_HTTP ?? "https://ic.claudemesh.com";
4469
+ url = `${brokerBase.replace(/\/$/, "")}/invites/${code}/claim`;
4470
+ }
4447
4471
  let res;
4448
4472
  try {
4449
4473
  res = await fetch(url, {
@@ -4672,18 +4696,12 @@ async function runList() {
4672
4696
  let serverMeshes = [];
4673
4697
  if (auth) {
4674
4698
  try {
4675
- let userId = "";
4676
- try {
4677
- const payload = JSON.parse(Buffer.from(auth.session_token.split(".")[1], "base64url").toString());
4678
- userId = payload.sub ?? "";
4679
- } catch {}
4680
- if (userId) {
4681
- const res = await request({
4682
- path: `/cli/meshes?user_id=${userId}`,
4683
- baseUrl: BROKER_HTTP4
4684
- });
4685
- serverMeshes = res.meshes ?? [];
4686
- }
4699
+ const res = await request({
4700
+ path: `/cli/meshes`,
4701
+ baseUrl: BROKER_HTTP4,
4702
+ token: auth.session_token
4703
+ });
4704
+ serverMeshes = res.meshes ?? [];
4687
4705
  } catch {}
4688
4706
  }
4689
4707
  const localSlugs = new Set(config.meshes.map((m) => m.slug));
@@ -6140,6 +6158,149 @@ var init_connect = __esm(() => {
6140
6158
  init_facade();
6141
6159
  });
6142
6160
 
6161
+ // src/commands/kick.ts
6162
+ var exports_kick = {};
6163
+ __export(exports_kick, {
6164
+ runKick: () => runKick
6165
+ });
6166
+ function parseStaleMs(input) {
6167
+ const m = input.match(/^(\d+)(s|m|h)$/);
6168
+ if (!m)
6169
+ return null;
6170
+ const val = parseInt(m[1], 10);
6171
+ const unit = m[2];
6172
+ if (unit === "s")
6173
+ return val * 1000;
6174
+ if (unit === "m")
6175
+ return val * 60000;
6176
+ if (unit === "h")
6177
+ return val * 3600000;
6178
+ return null;
6179
+ }
6180
+ async function runKick(target, opts = {}) {
6181
+ const config = readConfig();
6182
+ const meshSlug = opts.mesh ?? config.meshes[0]?.slug;
6183
+ if (!meshSlug) {
6184
+ render.err("No mesh joined.");
6185
+ return EXIT.NOT_FOUND;
6186
+ }
6187
+ return await withMesh({ meshSlug }, async (client) => {
6188
+ let payload;
6189
+ if (opts.all) {
6190
+ payload = { type: "kick", all: true };
6191
+ } else if (opts.stale) {
6192
+ const ms = parseStaleMs(opts.stale);
6193
+ if (!ms) {
6194
+ render.err(`Invalid stale duration: "${opts.stale}". Use e.g. 30m, 1h, 300s.`);
6195
+ return EXIT.INVALID_ARGS;
6196
+ }
6197
+ payload = { type: "kick", stale: ms };
6198
+ } else if (target) {
6199
+ payload = { type: "kick", target };
6200
+ } else {
6201
+ render.err("Usage: claudemesh kick <peer> | --stale 30m | --all");
6202
+ return EXIT.INVALID_ARGS;
6203
+ }
6204
+ const result = await client.sendAndWait(payload);
6205
+ const kicked = result?.kicked ?? [];
6206
+ if (kicked.length === 0) {
6207
+ render.info("No peers matched.");
6208
+ } else {
6209
+ render.ok(`Kicked ${kicked.length} peer(s): ${kicked.join(", ")}`);
6210
+ }
6211
+ return EXIT.SUCCESS;
6212
+ });
6213
+ }
6214
+ var init_kick = __esm(() => {
6215
+ init_connect();
6216
+ init_facade();
6217
+ init_render();
6218
+ init_exit_codes();
6219
+ });
6220
+
6221
+ // src/commands/ban.ts
6222
+ var exports_ban = {};
6223
+ __export(exports_ban, {
6224
+ runUnban: () => runUnban,
6225
+ runBans: () => runBans,
6226
+ runBan: () => runBan
6227
+ });
6228
+ async function runBan(target, opts = {}) {
6229
+ if (!target) {
6230
+ render.err("Usage: claudemesh ban <peer-name-or-pubkey>");
6231
+ return EXIT.INVALID_ARGS;
6232
+ }
6233
+ const config = readConfig();
6234
+ const meshSlug = opts.mesh ?? config.meshes[0]?.slug;
6235
+ if (!meshSlug) {
6236
+ render.err("No mesh joined.");
6237
+ return EXIT.NOT_FOUND;
6238
+ }
6239
+ return await withMesh({ meshSlug }, async (client) => {
6240
+ const result = await client.sendAndWait({ type: "ban", target });
6241
+ if (result?.banned) {
6242
+ render.ok(`Banned ${result.banned} from ${meshSlug}. They cannot reconnect until unbanned.`);
6243
+ render.hint(`Undo: claudemesh unban ${result.banned} --mesh ${meshSlug}`);
6244
+ } else {
6245
+ render.err(result?.error ?? "ban failed");
6246
+ }
6247
+ return result?.banned ? EXIT.SUCCESS : EXIT.INTERNAL_ERROR;
6248
+ });
6249
+ }
6250
+ async function runUnban(target, opts = {}) {
6251
+ if (!target) {
6252
+ render.err("Usage: claudemesh unban <peer-name-or-pubkey>");
6253
+ return EXIT.INVALID_ARGS;
6254
+ }
6255
+ const config = readConfig();
6256
+ const meshSlug = opts.mesh ?? config.meshes[0]?.slug;
6257
+ if (!meshSlug) {
6258
+ render.err("No mesh joined.");
6259
+ return EXIT.NOT_FOUND;
6260
+ }
6261
+ return await withMesh({ meshSlug }, async (client) => {
6262
+ const result = await client.sendAndWait({ type: "unban", target });
6263
+ if (result?.unbanned) {
6264
+ render.ok(`Unbanned ${result.unbanned} from ${meshSlug}. They can rejoin.`);
6265
+ } else {
6266
+ render.err(result?.error ?? "unban failed");
6267
+ }
6268
+ return result?.unbanned ? EXIT.SUCCESS : EXIT.INTERNAL_ERROR;
6269
+ });
6270
+ }
6271
+ async function runBans(opts = {}) {
6272
+ const config = readConfig();
6273
+ const meshSlug = opts.mesh ?? config.meshes[0]?.slug;
6274
+ if (!meshSlug) {
6275
+ render.err("No mesh joined.");
6276
+ return EXIT.NOT_FOUND;
6277
+ }
6278
+ return await withMesh({ meshSlug }, async (client) => {
6279
+ const result = await client.sendAndWait({ type: "list_bans" });
6280
+ const bans = result?.bans ?? [];
6281
+ if (opts.json) {
6282
+ process.stdout.write(JSON.stringify(bans, null, 2) + `
6283
+ `);
6284
+ return EXIT.SUCCESS;
6285
+ }
6286
+ if (bans.length === 0) {
6287
+ render.info("No banned members.");
6288
+ return EXIT.SUCCESS;
6289
+ }
6290
+ render.section(`banned members on ${meshSlug}`);
6291
+ for (const b of bans) {
6292
+ render.kv([[b.name, `${b.pubkey.slice(0, 16)}… · banned ${new Date(b.revokedAt).toLocaleDateString()}`]]);
6293
+ }
6294
+ return EXIT.SUCCESS;
6295
+ });
6296
+ }
6297
+ var init_ban = __esm(() => {
6298
+ init_connect();
6299
+ init_facade();
6300
+ init_render();
6301
+ init_exit_codes();
6302
+ });
6303
+
6143
6304
  // src/commands/peers.ts
6144
6305
  var exports_peers = {};
6145
6306
  __export(exports_peers, {
@@ -8611,21 +8772,13 @@ async function syncToBroker(meshSlug, grants) {
8611
8772
  const auth = getStoredToken();
8612
8773
  if (!auth)
8613
8774
  return;
8614
- let userId = "";
8615
- try {
8616
- const payload = JSON.parse(Buffer.from(auth.session_token.split(".")[1], "base64url").toString());
8617
- userId = payload.sub ?? "";
8618
- } catch {
8619
- return;
8620
- }
8621
- if (!userId)
8622
- return;
8623
8775
  try {
8624
8776
  await request({
8625
8777
  path: `/cli/mesh/${meshSlug}/grants`,
8626
8778
  method: "POST",
8627
- body: { user_id: userId, grants },
8628
- baseUrl: BROKER_HTTP7
8779
+ body: { grants },
8780
+ baseUrl: BROKER_HTTP7,
8781
+ token: auth.session_token
8629
8782
  });
8630
8783
  } catch (e) {
8631
8784
  render.warn(`broker grant sync failed — client filter still active: ${e instanceof Error ? e.message : e}`);
@@ -8654,8 +8807,11 @@ function resolveCaps(input) {
8654
8807
  async function resolvePeer(meshSlug, name) {
8655
8808
  return await withMesh({ meshSlug }, async (client) => {
8656
8809
  const peers = await client.listPeers();
8657
- const match = peers.find((p) => p.displayName === name || p.pubkey === name || p.pubkey.startsWith(name));
8658
- return match ? { displayName: match.displayName, pubkey: match.pubkey } : null;
8810
+ const match = peers.find((p) => p.displayName === name || p.pubkey === name || p.pubkey.startsWith(name) || p.memberPubkey === name || p.memberPubkey && p.memberPubkey.startsWith(name));
8811
+ if (!match)
8812
+ return null;
8813
+ const key = match.memberPubkey ?? match.pubkey;
8814
+ return { displayName: match.displayName, pubkey: key };
8659
8815
  });
8660
8816
  }
8661
8817
  function pickMesh2(slug) {
@@ -12164,6 +12320,12 @@ Mesh
12164
12320
  claudemesh delete [slug] delete a mesh (alias: rm)
12165
12321
  claudemesh rename <slug> <name> rename a mesh
12166
12322
  claudemesh share [email] share mesh (invite link / send email)
12323
+ claudemesh kick <peer> disconnect a peer (can reconnect)
12324
+ claudemesh kick --stale 30m disconnect idle peers (> duration)
12325
+ claudemesh kick --all disconnect everyone except you
12326
+ claudemesh ban <peer> kick + permanently revoke (can't rejoin)
12327
+ claudemesh unban <peer> lift a ban
12328
+ claudemesh bans list banned members
12167
12329
 
12168
12330
  Messaging
12169
12331
  claudemesh peers see who's online
@@ -12302,6 +12464,26 @@ async function main() {
12302
12464
  process.exit(await invite2(positionals[0], { mesh: flags.mesh, json: !!flags.json }));
12303
12465
  break;
12304
12466
  }
12467
+ case "kick": {
12468
+ const { runKick: runKick2 } = await Promise.resolve().then(() => (init_kick(), exports_kick));
12469
+ process.exit(await runKick2(positionals[0], { mesh: flags.mesh, stale: flags.stale, all: !!flags.all }));
12470
+ break;
12471
+ }
12472
+ case "ban": {
12473
+ const { runBan: runBan2 } = await Promise.resolve().then(() => (init_ban(), exports_ban));
12474
+ process.exit(await runBan2(positionals[0], { mesh: flags.mesh }));
12475
+ break;
12476
+ }
12477
+ case "unban": {
12478
+ const { runUnban: runUnban2 } = await Promise.resolve().then(() => (init_ban(), exports_ban));
12479
+ process.exit(await runUnban2(positionals[0], { mesh: flags.mesh }));
12480
+ break;
12481
+ }
12482
+ case "bans": {
12483
+ const { runBans: runBans2 } = await Promise.resolve().then(() => (init_ban(), exports_ban));
12484
+ process.exit(await runBans2({ mesh: flags.mesh, json: !!flags.json }));
12485
+ break;
12486
+ }
12305
12487
  case "peers": {
12306
12488
  const { runPeers: runPeers2 } = await Promise.resolve().then(() => (init_peers(), exports_peers));
12307
12489
  await runPeers2({ mesh: flags.mesh, json: !!flags.json });
@@ -12489,4 +12671,4 @@ main().catch((err) => {
12489
12671
  process.exit(EXIT.INTERNAL_ERROR);
12490
12672
  });
12491
12673
 
12492
- //# debugId=5BA2A4C8E9E8C67464756E2164756E21
12674
+ //# debugId=95D625F3B4CFD9EA64756E2164756E21