claudemesh-cli 1.0.0 → 1.0.2

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", env;
91
+ var URLS, VERSION = "1.0.2", 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) {
@@ -3156,6 +3170,75 @@ var init_facade8 = __esm(() => {
3156
3170
  init_errors3();
3157
3171
  });
3158
3172
 
3173
+ // src/ui/render.ts
3174
+ var OUT, ERR, INDENT = " ", render;
3175
+ var init_render = __esm(() => {
3176
+ init_styles();
3177
+ OUT = process.stdout;
3178
+ ERR = process.stderr;
3179
+ render = {
3180
+ blank() {
3181
+ OUT.write(`
3182
+ `);
3183
+ },
3184
+ ok(msg, detail) {
3185
+ const d = detail ? ` ${dim("(" + detail + ")")}` : "";
3186
+ OUT.write(`${INDENT}${green(icons.check)} ${msg}${d}
3187
+ `);
3188
+ },
3189
+ warn(msg, hint) {
3190
+ OUT.write(`${INDENT}${yellow(icons.warn)} ${msg}
3191
+ `);
3192
+ if (hint)
3193
+ OUT.write(`${INDENT} ${dim(hint)}
3194
+ `);
3195
+ },
3196
+ err(msg, hint) {
3197
+ ERR.write(`${INDENT}${red(icons.cross)} ${msg}
3198
+ `);
3199
+ if (hint)
3200
+ ERR.write(`${INDENT} ${dim(hint)}
3201
+ `);
3202
+ },
3203
+ info(msg) {
3204
+ OUT.write(`${INDENT}${msg}
3205
+ `);
3206
+ },
3207
+ section(title) {
3208
+ OUT.write(`
3209
+ ${INDENT}${dim("—")} ${clay(title)}
3210
+
3211
+ `);
3212
+ },
3213
+ heading(title) {
3214
+ OUT.write(`${INDENT}${bold(title)}
3215
+ `);
3216
+ },
3217
+ kv(pairs, opts) {
3218
+ const pad = opts?.padTo ?? Math.max(...pairs.map(([k]) => k.length)) + 2;
3219
+ for (const [k, v] of pairs) {
3220
+ OUT.write(`${INDENT}${dim(k.padEnd(pad, " "))}${v}
3221
+ `);
3222
+ }
3223
+ },
3224
+ code(snippet) {
3225
+ for (const line of snippet.split(`
3226
+ `)) {
3227
+ OUT.write(`${INDENT} ${cyan(line)}
3228
+ `);
3229
+ }
3230
+ },
3231
+ link(url) {
3232
+ OUT.write(`${INDENT}${clay(url)}
3233
+ `);
3234
+ },
3235
+ hint(msg) {
3236
+ OUT.write(`${INDENT}${dim(icons.arrow + " " + msg)}
3237
+ `);
3238
+ }
3239
+ };
3240
+ });
3241
+
3159
3242
  // src/ui/screen.ts
3160
3243
  import { createInterface as createInterface2 } from "node:readline";
3161
3244
  function termSize() {
@@ -3512,7 +3595,7 @@ async function runLaunch(flags, rawArgs) {
3512
3595
  claudeArgs: claudePassthrough
3513
3596
  };
3514
3597
  if (args.joinLink) {
3515
- console.log("Joining mesh...");
3598
+ render.info(dim("Joining mesh"));
3516
3599
  const invite = await parseInviteLink(args.joinLink);
3517
3600
  const keypair = await generateKeypair();
3518
3601
  const displayName2 = args.name ?? process.env.USER ?? process.env.USERNAME ?? hostname2();
@@ -3537,7 +3620,7 @@ async function runLaunch(flags, rawArgs) {
3537
3620
  });
3538
3621
  const { writeConfig: writeConfig2 } = await Promise.resolve().then(() => (init_facade(), exports_facade));
3539
3622
  writeConfig2(config2);
3540
- console.log(`✓ Joined "${invite.payload.mesh_slug}"${enroll.alreadyMember ? " (already member)" : ""}`);
3623
+ render.ok(`joined ${bold(invite.payload.mesh_slug)}`, enroll.alreadyMember ? "already member" : undefined);
3541
3624
  }
3542
3625
  const config = readConfig();
3543
3626
  let justSynced = false;
@@ -3608,14 +3691,14 @@ async function runLaunch(flags, rawArgs) {
3608
3691
  `);
3609
3692
  }
3610
3693
  if (config.meshes.length === 0) {
3611
- console.error("No meshes joined. Run `claudemesh join <url>` or use --join <url>.");
3694
+ render.err("No meshes joined.", "Run `claudemesh join <url>` or use --join <url>.");
3612
3695
  process.exit(1);
3613
3696
  }
3614
3697
  let mesh;
3615
3698
  if (args.meshSlug) {
3616
3699
  const found = config.meshes.find((m) => m.slug === args.meshSlug);
3617
3700
  if (!found) {
3618
- console.error(`Mesh "${args.meshSlug}" not found. Joined: ${config.meshes.map((m) => m.slug).join(", ")}`);
3701
+ render.err(`Mesh "${args.meshSlug}" not found.`, `Joined: ${config.meshes.map((m) => m.slug).join(", ")}`);
3619
3702
  process.exit(1);
3620
3703
  }
3621
3704
  mesh = found;
@@ -3838,9 +3921,9 @@ async function runLaunch(flags, rawArgs) {
3838
3921
  if (result.error) {
3839
3922
  const err = result.error;
3840
3923
  if (err.code === "ENOENT") {
3841
- console.error("`claude` not found on PATH. Install Claude Code first.");
3924
+ render.err("`claude` not found on PATH.", "Install Claude Code first.");
3842
3925
  } else {
3843
- console.error(`✗ failed to launch claude: ${err.message}`);
3926
+ render.err(`failed to launch claude: ${err.message}`);
3844
3927
  }
3845
3928
  process.exit(1);
3846
3929
  }
@@ -3855,6 +3938,7 @@ var init_launch = __esm(() => {
3855
3938
  init_facade6();
3856
3939
  init_facade5();
3857
3940
  init_facade8();
3941
+ init_render();
3858
3942
  init_styles();
3859
3943
  init_screen();
3860
3944
  init_spinner();
@@ -3904,13 +3988,13 @@ function prompt(question) {
3904
3988
  });
3905
3989
  }
3906
3990
  async function loginWithToken() {
3907
- console.log(`
3908
- Paste a token from ${dim(URLS.API_BASE + "/token")}`);
3909
- console.log(` ${dim("Generate one in your browser, then paste it here.")}
3910
- `);
3991
+ render.blank();
3992
+ render.info(`Paste a token from ${dim(URLS.API_BASE + "/token")}`);
3993
+ render.info(dim("Generate one in your browser, then paste it here."));
3994
+ render.blank();
3911
3995
  const token = await prompt(" Token: ");
3912
3996
  if (!token) {
3913
- console.error(` ${icons.cross} No token provided.`);
3997
+ render.err("No token provided.");
3914
3998
  return EXIT.AUTH_FAILED;
3915
3999
  }
3916
4000
  let user = { id: "", display_name: "", email: "" };
@@ -3919,7 +4003,7 @@ async function loginWithToken() {
3919
4003
  if (parts[1]) {
3920
4004
  const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
3921
4005
  if (payload.exp && payload.exp < Date.now() / 1000) {
3922
- console.error(` ${icons.cross} Token expired. Generate a new one.`);
4006
+ render.err("Token expired.", "Generate a new one.");
3923
4007
  return EXIT.AUTH_FAILED;
3924
4008
  }
3925
4009
  user = {
@@ -3929,11 +4013,11 @@ async function loginWithToken() {
3929
4013
  };
3930
4014
  }
3931
4015
  } catch {
3932
- console.error(` ${icons.cross} Invalid token format.`);
4016
+ render.err("Invalid token format.");
3933
4017
  return EXIT.AUTH_FAILED;
3934
4018
  }
3935
4019
  storeToken({ session_token: token, user, token_source: "manual" });
3936
- console.log(` ${green(icons.check)} Signed in as ${user.display_name || user.email || "user"}.`);
4020
+ render.ok(`signed in as ${bold(user.display_name || user.email || "user")}`);
3937
4021
  return EXIT.SUCCESS;
3938
4022
  }
3939
4023
  async function syncMeshes(token) {
@@ -3941,7 +4025,7 @@ async function syncMeshes(token) {
3941
4025
  const meshes = await exports_my.getMeshes(token);
3942
4026
  if (meshes.length > 0) {
3943
4027
  const names = meshes.map((m) => m.slug).join(", ");
3944
- console.log(` ${green(icons.check)} Synced ${meshes.length} mesh${meshes.length === 1 ? "" : "es"}: ${names}`);
4028
+ render.ok(`synced ${meshes.length} mesh${meshes.length === 1 ? "" : "es"}`, names);
3945
4029
  }
3946
4030
  } catch {}
3947
4031
  }
@@ -3949,23 +4033,27 @@ async function login() {
3949
4033
  const existing = getStoredToken();
3950
4034
  if (existing) {
3951
4035
  const name = existing.user.display_name || existing.user.email || "unknown";
3952
- console.log(`
3953
- Already signed in as ${bold(name)}.`);
3954
- console.log("");
3955
- console.log(` ${bold("1)")} Continue as ${name}`);
3956
- console.log(` ${bold("2)")} Sign in via browser`);
3957
- console.log(` ${bold("3)")} Paste a token from ${dim("claudemesh.com/token")}`);
3958
- console.log(` ${bold("4)")} Sign out`);
3959
- console.log("");
4036
+ render.blank();
4037
+ render.info(`Already signed in as ${bold(name)}.`);
4038
+ render.blank();
4039
+ process.stdout.write(` ${bold("1)")} Continue as ${name}
4040
+ `);
4041
+ process.stdout.write(` ${bold("2)")} Sign in via browser
4042
+ `);
4043
+ process.stdout.write(` ${bold("3)")} Paste a token from ${dim("claudemesh.com/token")}
4044
+ `);
4045
+ process.stdout.write(` ${bold("4)")} Sign out
4046
+ `);
4047
+ render.blank();
3960
4048
  const choice = await prompt(" Choice [1]: ") || "1";
3961
4049
  if (choice === "1") {
3962
- console.log(`
3963
- ${green(icons.check)} Continuing as ${name}.`);
4050
+ render.blank();
4051
+ render.ok(`continuing as ${bold(name)}`);
3964
4052
  return EXIT.SUCCESS;
3965
4053
  }
3966
4054
  if (choice === "4") {
3967
4055
  clearToken();
3968
- console.log(` ${green(icons.check)} Signed out.`);
4056
+ render.ok("signed out");
3969
4057
  return EXIT.SUCCESS;
3970
4058
  }
3971
4059
  if (choice === "3") {
@@ -3973,14 +4061,16 @@ async function login() {
3973
4061
  return loginWithToken();
3974
4062
  }
3975
4063
  clearToken();
3976
- console.log(` ${dim("Signing in…")}`);
4064
+ render.info(dim("Signing in…"));
3977
4065
  } else {
3978
- console.log(`
3979
- ${bold("claudemesh")} — sign in to connect your terminal`);
3980
- console.log("");
3981
- console.log(` ${bold("1)")} Sign in via browser ${dim("(opens automatically)")}`);
3982
- console.log(` ${bold("2)")} Paste a token from ${dim("claudemesh.com/token")}`);
3983
- console.log("");
4066
+ render.blank();
4067
+ render.heading(`${bold("claudemesh")} — sign in to connect your terminal`);
4068
+ render.blank();
4069
+ process.stdout.write(` ${bold("1)")} Sign in via browser ${dim("(opens automatically)")}
4070
+ `);
4071
+ process.stdout.write(` ${bold("2)")} Paste a token from ${dim("claudemesh.com/token")}
4072
+ `);
4073
+ render.blank();
3984
4074
  const choice = await prompt(" Choice [1]: ") || "1";
3985
4075
  if (choice === "2") {
3986
4076
  return loginWithToken();
@@ -3988,91 +4078,23 @@ async function login() {
3988
4078
  }
3989
4079
  try {
3990
4080
  const result = await loginWithDeviceCode();
3991
- console.log(` ${green(icons.check)} Signed in as ${result.user.display_name}.`);
4081
+ render.ok(`signed in as ${bold(result.user.display_name)}`);
3992
4082
  await syncMeshes(result.session_token);
3993
4083
  return EXIT.SUCCESS;
3994
4084
  } catch (err) {
3995
- console.error(` ${icons.cross} Login failed: ${err instanceof Error ? err.message : err}`);
4085
+ render.err(`Login failed: ${err instanceof Error ? err.message : err}`);
3996
4086
  return EXIT.AUTH_FAILED;
3997
4087
  }
3998
4088
  }
3999
4089
  var init_login = __esm(() => {
4000
4090
  init_facade6();
4001
4091
  init_facade3();
4092
+ init_render();
4002
4093
  init_styles();
4003
4094
  init_exit_codes();
4004
4095
  init_urls();
4005
4096
  });
4006
4097
 
4007
- // src/ui/render.ts
4008
- var OUT, ERR, INDENT = " ", render;
4009
- var init_render = __esm(() => {
4010
- init_styles();
4011
- OUT = process.stdout;
4012
- ERR = process.stderr;
4013
- render = {
4014
- blank() {
4015
- OUT.write(`
4016
- `);
4017
- },
4018
- ok(msg, detail) {
4019
- const d = detail ? ` ${dim("(" + detail + ")")}` : "";
4020
- OUT.write(`${INDENT}${green(icons.check)} ${msg}${d}
4021
- `);
4022
- },
4023
- warn(msg, hint) {
4024
- OUT.write(`${INDENT}${yellow(icons.warn)} ${msg}
4025
- `);
4026
- if (hint)
4027
- OUT.write(`${INDENT} ${dim(hint)}
4028
- `);
4029
- },
4030
- err(msg, hint) {
4031
- ERR.write(`${INDENT}${red(icons.cross)} ${msg}
4032
- `);
4033
- if (hint)
4034
- ERR.write(`${INDENT} ${dim(hint)}
4035
- `);
4036
- },
4037
- info(msg) {
4038
- OUT.write(`${INDENT}${msg}
4039
- `);
4040
- },
4041
- section(title) {
4042
- OUT.write(`
4043
- ${INDENT}${dim("—")} ${clay(title)}
4044
-
4045
- `);
4046
- },
4047
- heading(title) {
4048
- OUT.write(`${INDENT}${bold(title)}
4049
- `);
4050
- },
4051
- kv(pairs, opts) {
4052
- const pad = opts?.padTo ?? Math.max(...pairs.map(([k]) => k.length)) + 2;
4053
- for (const [k, v] of pairs) {
4054
- OUT.write(`${INDENT}${dim(k.padEnd(pad, " "))}${v}
4055
- `);
4056
- }
4057
- },
4058
- code(snippet) {
4059
- for (const line of snippet.split(`
4060
- `)) {
4061
- OUT.write(`${INDENT} ${cyan(line)}
4062
- `);
4063
- }
4064
- },
4065
- link(url) {
4066
- OUT.write(`${INDENT}${clay(url)}
4067
- `);
4068
- },
4069
- hint(msg) {
4070
- OUT.write(`${INDENT}${dim(icons.arrow + " " + msg)}
4071
- `);
4072
- }
4073
- };
4074
- });
4075
-
4076
4098
  // src/commands/welcome.ts
4077
4099
  var exports_welcome = {};
4078
4100
  __export(exports_welcome, {
@@ -4237,17 +4259,17 @@ __export(exports_new, {
4237
4259
  });
4238
4260
  async function newMesh(name, opts) {
4239
4261
  if (!name) {
4240
- console.error(" Usage: claudemesh mesh create <name>");
4262
+ render.err("Usage: claudemesh create <name>");
4241
4263
  return EXIT.INVALID_ARGS;
4242
4264
  }
4243
4265
  if (!getStoredToken()) {
4244
- console.log(dim(` Not signed in — starting login…
4245
- `));
4266
+ render.info(dim("not signed in — starting login…"));
4267
+ render.blank();
4246
4268
  const { login: login2 } = await Promise.resolve().then(() => (init_login(), exports_login));
4247
4269
  const loginResult = await login2();
4248
4270
  if (loginResult !== EXIT.SUCCESS)
4249
4271
  return loginResult;
4250
- console.log("");
4272
+ render.blank();
4251
4273
  }
4252
4274
  try {
4253
4275
  const result = await createMesh2(name, {
@@ -4256,22 +4278,24 @@ async function newMesh(name, opts) {
4256
4278
  });
4257
4279
  if (opts.json) {
4258
4280
  console.log(JSON.stringify({ schema_version: "1.0", ...result }, null, 2));
4259
- } else {
4260
- console.log(`
4261
- ${green(icons.check)} Created "${result.slug}" (id: ${result.id})`);
4262
- console.log(` ${green(icons.check)} You're the owner`);
4263
- console.log(` ${green(icons.check)} Joined locally`);
4264
- console.log(`
4265
- Share with: claudemesh mesh share
4266
- `);
4281
+ return EXIT.SUCCESS;
4267
4282
  }
4283
+ render.section(`created ${bold(result.slug)}`);
4284
+ render.kv([
4285
+ ["id", dim(result.id)],
4286
+ ["role", clay("owner")],
4287
+ ["local", "joined"]
4288
+ ]);
4289
+ render.blank();
4290
+ render.hint(`share with: ${bold("claudemesh share")}`);
4291
+ render.blank();
4268
4292
  return EXIT.SUCCESS;
4269
4293
  } catch (err) {
4270
4294
  const msg = err instanceof Error ? err.message : String(err);
4271
4295
  if (msg.includes("409") || msg.includes("already exists")) {
4272
- console.error(` ${icons.cross} A mesh with this name already exists. Try a different name.`);
4296
+ render.err("A mesh with this name already exists.", "Try a different name.");
4273
4297
  } else {
4274
- console.error(` ${icons.cross} Failed: ${msg}`);
4298
+ render.err(`Failed: ${msg}`);
4275
4299
  }
4276
4300
  return EXIT.INTERNAL_ERROR;
4277
4301
  }
@@ -4279,6 +4303,7 @@ async function newMesh(name, opts) {
4279
4303
  var init_new = __esm(() => {
4280
4304
  init_facade10();
4281
4305
  init_facade6();
4306
+ init_render();
4282
4307
  init_styles();
4283
4308
  init_exit_codes();
4284
4309
  });
@@ -4723,24 +4748,20 @@ async function runList() {
4723
4748
  const serverSlugs = new Set(serverMeshes.map((m) => m.slug));
4724
4749
  const allSlugs = new Set([...localSlugs, ...serverSlugs]);
4725
4750
  if (allSlugs.size === 0) {
4726
- console.log(`
4727
- No meshes yet.
4728
- `);
4729
- console.log(" Create one: claudemesh mesh create <name>");
4730
- console.log(` Join one: claudemesh mesh add <invite-url>
4731
- `);
4751
+ render.section("no meshes yet");
4752
+ render.info(`${dim("create one:")} ${bold("claudemesh create")} ${clay("<name>")}`);
4753
+ render.info(`${dim("join one:")} ${bold("claudemesh")} ${clay("<invite-url>")}`);
4754
+ render.blank();
4732
4755
  return;
4733
4756
  }
4734
- console.log(`
4735
- Your meshes:
4736
- `);
4757
+ render.section(`your meshes (${allSlugs.size})`);
4737
4758
  for (const slug of allSlugs) {
4738
4759
  const local = config.meshes.find((m) => m.slug === slug);
4739
4760
  const server = serverMeshes.find((m) => m.slug === slug);
4740
4761
  const name = server?.name ?? local?.name ?? slug;
4741
4762
  const role = server?.role ?? "member";
4742
4763
  const isOwner = server?.is_owner ?? false;
4743
- const roleLabel = isOwner ? "owner" : role;
4764
+ const roleLabel = isOwner ? clay("owner") : dim(role);
4744
4765
  const memberCount = server?.member_count;
4745
4766
  const activePeers = server?.active_peers ?? 0;
4746
4767
  const inLocal = localSlugs.has(slug);
@@ -4759,15 +4780,18 @@ async function runList() {
4759
4780
  }
4760
4781
  const memberInfo = memberCount ? dim(`${memberCount} member${memberCount !== 1 ? "s" : ""}`) : "";
4761
4782
  const parts = [roleLabel, memberInfo, status].filter(Boolean);
4762
- console.log(` ${icon} ${bold(name)} ${dim(slug)}`);
4763
- console.log(` ${parts.join(" · ")}`);
4783
+ process.stdout.write(` ${icon} ${bold(name)} ${dim(slug)}
4784
+ `);
4785
+ process.stdout.write(` ${parts.join(dim(" · "))}
4786
+ `);
4764
4787
  }
4765
- console.log("");
4788
+ process.stdout.write(`
4789
+ `);
4766
4790
  if (serverMeshes.some((m) => !localSlugs.has(m.slug))) {
4767
- console.log(dim(" = server only — run `claudemesh mesh add` to use locally"));
4791
+ render.hint(`${dim("○")} = server only — run ${bold("claudemesh join")} to use locally`);
4768
4792
  }
4769
- console.log(dim(` Config: ${getConfigPath()}`));
4770
- console.log("");
4793
+ render.hint(`config: ${dim(getConfigPath())}`);
4794
+ render.blank();
4771
4795
  }
4772
4796
  var BROKER_HTTP4;
4773
4797
  var init_list2 = __esm(() => {
@@ -4776,6 +4800,7 @@ var init_list2 = __esm(() => {
4776
4800
  init_facade3();
4777
4801
  init_urls();
4778
4802
  init_styles();
4803
+ init_render();
4779
4804
  BROKER_HTTP4 = URLS.BROKER.replace("wss://", "https://").replace("ws://", "http://").replace("/ws", "");
4780
4805
  });
4781
4806
 
@@ -4802,11 +4827,12 @@ function getUserId(token) {
4802
4827
  return "";
4803
4828
  }
4804
4829
  }
4805
- async function isOwner(slug, userId) {
4830
+ async function isOwner(slug, auth) {
4806
4831
  try {
4807
4832
  const res = await request({
4808
- path: `/cli/meshes?user_id=${userId}`,
4809
- baseUrl: BROKER_HTTP5
4833
+ path: `/cli/meshes`,
4834
+ baseUrl: BROKER_HTTP5,
4835
+ token: auth.session_token
4810
4836
  });
4811
4837
  return res.meshes?.find((m) => m.slug === slug)?.is_owner ?? false;
4812
4838
  } catch {
@@ -4817,86 +4843,88 @@ async function deleteMesh(slug, opts = {}) {
4817
4843
  const config = readConfig();
4818
4844
  if (!slug) {
4819
4845
  if (config.meshes.length === 0) {
4820
- console.error(" No meshes to remove.");
4846
+ render.err("No meshes to remove.");
4821
4847
  return EXIT.NOT_FOUND;
4822
4848
  }
4823
- console.log(`
4824
- Select mesh to remove:
4825
- `);
4849
+ render.section("select mesh to remove");
4826
4850
  config.meshes.forEach((m, i) => {
4827
- console.log(` ${bold(String(i + 1) + ")")} ${m.slug} ${dim("(" + m.name + ")")}`);
4851
+ process.stdout.write(` ${bold(String(i + 1) + ")")} ${clay(m.slug)} ${dim("(" + m.name + ")")}
4852
+ `);
4828
4853
  });
4829
- console.log("");
4830
- const choice = await prompt3(" Choice: ");
4854
+ render.blank();
4855
+ const choice = await prompt3(` ${dim("choice:")} `);
4831
4856
  const idx = parseInt(choice, 10) - 1;
4832
4857
  if (idx < 0 || idx >= config.meshes.length) {
4833
- console.log(" Cancelled.");
4858
+ render.info(dim("cancelled."));
4834
4859
  return EXIT.USER_CANCELLED;
4835
4860
  }
4836
4861
  slug = config.meshes[idx].slug;
4837
4862
  }
4838
4863
  const auth = getStoredToken();
4839
4864
  const userId = auth ? getUserId(auth.session_token) : "";
4840
- const ownerCheck = userId ? await isOwner(slug, userId) : false;
4865
+ const ownerCheck = auth ? await isOwner(slug, auth) : false;
4841
4866
  if (!opts.yes) {
4842
- console.log(`
4843
- ${bold(slug)}
4844
- `);
4867
+ render.section(slug);
4845
4868
  if (ownerCheck) {
4846
- console.log(` ${bold("1)")} Remove from this device only ${dim("(keep on server)")}`);
4847
- console.log(` ${bold("2)")} ${red("Delete everywhere")} ${dim("(removes for all members)")}`);
4848
- console.log(` ${bold("3)")} Cancel`);
4849
- console.log("");
4850
- const choice = await prompt3(" Choice [1]: ") || "1";
4869
+ process.stdout.write(` ${bold("1)")} remove from this device only ${dim("(keep on server)")}
4870
+ `);
4871
+ process.stdout.write(` ${bold("2)")} ${red("delete everywhere")} ${dim("(removes for all members)")}
4872
+ `);
4873
+ process.stdout.write(` ${bold("3)")} cancel
4874
+ `);
4875
+ render.blank();
4876
+ const choice = await prompt3(` ${dim("choice [1]:")} `) || "1";
4851
4877
  if (choice === "3") {
4852
- console.log(" Cancelled.");
4878
+ render.info(dim("cancelled."));
4853
4879
  return EXIT.USER_CANCELLED;
4854
4880
  }
4855
4881
  if (choice === "2") {
4856
- console.log(`
4857
- ${red("Warning:")} This will delete ${bold(slug)} for all members.`);
4858
- const confirm = await prompt3(` Type "${slug}" to confirm: `);
4882
+ render.blank();
4883
+ render.warn(`this will delete ${bold(slug)} for all members.`);
4884
+ const confirm = await prompt3(` ${dim(`type "${slug}" to confirm:`)} `);
4859
4885
  if (confirm.toLowerCase() !== slug.toLowerCase()) {
4860
- console.log(" Cancelled.");
4886
+ render.info(dim("cancelled."));
4861
4887
  return EXIT.USER_CANCELLED;
4862
4888
  }
4863
4889
  try {
4864
4890
  await request({
4865
4891
  path: `/cli/mesh/${slug}`,
4866
4892
  method: "DELETE",
4867
- body: { user_id: userId },
4868
- baseUrl: BROKER_HTTP5
4893
+ baseUrl: BROKER_HTTP5,
4894
+ token: auth?.session_token,
4895
+ body: { user_id: userId }
4869
4896
  });
4870
- console.log(` ${green(icons.check)} Deleted "${slug}" from server.`);
4897
+ render.ok(`deleted ${bold(slug)} from server.`);
4871
4898
  } catch (err) {
4872
- const msg = err instanceof Error ? err.message : String(err);
4873
- console.error(` ${icons.cross} Server delete failed: ${msg}`);
4899
+ render.err(`server delete failed: ${err instanceof Error ? err.message : String(err)}`);
4874
4900
  }
4875
4901
  leaveMesh(slug);
4876
- console.log(` ${green(icons.check)} Removed from local config.`);
4902
+ render.ok("removed from local config.");
4877
4903
  return EXIT.SUCCESS;
4878
4904
  }
4879
4905
  } else {
4880
- console.log(` ${bold("1)")} Remove from this device ${dim("(you can re-add later)")}`);
4881
- console.log(` ${bold("2)")} Cancel`);
4882
- if (!ownerCheck && userId) {
4883
- console.log(dim(`
4884
- ${yellow(icons.warn)} Only the mesh owner can delete it from the server.`));
4906
+ process.stdout.write(` ${bold("1)")} remove from this device ${dim("(you can re-add later)")}
4907
+ `);
4908
+ process.stdout.write(` ${bold("2)")} cancel
4909
+ `);
4910
+ if (userId) {
4911
+ render.blank();
4912
+ render.warn("only the mesh owner can delete it from the server.");
4885
4913
  }
4886
- console.log("");
4887
- const choice = await prompt3(" Choice [1]: ") || "1";
4914
+ render.blank();
4915
+ const choice = await prompt3(` ${dim("choice [1]:")} `) || "1";
4888
4916
  if (choice === "2") {
4889
- console.log(" Cancelled.");
4917
+ render.info(dim("cancelled."));
4890
4918
  return EXIT.USER_CANCELLED;
4891
4919
  }
4892
4920
  }
4893
4921
  }
4894
4922
  const removed = leaveMesh(slug);
4895
4923
  if (removed) {
4896
- console.log(` ${green(icons.check)} Removed "${slug}" from this device.`);
4897
- console.log(dim(` Re-add anytime with: claudemesh mesh add <invite-url>`));
4924
+ render.ok(`removed ${bold(slug)} from this device.`);
4925
+ render.hint(`re-add anytime with: ${bold("claudemesh")} ${clay("<invite-url>")}`);
4898
4926
  } else {
4899
- console.error(` Mesh "${slug}" not found in local config.`);
4927
+ render.err(`mesh "${slug}" not found in local config.`);
4900
4928
  }
4901
4929
  return EXIT.SUCCESS;
4902
4930
  }
@@ -4907,6 +4935,7 @@ var init_delete_mesh = __esm(() => {
4907
4935
  init_facade6();
4908
4936
  init_facade3();
4909
4937
  init_urls();
4938
+ init_render();
4910
4939
  init_styles();
4911
4940
  init_exit_codes();
4912
4941
  BROKER_HTTP5 = URLS.BROKER.replace("wss://", "https://").replace("ws://", "http://").replace("/ws", "");
@@ -6164,6 +6193,25 @@ async function withMesh(opts, fn) {
6164
6193
  await client.connect();
6165
6194
  const result = await fn(client, mesh);
6166
6195
  return result;
6196
+ } catch (e) {
6197
+ if (client.terminalClose) {
6198
+ const { code, reason } = client.terminalClose;
6199
+ if (code === 4002) {
6200
+ console.error(`
6201
+ ✘ ${reason}
6202
+ `);
6203
+ } else if (code === 4001) {
6204
+ console.error(`
6205
+ ✘ Kicked from this mesh. Run \`claudemesh\` to rejoin.
6206
+ `);
6207
+ } else {
6208
+ console.error(`
6209
+ ✘ Broker closed connection: ${reason}
6210
+ `);
6211
+ }
6212
+ process.exit(1);
6213
+ }
6214
+ throw e;
6167
6215
  } finally {
6168
6216
  client.close();
6169
6217
  }
@@ -6176,7 +6224,8 @@ var init_connect = __esm(() => {
6176
6224
  // src/commands/kick.ts
6177
6225
  var exports_kick = {};
6178
6226
  __export(exports_kick, {
6179
- runKick: () => runKick
6227
+ runKick: () => runKick,
6228
+ runDisconnect: () => runDisconnect
6180
6229
  });
6181
6230
  function parseStaleMs(input) {
6182
6231
  const m = input.match(/^(\d+)(s|m|h)$/);
@@ -6192,36 +6241,63 @@ function parseStaleMs(input) {
6192
6241
  return val * 3600000;
6193
6242
  return null;
6194
6243
  }
6195
- async function runKick(target, opts = {}) {
6244
+ function buildPayload(kind, target, opts) {
6245
+ if (opts.all)
6246
+ return { type: kind, all: true };
6247
+ if (opts.stale) {
6248
+ const ms = parseStaleMs(opts.stale);
6249
+ if (!ms)
6250
+ return { error: `Invalid stale duration: "${opts.stale}". Use e.g. 30m, 1h, 300s.` };
6251
+ return { type: kind, stale: ms };
6252
+ }
6253
+ if (target)
6254
+ return { type: kind, target };
6255
+ return { error: `Usage: claudemesh ${kind} <peer> | --stale 30m | --all` };
6256
+ }
6257
+ async function runDisconnect(target, opts = {}) {
6196
6258
  const config = readConfig();
6197
6259
  const meshSlug = opts.mesh ?? config.meshes[0]?.slug;
6198
6260
  if (!meshSlug) {
6199
6261
  render.err("No mesh joined.");
6200
6262
  return EXIT.NOT_FOUND;
6201
6263
  }
6264
+ const built = buildPayload("disconnect", target, opts);
6265
+ if ("error" in built) {
6266
+ render.err(String(built.error));
6267
+ return EXIT.INVALID_ARGS;
6268
+ }
6202
6269
  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;
6270
+ const result = await client.sendAndWait(built);
6271
+ const peers = result?.affected ?? result?.kicked ?? [];
6272
+ if (peers.length === 0)
6273
+ render.info("No peers matched.");
6274
+ else {
6275
+ render.ok(`Disconnected ${peers.length} peer(s): ${peers.join(", ")}`);
6276
+ render.hint("They will auto-reconnect within seconds. For a session-ending kick, use `claudemesh kick`.");
6218
6277
  }
6219
- const result = await client.sendAndWait(payload);
6220
- const kicked = result?.kicked ?? [];
6221
- if (kicked.length === 0) {
6278
+ return EXIT.SUCCESS;
6279
+ });
6280
+ }
6281
+ async function runKick(target, opts = {}) {
6282
+ const config = readConfig();
6283
+ const meshSlug = opts.mesh ?? config.meshes[0]?.slug;
6284
+ if (!meshSlug) {
6285
+ render.err("No mesh joined.");
6286
+ return EXIT.NOT_FOUND;
6287
+ }
6288
+ const built = buildPayload("kick", target, opts);
6289
+ if ("error" in built) {
6290
+ render.err(String(built.error));
6291
+ return EXIT.INVALID_ARGS;
6292
+ }
6293
+ return await withMesh({ meshSlug }, async (client) => {
6294
+ const result = await client.sendAndWait(built);
6295
+ const peers = result?.affected ?? result?.kicked ?? [];
6296
+ if (peers.length === 0)
6222
6297
  render.info("No peers matched.");
6223
- } else {
6224
- render.ok(`Kicked ${kicked.length} peer(s): ${kicked.join(", ")}`);
6298
+ else {
6299
+ render.ok(`Kicked ${peers.length} peer(s): ${peers.join(", ")}`);
6300
+ render.hint("Their Claude Code session ended. They can rejoin anytime by running `claudemesh`.");
6225
6301
  }
6226
6302
  return EXIT.SUCCESS;
6227
6303
  });
@@ -6461,12 +6537,10 @@ __export(exports_state, {
6461
6537
  runStateGet: () => runStateGet
6462
6538
  });
6463
6539
  async function runStateGet(flags, key) {
6464
- const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
6465
- const dim2 = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
6466
6540
  await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
6467
6541
  const entry = await client.getState(key);
6468
6542
  if (!entry) {
6469
- console.log(dim2(`(not set)`));
6543
+ render.info(dim("(not set)"));
6470
6544
  return;
6471
6545
  }
6472
6546
  if (flags.json) {
@@ -6474,8 +6548,8 @@ async function runStateGet(flags, key) {
6474
6548
  return;
6475
6549
  }
6476
6550
  const val = typeof entry.value === "string" ? entry.value : JSON.stringify(entry.value);
6477
- console.log(val);
6478
- console.log(dim2(` set by ${entry.updatedBy} at ${new Date(entry.updatedAt).toLocaleString()}`));
6551
+ render.info(val);
6552
+ render.info(dim(` set by ${entry.updatedBy} at ${new Date(entry.updatedAt).toLocaleString()}`));
6479
6553
  });
6480
6554
  }
6481
6555
  async function runStateSet(flags, key, value) {
@@ -6487,13 +6561,10 @@ async function runStateSet(flags, key, value) {
6487
6561
  }
6488
6562
  await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
6489
6563
  await client.setState(key, parsed);
6490
- console.log(`✓ ${key} = ${JSON.stringify(parsed)}`);
6564
+ render.ok(`${bold(key)} = ${JSON.stringify(parsed)}`);
6491
6565
  });
6492
6566
  }
6493
6567
  async function runStateList(flags) {
6494
- const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
6495
- const dim2 = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
6496
- const bold2 = (s) => useColor ? `\x1B[1m${s}\x1B[22m` : s;
6497
6568
  await withMesh({ meshSlug: flags.mesh ?? null }, async (client, mesh) => {
6498
6569
  const entries = await client.listState();
6499
6570
  if (flags.json) {
@@ -6501,18 +6572,23 @@ async function runStateList(flags) {
6501
6572
  return;
6502
6573
  }
6503
6574
  if (entries.length === 0) {
6504
- console.log(dim2(`No state on mesh "${mesh.slug}".`));
6575
+ render.info(dim(`No state on mesh "${mesh.slug}".`));
6505
6576
  return;
6506
6577
  }
6578
+ render.section(`state (${entries.length})`);
6507
6579
  for (const e of entries) {
6508
6580
  const val = typeof e.value === "string" ? e.value : JSON.stringify(e.value);
6509
- console.log(`${bold2(e.key)}: ${val}`);
6510
- console.log(dim2(` ${e.updatedBy} · ${new Date(e.updatedAt).toLocaleString()}`));
6581
+ process.stdout.write(` ${bold(e.key)}: ${val}
6582
+ `);
6583
+ process.stdout.write(` ${dim(e.updatedBy + " · " + new Date(e.updatedAt).toLocaleString())}
6584
+ `);
6511
6585
  }
6512
6586
  });
6513
6587
  }
6514
6588
  var init_state = __esm(() => {
6515
6589
  init_connect();
6590
+ init_render();
6591
+ init_styles();
6516
6592
  });
6517
6593
 
6518
6594
  // src/commands/info.ts
@@ -6576,7 +6652,7 @@ __export(exports_remember, {
6576
6652
  async function remember(content, opts = {}) {
6577
6653
  const client = allClients()[0];
6578
6654
  if (!client) {
6579
- console.error("Not connected to any mesh.");
6655
+ render.err("Not connected to any mesh.");
6580
6656
  return EXIT.NETWORK_ERROR;
6581
6657
  }
6582
6658
  const tags = opts.tags?.split(",").map((t) => t.trim()).filter(Boolean);
@@ -6586,14 +6662,16 @@ async function remember(content, opts = {}) {
6586
6662
  return EXIT.SUCCESS;
6587
6663
  }
6588
6664
  if (id) {
6589
- console.log(`✓ Remembered (${id.slice(0, 8)})`);
6665
+ render.ok("remembered", dim(id.slice(0, 8)));
6590
6666
  return EXIT.SUCCESS;
6591
6667
  }
6592
- console.error(" Failed to store memory");
6668
+ render.err("failed to store memory");
6593
6669
  return EXIT.INTERNAL_ERROR;
6594
6670
  }
6595
6671
  var init_remember = __esm(() => {
6596
6672
  init_facade8();
6673
+ init_render();
6674
+ init_styles();
6597
6675
  init_exit_codes();
6598
6676
  });
6599
6677
 
@@ -6605,7 +6683,7 @@ __export(exports_recall, {
6605
6683
  async function recall(query, opts = {}) {
6606
6684
  const client = allClients()[0];
6607
6685
  if (!client) {
6608
- console.error("Not connected to any mesh.");
6686
+ render.err("Not connected to any mesh.");
6609
6687
  return EXIT.NETWORK_ERROR;
6610
6688
  }
6611
6689
  const memories = await client.recall(query);
@@ -6614,20 +6692,25 @@ async function recall(query, opts = {}) {
6614
6692
  return EXIT.SUCCESS;
6615
6693
  }
6616
6694
  if (memories.length === 0) {
6617
- console.log(dim("No memories found."));
6695
+ render.info(dim("no memories found."));
6618
6696
  return EXIT.SUCCESS;
6619
6697
  }
6698
+ render.section(`memories (${memories.length})`);
6620
6699
  for (const m of memories) {
6621
- const tags = m.tags.length ? dim(` [${m.tags.join(", ")}]`) : "";
6622
- console.log(`${bold(m.id.slice(0, 8))}${tags}`);
6623
- console.log(` ${m.content}`);
6624
- console.log(dim(` ${m.rememberedBy} · ${new Date(m.rememberedAt).toLocaleString()}`));
6625
- console.log("");
6700
+ const tags = m.tags.length ? dim(` [${m.tags.map((t) => clay(t)).join(dim(", "))}]`) : "";
6701
+ process.stdout.write(` ${bold(m.id.slice(0, 8))}${tags}
6702
+ `);
6703
+ process.stdout.write(` ${m.content}
6704
+ `);
6705
+ process.stdout.write(` ${dim(m.rememberedBy + " · " + new Date(m.rememberedAt).toLocaleString())}
6706
+
6707
+ `);
6626
6708
  }
6627
6709
  return EXIT.SUCCESS;
6628
6710
  }
6629
6711
  var init_recall = __esm(() => {
6630
6712
  init_facade8();
6713
+ init_render();
6631
6714
  init_styles();
6632
6715
  init_exit_codes();
6633
6716
  });
@@ -6674,9 +6757,6 @@ function parseDeliverAt(flags) {
6674
6757
  return null;
6675
6758
  }
6676
6759
  async function runRemind(flags, positional) {
6677
- const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
6678
- const dim2 = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
6679
- const bold2 = (s) => useColor ? `\x1B[1m${s}\x1B[22m` : s;
6680
6760
  const action = positional[0];
6681
6761
  if (action === "list") {
6682
6762
  await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
@@ -6686,15 +6766,18 @@ async function runRemind(flags, positional) {
6686
6766
  return;
6687
6767
  }
6688
6768
  if (scheduled.length === 0) {
6689
- console.log(dim2("No pending reminders."));
6769
+ render.info(dim("No pending reminders."));
6690
6770
  return;
6691
6771
  }
6772
+ render.section(`reminders (${scheduled.length})`);
6692
6773
  for (const m of scheduled) {
6693
6774
  const when = new Date(m.deliverAt).toLocaleString();
6694
- const to = m.to === client.getSessionPubkey() ? dim2("(self)") : m.to;
6695
- console.log(` ${bold2(m.id.slice(0, 8))} → ${to} at ${when}`);
6696
- console.log(` ${dim2(m.message.slice(0, 80))}`);
6697
- console.log("");
6775
+ const to = m.to === client.getSessionPubkey() ? dim("(self)") : m.to;
6776
+ process.stdout.write(` ${bold(m.id.slice(0, 8))} ${dim("")} ${to} ${dim("at")} ${when}
6777
+ `);
6778
+ process.stdout.write(` ${dim(m.message.slice(0, 80))}
6779
+
6780
+ `);
6698
6781
  }
6699
6782
  });
6700
6783
  return;
@@ -6702,15 +6785,15 @@ async function runRemind(flags, positional) {
6702
6785
  if (action === "cancel") {
6703
6786
  const id = positional[1];
6704
6787
  if (!id) {
6705
- console.error("Usage: claudemesh remind cancel <id>");
6788
+ render.err("Usage: claudemesh remind cancel <id>");
6706
6789
  process.exit(1);
6707
6790
  }
6708
6791
  await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
6709
6792
  const ok = await client.cancelScheduled(id);
6710
6793
  if (ok)
6711
- console.log(`✓ Cancelled ${id}`);
6794
+ render.ok(`cancelled ${bold(id.slice(0, 8))}`);
6712
6795
  else {
6713
- console.error(`✗ Not found or already fired: ${id}`);
6796
+ render.err(`not found or already fired: ${id}`);
6714
6797
  process.exit(1);
6715
6798
  }
6716
6799
  });
@@ -6718,17 +6801,17 @@ async function runRemind(flags, positional) {
6718
6801
  }
6719
6802
  const message = action ?? positional.join(" ");
6720
6803
  if (!message) {
6721
- console.error("Usage: claudemesh remind <message> --in <duration>");
6722
- console.error(" claudemesh remind <message> --at <time>");
6723
- console.error(' claudemesh remind <message> --cron "0 */2 * * *"');
6724
- console.error(" claudemesh remind list");
6725
- console.error(" claudemesh remind cancel <id>");
6804
+ render.err("Usage: claudemesh remind <message> --in <duration>");
6805
+ render.info(dim(" claudemesh remind <message> --at <time>"));
6806
+ render.info(dim(' claudemesh remind <message> --cron "0 */2 * * *"'));
6807
+ render.info(dim(" claudemesh remind list"));
6808
+ render.info(dim(" claudemesh remind cancel <id>"));
6726
6809
  process.exit(1);
6727
6810
  }
6728
6811
  const isCron = !!flags.cron;
6729
6812
  const deliverAt = isCron ? 0 : parseDeliverAt(flags);
6730
6813
  if (!isCron && deliverAt === null) {
6731
- console.error('Specify when: --in <duration> (e.g. "2h", "30m"), --at <time> (e.g. "15:00"), or --cron <expression>');
6814
+ render.err("Specify when", 'use --in <duration> (e.g. "2h", "30m"), --at <time> (e.g. "15:00"), or --cron <expression>');
6732
6815
  process.exit(1);
6733
6816
  }
6734
6817
  await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
@@ -6740,7 +6823,7 @@ async function runRemind(flags, positional) {
6740
6823
  const peers = await client.listPeers();
6741
6824
  const match = peers.find((p) => p.displayName.toLowerCase() === flags.to.toLowerCase());
6742
6825
  if (!match) {
6743
- console.error(`Peer "${flags.to}" not found. Online: ${peers.map((p) => p.displayName).join(", ") || "(none)"}`);
6826
+ render.err(`Peer "${flags.to}" not found`, `online: ${peers.map((p) => p.displayName).join(", ") || "(none)"}`);
6744
6827
  process.exit(1);
6745
6828
  }
6746
6829
  targetSpec = match.pubkey;
@@ -6750,7 +6833,7 @@ async function runRemind(flags, positional) {
6750
6833
  }
6751
6834
  const result = await client.scheduleMessage(targetSpec, message, deliverAt ?? 0, false, flags.cron);
6752
6835
  if (!result) {
6753
- console.error("Broker did not acknowledge — check connection");
6836
+ render.err("Broker did not acknowledge — check connection");
6754
6837
  process.exit(1);
6755
6838
  }
6756
6839
  if (flags.json) {
@@ -6760,15 +6843,17 @@ async function runRemind(flags, positional) {
6760
6843
  const toLabel = !flags.to || flags.to === "self" ? "yourself" : flags.to;
6761
6844
  if (isCron) {
6762
6845
  const nextFire = new Date(result.deliverAt).toLocaleString();
6763
- console.log(`✓ Recurring reminder set (${result.scheduledId.slice(0, 8)}): "${message}" → ${toLabel}cron: ${flags.cron}, next fire: ${nextFire}`);
6846
+ render.ok(`recurring reminder set`, `${result.scheduledId.slice(0, 8)} · ${clay(message)} → ${toLabel} · cron ${flags.cron} · next ${nextFire}`);
6764
6847
  } else {
6765
6848
  const when = new Date(result.deliverAt).toLocaleString();
6766
- console.log(`✓ Reminder set (${result.scheduledId.slice(0, 8)}): "${message}" → ${toLabel} at ${when}`);
6849
+ render.ok(`reminder set`, `${result.scheduledId.slice(0, 8)} · ${clay(message)} → ${toLabel} at ${when}`);
6767
6850
  }
6768
6851
  });
6769
6852
  }
6770
6853
  var init_remind = __esm(() => {
6771
6854
  init_connect();
6855
+ init_render();
6856
+ init_styles();
6772
6857
  });
6773
6858
 
6774
6859
  // src/commands/profile.ts
@@ -6908,20 +6993,21 @@ async function whoami(opts) {
6908
6993
  return EXIT.SUCCESS;
6909
6994
  }
6910
6995
  if (!result.signed_in) {
6911
- console.log(` Not signed in. Run \`claudemesh login\` to sign in.`);
6996
+ render.err("Not signed in", "Run `claudemesh login` to sign in.");
6912
6997
  return EXIT.AUTH_FAILED;
6913
6998
  }
6914
- console.log(`
6915
- Signed in as ${result.user.display_name} (${result.user.email})`);
6916
- console.log(` Token source: ${result.token_source} ${dim("(~/.claudemesh/auth.json)")}`);
6917
- if (result.meshes) {
6918
- console.log(` Meshes: ${result.meshes.owned} owned, ${result.meshes.guest} guest`);
6919
- }
6920
- console.log();
6999
+ render.section("whoami");
7000
+ render.kv([
7001
+ ["user", `${bold(result.user.display_name)} ${dim(`(${result.user.email})`)}`],
7002
+ ["token", `${result.token_source} ${dim("(~/.claudemesh/auth.json)")}`],
7003
+ ...result.meshes ? [["meshes", `${result.meshes.owned} owned · ${result.meshes.guest} guest`]] : []
7004
+ ]);
7005
+ render.blank();
6921
7006
  return EXIT.SUCCESS;
6922
7007
  }
6923
7008
  var init_whoami = __esm(() => {
6924
7009
  init_facade6();
7010
+ init_render();
6925
7011
  init_styles();
6926
7012
  init_exit_codes();
6927
7013
  });
@@ -7137,16 +7223,15 @@ function installStatusLine() {
7137
7223
  function runInstall(args = []) {
7138
7224
  const skipHooks = args.includes("--no-hooks");
7139
7225
  const wantStatusLine = args.includes("--status-line");
7140
- console.log("claudemesh install");
7141
- console.log("------------------");
7226
+ render.section("claudemesh install");
7142
7227
  const entry = resolveEntry();
7143
7228
  const isBundled = entry.endsWith("/dist/index.js") || entry.endsWith("\\dist\\index.js");
7144
7229
  if (!isBundled && !bunAvailable()) {
7145
- console.error("`bun` is not on PATH. Install Bun first: https://bun.com");
7230
+ render.err("`bun` is not on PATH.", "Install Bun first: https://bun.com");
7146
7231
  process.exit(1);
7147
7232
  }
7148
7233
  if (!existsSync5(entry)) {
7149
- console.error(`✗ MCP entry not found at ${entry}`);
7234
+ render.err(`MCP entry not found at ${entry}`);
7150
7235
  process.exit(1);
7151
7236
  }
7152
7237
  const desired = buildMcpEntry(entry);
@@ -7155,56 +7240,53 @@ function runInstall(args = []) {
7155
7240
  const verifyServers = verify2.mcpServers ?? {};
7156
7241
  const stored = verifyServers[MCP_NAME];
7157
7242
  if (!stored || !entriesEqual(stored, desired)) {
7158
- console.error(`✗ post-write verification failed — ${CLAUDE_CONFIG} may be corrupt`);
7243
+ render.err("post-write verification failed", `${CLAUDE_CONFIG} may be corrupt`);
7159
7244
  process.exit(1);
7160
7245
  }
7161
- const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
7162
- const bold2 = (s) => useColor ? `\x1B[1m${s}\x1B[22m` : s;
7163
- const yellow2 = (s) => useColor ? `\x1B[33m${s}\x1B[39m` : s;
7164
- const dim2 = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
7165
- console.log(`✓ MCP server "${MCP_NAME}" ${action}`);
7166
- console.log(dim2(` config: ${CLAUDE_CONFIG}`));
7167
- console.log(dim2(` command: ${desired.command}${desired.args?.length ? " " + desired.args.join(" ") : ""}`));
7246
+ render.ok(`MCP server "${bold(MCP_NAME)}" ${action}`);
7247
+ render.kv([
7248
+ ["config", dim(CLAUDE_CONFIG)],
7249
+ ["command", dim(`${desired.command}${desired.args?.length ? " " + desired.args.join(" ") : ""}`)]
7250
+ ]);
7168
7251
  try {
7169
7252
  const { added, unchanged } = installAllowedTools();
7170
7253
  if (added.length > 0) {
7171
- console.log(`✓ allowedTools: ${added.length} claudemesh tools pre-approved${unchanged > 0 ? `, ${unchanged} already present` : ""}`);
7172
- console.log(dim2(` This lets claudemesh tools run without --dangerously-skip-permissions.`));
7173
- console.log(dim2(` Your existing allowedTools entries were preserved.`));
7254
+ render.ok(`allowedTools: ${added.length} claudemesh tools pre-approved`, unchanged > 0 ? `${unchanged} already present` : undefined);
7255
+ render.info(dim("This lets claudemesh tools run without --dangerously-skip-permissions."));
7256
+ render.info(dim("Your existing allowedTools entries were preserved."));
7174
7257
  } else {
7175
- console.log(`✓ allowedTools: all ${unchanged} claudemesh tools already pre-approved`);
7258
+ render.ok(`allowedTools: all ${unchanged} claudemesh tools already pre-approved`);
7176
7259
  }
7177
- console.log(dim2(` config: ${CLAUDE_SETTINGS}`));
7260
+ render.info(dim(` config: ${CLAUDE_SETTINGS}`));
7178
7261
  } catch (e) {
7179
- console.error(`⚠ allowedTools update failed: ${e instanceof Error ? e.message : String(e)}`);
7262
+ render.warn(`allowedTools update failed: ${e instanceof Error ? e.message : String(e)}`);
7180
7263
  }
7181
7264
  if (!skipHooks) {
7182
7265
  try {
7183
7266
  const { added, unchanged } = installHooks();
7184
7267
  if (added > 0) {
7185
- console.log(`✓ Hooks registered (Stop + UserPromptSubmit) → ${added} added, ${unchanged} already present`);
7268
+ render.ok(`Hooks registered (Stop + UserPromptSubmit)`, `${added} added, ${unchanged} already present`);
7186
7269
  } else {
7187
- console.log(`✓ Hooks already registered (${unchanged} present)`);
7270
+ render.ok(`Hooks already registered`, `${unchanged} present`);
7188
7271
  }
7189
- console.log(dim2(` config: ${CLAUDE_SETTINGS}`));
7272
+ render.info(dim(` config: ${CLAUDE_SETTINGS}`));
7190
7273
  } catch (e) {
7191
- console.error(`⚠ hook registration failed: ${e instanceof Error ? e.message : String(e)}`);
7192
- console.error(" (MCP is still installed — hooks just skip. Retry with --no-hooks to suppress.)");
7274
+ render.warn(`hook registration failed: ${e instanceof Error ? e.message : String(e)}`, "MCP is still installed — hooks just skip. Retry with --no-hooks to suppress.");
7193
7275
  }
7194
7276
  } else {
7195
- console.log(dim2("· Hooks skipped (--no-hooks)"));
7277
+ render.info(dim("· Hooks skipped (--no-hooks)"));
7196
7278
  }
7197
7279
  if (wantStatusLine) {
7198
7280
  try {
7199
7281
  const { installed } = installStatusLine();
7200
7282
  if (installed) {
7201
- console.log(`✓ Claude Code statusLine → \`claudemesh status-line\``);
7202
- console.log(dim2(` Shows: ◇ <mesh> · <online>/<total> online · <you>`));
7283
+ render.ok(`Claude Code statusLine → ${clay("claudemesh status-line")}`);
7284
+ render.info(dim(" Shows: ◇ <mesh> · <online>/<total> online · <you>"));
7203
7285
  } else {
7204
- console.log(dim2("· statusLine already set to a custom command — left alone"));
7286
+ render.info(dim("· statusLine already set to a custom command — left alone"));
7205
7287
  }
7206
7288
  } catch (e) {
7207
- console.error(`⚠ statusLine install failed: ${e instanceof Error ? e.message : String(e)}`);
7289
+ render.warn(`statusLine install failed: ${e instanceof Error ? e.message : String(e)}`);
7208
7290
  }
7209
7291
  }
7210
7292
  let hasMeshes = false;
@@ -7212,57 +7294,58 @@ function runInstall(args = []) {
7212
7294
  const meshConfig = readConfig();
7213
7295
  hasMeshes = meshConfig.meshes.length > 0;
7214
7296
  } catch {}
7215
- console.log("");
7216
- console.log(yellow2(bold2("RESTART CLAUDE CODE")) + yellow2(" for MCP tools to appear."));
7297
+ render.blank();
7298
+ render.warn(`${bold("RESTART CLAUDE CODE")} ${yellow("for MCP tools to appear.")}`);
7217
7299
  if (!hasMeshes) {
7218
- console.log("");
7219
- console.log(yellow2("No meshes joined.") + " To connect with peers:");
7220
- console.log(` ${bold2("claudemesh <invite-url>")}` + dim2(" — joins + launches in one step"));
7221
- console.log(` ${dim2("Create one at")} ${bold2("https://claudemesh.com/dashboard")}`);
7300
+ render.blank();
7301
+ render.info(`${yellow("No meshes joined.")} To connect with peers:`);
7302
+ render.info(` ${bold("claudemesh <invite-url>")}${dim(" — joins + launches in one step")}`);
7303
+ render.info(` ${dim("Create one at")} ${bold("https://claudemesh.com/dashboard")}`);
7222
7304
  } else {
7223
- console.log("");
7224
- console.log(`Next: ${bold2("claudemesh")}` + dim2(" — launch with your joined mesh"));
7305
+ render.blank();
7306
+ render.info(`Next: ${bold("claudemesh")}${dim(" — launch with your joined mesh")}`);
7225
7307
  }
7226
- console.log("");
7227
- console.log(dim2("Optional:"));
7228
- console.log(dim2(` claudemesh url-handler install # click-to-launch from email`));
7229
- console.log(dim2(` claudemesh install --status-line # live peer count in Claude Code`));
7230
- console.log(dim2(` claudemesh completions zsh # shell completions`));
7308
+ render.blank();
7309
+ render.info(dim("Optional:"));
7310
+ render.info(dim(` claudemesh url-handler install # click-to-launch from email`));
7311
+ render.info(dim(` claudemesh install --status-line # live peer count in Claude Code`));
7312
+ render.info(dim(` claudemesh completions zsh # shell completions`));
7231
7313
  }
7232
7314
  function runUninstall() {
7233
- console.log("claudemesh uninstall");
7234
- console.log("--------------------");
7315
+ render.section("claudemesh uninstall");
7235
7316
  if (removeMcpServer()) {
7236
- console.log(`✓ MCP server "${MCP_NAME}" removed`);
7317
+ render.ok(`MCP server "${bold(MCP_NAME)}" removed`);
7237
7318
  } else {
7238
- console.log(`· MCP server "${MCP_NAME}" not present`);
7319
+ render.info(dim(`· MCP server "${MCP_NAME}" not present`));
7239
7320
  }
7240
7321
  try {
7241
7322
  const removed = uninstallAllowedTools();
7242
7323
  if (removed > 0) {
7243
- console.log(`✓ allowedTools: ${removed} claudemesh tools removed`);
7324
+ render.ok(`allowedTools: ${removed} claudemesh tools removed`);
7244
7325
  } else {
7245
- console.log("· No claudemesh allowedTools to remove");
7326
+ render.info(dim("· No claudemesh allowedTools to remove"));
7246
7327
  }
7247
7328
  } catch (e) {
7248
- console.error(`⚠ allowedTools removal failed: ${e instanceof Error ? e.message : String(e)}`);
7329
+ render.warn(`allowedTools removal failed: ${e instanceof Error ? e.message : String(e)}`);
7249
7330
  }
7250
7331
  try {
7251
7332
  const removed = uninstallHooks();
7252
7333
  if (removed > 0) {
7253
- console.log(`✓ Hooks removed (${removed} entries)`);
7334
+ render.ok(`Hooks removed`, `${removed} entries`);
7254
7335
  } else {
7255
- console.log("· No claudemesh hooks to remove");
7336
+ render.info(dim("· No claudemesh hooks to remove"));
7256
7337
  }
7257
7338
  } catch (e) {
7258
- console.error(`⚠ hook removal failed: ${e instanceof Error ? e.message : String(e)}`);
7339
+ render.warn(`hook removal failed: ${e instanceof Error ? e.message : String(e)}`);
7259
7340
  }
7260
- console.log("");
7261
- console.log("Restart Claude Code to drop the MCP connection + hooks.");
7341
+ render.blank();
7342
+ render.info("Restart Claude Code to drop the MCP connection + hooks.");
7262
7343
  }
7263
7344
  var MCP_NAME = "claudemesh", CLAUDE_CONFIG, CLAUDE_SETTINGS, HOOK_COMMAND_STOP = "claudemesh hook idle", HOOK_COMMAND_USER_PROMPT = "claudemesh hook working", HOOK_MARKER = "claudemesh hook ", CLAUDEMESH_TOOLS;
7264
7345
  var init_install = __esm(() => {
7265
7346
  init_facade();
7347
+ init_render();
7348
+ init_styles();
7266
7349
  CLAUDE_CONFIG = join4(homedir4(), ".claude.json");
7267
7350
  CLAUDE_SETTINGS = join4(homedir4(), ".claude", "settings.json");
7268
7351
  CLAUDEMESH_TOOLS = [
@@ -7331,7 +7414,7 @@ async function uninstall() {
7331
7414
  delete servers.claudemesh;
7332
7415
  writeFileSync6(PATHS.CLAUDE_JSON, JSON.stringify(config, null, 2) + `
7333
7416
  `, "utf-8");
7334
- console.log(` ${green(icons.check)} Removed MCP server from ~/.claude.json`);
7417
+ render.ok("removed MCP server", dim("~/.claude.json"));
7335
7418
  removed++;
7336
7419
  }
7337
7420
  } catch {}
@@ -7361,19 +7444,20 @@ async function uninstall() {
7361
7444
  if (removedHooks > 0) {
7362
7445
  writeFileSync6(PATHS.CLAUDE_SETTINGS, JSON.stringify(config, null, 2) + `
7363
7446
  `, "utf-8");
7364
- console.log(` ${green(icons.check)} Removed ${removedHooks} claudemesh hook(s) from settings.json`);
7447
+ render.ok(`removed ${removedHooks} claudemesh hook${removedHooks === 1 ? "" : "s"}`, dim("settings.json"));
7365
7448
  removed++;
7366
7449
  }
7367
7450
  }
7368
7451
  } catch {}
7369
7452
  }
7370
7453
  if (removed === 0) {
7371
- console.log(" Nothing to remove — claudemesh was not installed.");
7454
+ render.info(dim("Nothing to remove — claudemesh was not installed."));
7372
7455
  }
7373
7456
  return EXIT.SUCCESS;
7374
7457
  }
7375
7458
  var init_uninstall = __esm(() => {
7376
7459
  init_paths();
7460
+ init_render();
7377
7461
  init_styles();
7378
7462
  init_exit_codes();
7379
7463
  });
@@ -7603,7 +7687,7 @@ async function runDoctor() {
7603
7687
  const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
7604
7688
  const dim2 = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
7605
7689
  const green3 = (s) => useColor ? `\x1B[32m${s}\x1B[39m` : s;
7606
- const red3 = (s) => useColor ? `\x1B[31m${s}\x1B[39m` : s;
7690
+ const red2 = (s) => useColor ? `\x1B[31m${s}\x1B[39m` : s;
7607
7691
  console.log(`claudemesh doctor (v${VERSION})`);
7608
7692
  console.log("─".repeat(60));
7609
7693
  const checks = [
@@ -7617,7 +7701,7 @@ async function runDoctor() {
7617
7701
  await checkNpmLatest()
7618
7702
  ];
7619
7703
  for (const c of checks) {
7620
- const mark = c.pass ? green3("✓") : red3("✗");
7704
+ const mark = c.pass ? green3("✓") : red2("✗");
7621
7705
  const detail = c.detail ? dim2(` (${c.detail})`) : "";
7622
7706
  console.log(`${mark} ${c.name}${detail}`);
7623
7707
  if (!c.pass && c.fix) {
@@ -7630,7 +7714,7 @@ async function runDoctor() {
7630
7714
  console.log(green3("All checks passed."));
7631
7715
  process.exit(0);
7632
7716
  } else {
7633
- console.log(red3(`${failing.length} check(s) failed.`));
7717
+ console.log(red2(`${failing.length} check(s) failed.`));
7634
7718
  process.exit(1);
7635
7719
  }
7636
7720
  }
@@ -8208,7 +8292,7 @@ complete -c claudemesh -l join -d 'invite url'
8208
8292
  }
8209
8293
  async function runCompletions(shell) {
8210
8294
  if (!shell) {
8211
- console.error("Usage: claudemesh completions <bash|zsh|fish>");
8295
+ render.err("Usage: claudemesh completions <bash|zsh|fish>");
8212
8296
  return EXIT.INVALID_ARGS;
8213
8297
  }
8214
8298
  switch (shell.toLowerCase()) {
@@ -8222,13 +8306,14 @@ async function runCompletions(shell) {
8222
8306
  process.stdout.write(fish());
8223
8307
  return EXIT.SUCCESS;
8224
8308
  default:
8225
- console.error(`Unsupported shell: ${shell}. Use bash, zsh, or fish.`);
8309
+ render.err(`Unsupported shell: ${shell}`, "use bash, zsh, or fish.");
8226
8310
  return EXIT.INVALID_ARGS;
8227
8311
  }
8228
8312
  }
8229
8313
  var COMMANDS, FLAGS;
8230
8314
  var init_completions = __esm(() => {
8231
8315
  init_exit_codes();
8316
+ init_render();
8232
8317
  COMMANDS = [
8233
8318
  "create",
8234
8319
  "new",
@@ -8311,26 +8396,22 @@ function safetyNumber(myPubkey, peerPubkey) {
8311
8396
  return groups.join(" ");
8312
8397
  }
8313
8398
  async function runVerify(target, opts = {}) {
8314
- const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
8315
- const bold2 = (s) => useColor ? `\x1B[1m${s}\x1B[22m` : s;
8316
- const dim2 = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
8317
- const clay2 = (s) => useColor ? `\x1B[38;2;217;119;87m${s}\x1B[39m` : s;
8318
8399
  const config = readConfig();
8319
8400
  const meshSlug = opts.mesh ?? config.meshes[0]?.slug;
8320
8401
  if (!meshSlug) {
8321
- console.error(" No meshes joined. Run `claudemesh join <url>` first.");
8402
+ render.err("No meshes joined.", "Run `claudemesh join <url>` first.");
8322
8403
  return EXIT.NOT_FOUND;
8323
8404
  }
8324
8405
  const mesh = config.meshes.find((m) => m.slug === meshSlug);
8325
8406
  if (!mesh) {
8326
- console.error(` Mesh "${meshSlug}" not found locally.`);
8407
+ render.err(`Mesh "${meshSlug}" not found locally.`);
8327
8408
  return EXIT.NOT_FOUND;
8328
8409
  }
8329
8410
  return await withMesh({ meshSlug }, async (client) => {
8330
8411
  const peers = await client.listPeers();
8331
8412
  const targets = target ? peers.filter((p) => p.displayName === target || p.pubkey === target || p.pubkey.startsWith(target)) : peers;
8332
8413
  if (targets.length === 0) {
8333
- console.error(` No peer matching "${target ?? "(all)"}" on mesh ${meshSlug}.`);
8414
+ render.err(`No peer matching "${target ?? "(all)"}" on mesh ${meshSlug}.`);
8334
8415
  return EXIT.NOT_FOUND;
8335
8416
  }
8336
8417
  if (opts.json) {
@@ -8342,19 +8423,20 @@ async function runVerify(target, opts = {}) {
8342
8423
  })), null, 2));
8343
8424
  return EXIT.SUCCESS;
8344
8425
  }
8345
- console.log("");
8346
- console.log(` ${dim2("— safety numbers on")} ${bold2(meshSlug)}`);
8347
- console.log("");
8426
+ render.section(`safety numbers on ${meshSlug}`);
8348
8427
  for (const p of targets) {
8349
8428
  const sn = safetyNumber(mesh.pubkey, p.pubkey);
8350
- console.log(` ${bold2(p.displayName)}`);
8351
- console.log(` ${clay2(sn)}`);
8352
- console.log(` ${dim2(`pubkey ${p.pubkey.slice(0, 16)}…`)}`);
8353
- console.log("");
8429
+ process.stdout.write(` ${bold(p.displayName)}
8430
+ `);
8431
+ process.stdout.write(` ${clay(sn)}
8432
+ `);
8433
+ process.stdout.write(` ${dim(`pubkey ${p.pubkey.slice(0, 16)}…`)}
8434
+
8435
+ `);
8354
8436
  }
8355
- console.log(dim2(" Compare these digits with your peer (phone, in person, not chat)."));
8356
- console.log(dim2(" If they match on both sides, the channel is not being intercepted."));
8357
- console.log("");
8437
+ render.hint("Compare these digits with your peer (phone, in person not chat).");
8438
+ render.hint("If they match on both sides, the channel is not being intercepted.");
8439
+ render.blank();
8358
8440
  return EXIT.SUCCESS;
8359
8441
  });
8360
8442
  }
@@ -8362,6 +8444,8 @@ var init_verify = __esm(() => {
8362
8444
  init_connect();
8363
8445
  init_facade();
8364
8446
  init_exit_codes();
8447
+ init_render();
8448
+ init_styles();
8365
8449
  });
8366
8450
 
8367
8451
  // src/commands/url-handler.ts
@@ -8423,10 +8507,9 @@ EOF
8423
8507
  chmodSync4(shimPath, 493);
8424
8508
  const lsreg = spawnSync5("/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister", ["-f", appDir], { encoding: "utf-8" });
8425
8509
  if (lsreg.status !== 0) {
8426
- console.log("lsregister returned non-zero; scheme may not activate until Finder rescans.");
8510
+ render.warn("lsregister returned non-zero", "scheme may not activate until Finder rescans.");
8427
8511
  }
8428
- console.log(` ✓ Registered claudemesh:// scheme on macOS`);
8429
- console.log(` app bundle: ${appDir}`);
8512
+ render.ok("registered claudemesh:// scheme on macOS", dim(appDir));
8430
8513
  return EXIT.SUCCESS;
8431
8514
  }
8432
8515
  function installLinux() {
@@ -8447,12 +8530,11 @@ NoDisplay=true
8447
8530
  writeFileSync7(desktopPath, desktop);
8448
8531
  const xdg1 = spawnSync5("xdg-mime", ["default", "claudemesh.desktop", "x-scheme-handler/claudemesh"], { encoding: "utf-8" });
8449
8532
  if (xdg1.status !== 0) {
8450
- console.log("xdg-mime not available — skipped mime default registration");
8533
+ render.warn("xdg-mime not available — skipped mime default registration");
8451
8534
  }
8452
8535
  const xdg2 = spawnSync5("update-desktop-database", [appsDir], { encoding: "utf-8" });
8453
8536
  xdg2.status;
8454
- console.log(` ✓ Registered claudemesh:// scheme on Linux`);
8455
- console.log(` desktop entry: ${desktopPath}`);
8537
+ render.ok("registered claudemesh:// scheme on Linux", dim(desktopPath));
8456
8538
  return EXIT.SUCCESS;
8457
8539
  }
8458
8540
  function installWindows() {
@@ -8472,29 +8554,29 @@ function installWindows() {
8472
8554
  `));
8473
8555
  const res = spawnSync5("reg.exe", ["import", regPath], { encoding: "utf-8" });
8474
8556
  if (res.status !== 0) {
8475
- console.log(` ⚠ reg.exe import failed. Manual: double-click ${regPath}`);
8557
+ render.warn("reg.exe import failed", `manual: double-click ${regPath}`);
8476
8558
  return EXIT.INTERNAL_ERROR;
8477
8559
  }
8478
- console.log(` ✓ Registered claudemesh:// scheme on Windows`);
8560
+ render.ok("registered claudemesh:// scheme on Windows");
8479
8561
  return EXIT.SUCCESS;
8480
8562
  }
8481
8563
  function uninstallDarwin() {
8482
8564
  const appDir = join6(homedir6(), "Library", "Application Support", "claudemesh", "ClaudemeshHandler.app");
8483
8565
  if (existsSync13(appDir))
8484
8566
  rmSync2(appDir, { recursive: true, force: true });
8485
- console.log(" Removed claudemesh:// handler on macOS");
8567
+ render.ok("removed claudemesh:// handler on macOS");
8486
8568
  return EXIT.SUCCESS;
8487
8569
  }
8488
8570
  function uninstallLinux() {
8489
8571
  const desktopPath = join6(homedir6(), ".local", "share", "applications", "claudemesh.desktop");
8490
8572
  if (existsSync13(desktopPath))
8491
8573
  rmSync2(desktopPath, { force: true });
8492
- console.log(" Removed claudemesh:// handler on Linux");
8574
+ render.ok("removed claudemesh:// handler on Linux");
8493
8575
  return EXIT.SUCCESS;
8494
8576
  }
8495
8577
  function uninstallWindows() {
8496
8578
  spawnSync5("reg.exe", ["delete", "HKCU\\Software\\Classes\\claudemesh", "/f"], { encoding: "utf-8" });
8497
- console.log(" Removed claudemesh:// handler on Windows");
8579
+ render.ok("removed claudemesh:// handler on Windows");
8498
8580
  return EXIT.SUCCESS;
8499
8581
  }
8500
8582
  async function runUrlHandler(action) {
@@ -8515,14 +8597,16 @@ async function runUrlHandler(action) {
8515
8597
  if (p === "win32")
8516
8598
  return uninstallWindows();
8517
8599
  } else {
8518
- console.error("Usage: claudemesh url-handler <install|uninstall>");
8600
+ render.err("Usage: claudemesh url-handler <install|uninstall>");
8519
8601
  return EXIT.INVALID_ARGS;
8520
8602
  }
8521
- console.error(`Unsupported platform: ${p}`);
8603
+ render.err(`Unsupported platform: ${p}`);
8522
8604
  return EXIT.INTERNAL_ERROR;
8523
8605
  }
8524
8606
  var init_url_handler = __esm(() => {
8525
8607
  init_exit_codes();
8608
+ init_render();
8609
+ init_styles();
8526
8610
  });
8527
8611
 
8528
8612
  // src/commands/status-line.ts
@@ -12240,9 +12324,8 @@ __export(exports_seed_test_mesh, {
12240
12324
  function runSeedTestMesh(args) {
12241
12325
  const [brokerUrl, meshId, memberId, pubkey, slug] = args;
12242
12326
  if (!brokerUrl || !meshId || !memberId || !pubkey || !slug) {
12243
- console.error("Usage: claudemesh seed-test-mesh <broker-ws-url> <mesh-id> <member-id> <pubkey> <slug>");
12244
- console.error("");
12245
- console.error('Example: claudemesh seed-test-mesh "ws://localhost:7900/ws" mesh-123 member-abc aaa..aaa smoke-test');
12327
+ render.err("Usage: claudemesh seed-test-mesh <broker-ws-url> <mesh-id> <member-id> <pubkey> <slug>");
12328
+ render.info(dim('Example: claudemesh seed-test-mesh "ws://localhost:7900/ws" mesh-123 member-abc aaa..aaa smoke-test'));
12246
12329
  process.exit(1);
12247
12330
  }
12248
12331
  const config = readConfig();
@@ -12258,11 +12341,13 @@ function runSeedTestMesh(args) {
12258
12341
  joinedAt: new Date().toISOString()
12259
12342
  });
12260
12343
  writeConfig(config);
12261
- console.log(`Seeded mesh "${slug}" (${meshId}) into local config.`);
12262
- console.log(`Run \`claudemesh mcp\` to connect, or register with Claude Code via \`claudemesh install\`.`);
12344
+ render.ok(`seeded ${bold(slug)}`, dim(meshId));
12345
+ render.hint(`run ${bold("claudemesh mcp")} to connect, or register with Claude Code via ${bold("claudemesh install")}`);
12263
12346
  }
12264
12347
  var init_seed_test_mesh = __esm(() => {
12265
12348
  init_facade();
12349
+ init_render();
12350
+ init_styles();
12266
12351
  });
12267
12352
 
12268
12353
  // src/cli/argv.ts
@@ -12412,9 +12497,10 @@ Mesh
12412
12497
  claudemesh delete [slug] delete a mesh (alias: rm)
12413
12498
  claudemesh rename <slug> <name> rename a mesh
12414
12499
  claudemesh share [email] share mesh (invite link / send email)
12415
- claudemesh kick <peer> disconnect a peer (can reconnect)
12416
- claudemesh kick --stale 30m disconnect idle peers (> duration)
12417
- claudemesh kick --all disconnect everyone except you
12500
+ claudemesh disconnect <peer> soft disconnect (peer auto-reconnects)
12501
+ claudemesh kick <peer> end session (peer must manually rejoin)
12502
+ claudemesh kick --stale 30m kick peers idle > duration
12503
+ claudemesh kick --all kick everyone except yourself
12418
12504
  claudemesh ban <peer> kick + permanently revoke (can't rejoin)
12419
12505
  claudemesh unban <peer> lift a ban
12420
12506
  claudemesh bans list banned members
@@ -12556,6 +12642,11 @@ async function main() {
12556
12642
  process.exit(await invite2(positionals[0], { mesh: flags.mesh, json: !!flags.json }));
12557
12643
  break;
12558
12644
  }
12645
+ case "disconnect": {
12646
+ const { runDisconnect: runDisconnect2 } = await Promise.resolve().then(() => (init_kick(), exports_kick));
12647
+ process.exit(await runDisconnect2(positionals[0], { mesh: flags.mesh, stale: flags.stale, all: !!flags.all }));
12648
+ break;
12649
+ }
12559
12650
  case "kick": {
12560
12651
  const { runKick: runKick2 } = await Promise.resolve().then(() => (init_kick(), exports_kick));
12561
12652
  process.exit(await runKick2(positionals[0], { mesh: flags.mesh, stale: flags.stale, all: !!flags.all }));
@@ -12763,4 +12854,4 @@ main().catch((err) => {
12763
12854
  process.exit(EXIT.INTERNAL_ERROR);
12764
12855
  });
12765
12856
 
12766
- //# debugId=BE63FC7A7529110A64756E2164756E21
12857
+ //# debugId=80234995420998CA64756E2164756E21