claudemesh-cli 1.0.1 → 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.1", 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",
@@ -3170,6 +3170,75 @@ var init_facade8 = __esm(() => {
3170
3170
  init_errors3();
3171
3171
  });
3172
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
+
3173
3242
  // src/ui/screen.ts
3174
3243
  import { createInterface as createInterface2 } from "node:readline";
3175
3244
  function termSize() {
@@ -3526,7 +3595,7 @@ async function runLaunch(flags, rawArgs) {
3526
3595
  claudeArgs: claudePassthrough
3527
3596
  };
3528
3597
  if (args.joinLink) {
3529
- console.log("Joining mesh...");
3598
+ render.info(dim("Joining mesh"));
3530
3599
  const invite = await parseInviteLink(args.joinLink);
3531
3600
  const keypair = await generateKeypair();
3532
3601
  const displayName2 = args.name ?? process.env.USER ?? process.env.USERNAME ?? hostname2();
@@ -3551,7 +3620,7 @@ async function runLaunch(flags, rawArgs) {
3551
3620
  });
3552
3621
  const { writeConfig: writeConfig2 } = await Promise.resolve().then(() => (init_facade(), exports_facade));
3553
3622
  writeConfig2(config2);
3554
- 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);
3555
3624
  }
3556
3625
  const config = readConfig();
3557
3626
  let justSynced = false;
@@ -3622,14 +3691,14 @@ async function runLaunch(flags, rawArgs) {
3622
3691
  `);
3623
3692
  }
3624
3693
  if (config.meshes.length === 0) {
3625
- 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>.");
3626
3695
  process.exit(1);
3627
3696
  }
3628
3697
  let mesh;
3629
3698
  if (args.meshSlug) {
3630
3699
  const found = config.meshes.find((m) => m.slug === args.meshSlug);
3631
3700
  if (!found) {
3632
- 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(", ")}`);
3633
3702
  process.exit(1);
3634
3703
  }
3635
3704
  mesh = found;
@@ -3852,9 +3921,9 @@ async function runLaunch(flags, rawArgs) {
3852
3921
  if (result.error) {
3853
3922
  const err = result.error;
3854
3923
  if (err.code === "ENOENT") {
3855
- console.error("`claude` not found on PATH. Install Claude Code first.");
3924
+ render.err("`claude` not found on PATH.", "Install Claude Code first.");
3856
3925
  } else {
3857
- console.error(`✗ failed to launch claude: ${err.message}`);
3926
+ render.err(`failed to launch claude: ${err.message}`);
3858
3927
  }
3859
3928
  process.exit(1);
3860
3929
  }
@@ -3869,6 +3938,7 @@ var init_launch = __esm(() => {
3869
3938
  init_facade6();
3870
3939
  init_facade5();
3871
3940
  init_facade8();
3941
+ init_render();
3872
3942
  init_styles();
3873
3943
  init_screen();
3874
3944
  init_spinner();
@@ -3918,13 +3988,13 @@ function prompt(question) {
3918
3988
  });
3919
3989
  }
3920
3990
  async function loginWithToken() {
3921
- console.log(`
3922
- Paste a token from ${dim(URLS.API_BASE + "/token")}`);
3923
- console.log(` ${dim("Generate one in your browser, then paste it here.")}
3924
- `);
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();
3925
3995
  const token = await prompt(" Token: ");
3926
3996
  if (!token) {
3927
- console.error(` ${icons.cross} No token provided.`);
3997
+ render.err("No token provided.");
3928
3998
  return EXIT.AUTH_FAILED;
3929
3999
  }
3930
4000
  let user = { id: "", display_name: "", email: "" };
@@ -3933,7 +4003,7 @@ async function loginWithToken() {
3933
4003
  if (parts[1]) {
3934
4004
  const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
3935
4005
  if (payload.exp && payload.exp < Date.now() / 1000) {
3936
- console.error(` ${icons.cross} Token expired. Generate a new one.`);
4006
+ render.err("Token expired.", "Generate a new one.");
3937
4007
  return EXIT.AUTH_FAILED;
3938
4008
  }
3939
4009
  user = {
@@ -3943,11 +4013,11 @@ async function loginWithToken() {
3943
4013
  };
3944
4014
  }
3945
4015
  } catch {
3946
- console.error(` ${icons.cross} Invalid token format.`);
4016
+ render.err("Invalid token format.");
3947
4017
  return EXIT.AUTH_FAILED;
3948
4018
  }
3949
4019
  storeToken({ session_token: token, user, token_source: "manual" });
3950
- 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")}`);
3951
4021
  return EXIT.SUCCESS;
3952
4022
  }
3953
4023
  async function syncMeshes(token) {
@@ -3955,7 +4025,7 @@ async function syncMeshes(token) {
3955
4025
  const meshes = await exports_my.getMeshes(token);
3956
4026
  if (meshes.length > 0) {
3957
4027
  const names = meshes.map((m) => m.slug).join(", ");
3958
- 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);
3959
4029
  }
3960
4030
  } catch {}
3961
4031
  }
@@ -3963,23 +4033,27 @@ async function login() {
3963
4033
  const existing = getStoredToken();
3964
4034
  if (existing) {
3965
4035
  const name = existing.user.display_name || existing.user.email || "unknown";
3966
- console.log(`
3967
- Already signed in as ${bold(name)}.`);
3968
- console.log("");
3969
- console.log(` ${bold("1)")} Continue as ${name}`);
3970
- console.log(` ${bold("2)")} Sign in via browser`);
3971
- console.log(` ${bold("3)")} Paste a token from ${dim("claudemesh.com/token")}`);
3972
- console.log(` ${bold("4)")} Sign out`);
3973
- 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();
3974
4048
  const choice = await prompt(" Choice [1]: ") || "1";
3975
4049
  if (choice === "1") {
3976
- console.log(`
3977
- ${green(icons.check)} Continuing as ${name}.`);
4050
+ render.blank();
4051
+ render.ok(`continuing as ${bold(name)}`);
3978
4052
  return EXIT.SUCCESS;
3979
4053
  }
3980
4054
  if (choice === "4") {
3981
4055
  clearToken();
3982
- console.log(` ${green(icons.check)} Signed out.`);
4056
+ render.ok("signed out");
3983
4057
  return EXIT.SUCCESS;
3984
4058
  }
3985
4059
  if (choice === "3") {
@@ -3987,14 +4061,16 @@ async function login() {
3987
4061
  return loginWithToken();
3988
4062
  }
3989
4063
  clearToken();
3990
- console.log(` ${dim("Signing in…")}`);
4064
+ render.info(dim("Signing in…"));
3991
4065
  } else {
3992
- console.log(`
3993
- ${bold("claudemesh")} — sign in to connect your terminal`);
3994
- console.log("");
3995
- console.log(` ${bold("1)")} Sign in via browser ${dim("(opens automatically)")}`);
3996
- console.log(` ${bold("2)")} Paste a token from ${dim("claudemesh.com/token")}`);
3997
- 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();
3998
4074
  const choice = await prompt(" Choice [1]: ") || "1";
3999
4075
  if (choice === "2") {
4000
4076
  return loginWithToken();
@@ -4002,91 +4078,23 @@ async function login() {
4002
4078
  }
4003
4079
  try {
4004
4080
  const result = await loginWithDeviceCode();
4005
- console.log(` ${green(icons.check)} Signed in as ${result.user.display_name}.`);
4081
+ render.ok(`signed in as ${bold(result.user.display_name)}`);
4006
4082
  await syncMeshes(result.session_token);
4007
4083
  return EXIT.SUCCESS;
4008
4084
  } catch (err) {
4009
- console.error(` ${icons.cross} Login failed: ${err instanceof Error ? err.message : err}`);
4085
+ render.err(`Login failed: ${err instanceof Error ? err.message : err}`);
4010
4086
  return EXIT.AUTH_FAILED;
4011
4087
  }
4012
4088
  }
4013
4089
  var init_login = __esm(() => {
4014
4090
  init_facade6();
4015
4091
  init_facade3();
4092
+ init_render();
4016
4093
  init_styles();
4017
4094
  init_exit_codes();
4018
4095
  init_urls();
4019
4096
  });
4020
4097
 
4021
- // src/ui/render.ts
4022
- var OUT, ERR, INDENT = " ", render;
4023
- var init_render = __esm(() => {
4024
- init_styles();
4025
- OUT = process.stdout;
4026
- ERR = process.stderr;
4027
- render = {
4028
- blank() {
4029
- OUT.write(`
4030
- `);
4031
- },
4032
- ok(msg, detail) {
4033
- const d = detail ? ` ${dim("(" + detail + ")")}` : "";
4034
- OUT.write(`${INDENT}${green(icons.check)} ${msg}${d}
4035
- `);
4036
- },
4037
- warn(msg, hint) {
4038
- OUT.write(`${INDENT}${yellow(icons.warn)} ${msg}
4039
- `);
4040
- if (hint)
4041
- OUT.write(`${INDENT} ${dim(hint)}
4042
- `);
4043
- },
4044
- err(msg, hint) {
4045
- ERR.write(`${INDENT}${red(icons.cross)} ${msg}
4046
- `);
4047
- if (hint)
4048
- ERR.write(`${INDENT} ${dim(hint)}
4049
- `);
4050
- },
4051
- info(msg) {
4052
- OUT.write(`${INDENT}${msg}
4053
- `);
4054
- },
4055
- section(title) {
4056
- OUT.write(`
4057
- ${INDENT}${dim("—")} ${clay(title)}
4058
-
4059
- `);
4060
- },
4061
- heading(title) {
4062
- OUT.write(`${INDENT}${bold(title)}
4063
- `);
4064
- },
4065
- kv(pairs, opts) {
4066
- const pad = opts?.padTo ?? Math.max(...pairs.map(([k]) => k.length)) + 2;
4067
- for (const [k, v] of pairs) {
4068
- OUT.write(`${INDENT}${dim(k.padEnd(pad, " "))}${v}
4069
- `);
4070
- }
4071
- },
4072
- code(snippet) {
4073
- for (const line of snippet.split(`
4074
- `)) {
4075
- OUT.write(`${INDENT} ${cyan(line)}
4076
- `);
4077
- }
4078
- },
4079
- link(url) {
4080
- OUT.write(`${INDENT}${clay(url)}
4081
- `);
4082
- },
4083
- hint(msg) {
4084
- OUT.write(`${INDENT}${dim(icons.arrow + " " + msg)}
4085
- `);
4086
- }
4087
- };
4088
- });
4089
-
4090
4098
  // src/commands/welcome.ts
4091
4099
  var exports_welcome = {};
4092
4100
  __export(exports_welcome, {
@@ -4251,17 +4259,17 @@ __export(exports_new, {
4251
4259
  });
4252
4260
  async function newMesh(name, opts) {
4253
4261
  if (!name) {
4254
- console.error(" Usage: claudemesh mesh create <name>");
4262
+ render.err("Usage: claudemesh create <name>");
4255
4263
  return EXIT.INVALID_ARGS;
4256
4264
  }
4257
4265
  if (!getStoredToken()) {
4258
- console.log(dim(` Not signed in — starting login…
4259
- `));
4266
+ render.info(dim("not signed in — starting login…"));
4267
+ render.blank();
4260
4268
  const { login: login2 } = await Promise.resolve().then(() => (init_login(), exports_login));
4261
4269
  const loginResult = await login2();
4262
4270
  if (loginResult !== EXIT.SUCCESS)
4263
4271
  return loginResult;
4264
- console.log("");
4272
+ render.blank();
4265
4273
  }
4266
4274
  try {
4267
4275
  const result = await createMesh2(name, {
@@ -4270,22 +4278,24 @@ async function newMesh(name, opts) {
4270
4278
  });
4271
4279
  if (opts.json) {
4272
4280
  console.log(JSON.stringify({ schema_version: "1.0", ...result }, null, 2));
4273
- } else {
4274
- console.log(`
4275
- ${green(icons.check)} Created "${result.slug}" (id: ${result.id})`);
4276
- console.log(` ${green(icons.check)} You're the owner`);
4277
- console.log(` ${green(icons.check)} Joined locally`);
4278
- console.log(`
4279
- Share with: claudemesh mesh share
4280
- `);
4281
+ return EXIT.SUCCESS;
4281
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();
4282
4292
  return EXIT.SUCCESS;
4283
4293
  } catch (err) {
4284
4294
  const msg = err instanceof Error ? err.message : String(err);
4285
4295
  if (msg.includes("409") || msg.includes("already exists")) {
4286
- 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.");
4287
4297
  } else {
4288
- console.error(` ${icons.cross} Failed: ${msg}`);
4298
+ render.err(`Failed: ${msg}`);
4289
4299
  }
4290
4300
  return EXIT.INTERNAL_ERROR;
4291
4301
  }
@@ -4293,6 +4303,7 @@ async function newMesh(name, opts) {
4293
4303
  var init_new = __esm(() => {
4294
4304
  init_facade10();
4295
4305
  init_facade6();
4306
+ init_render();
4296
4307
  init_styles();
4297
4308
  init_exit_codes();
4298
4309
  });
@@ -4737,24 +4748,20 @@ async function runList() {
4737
4748
  const serverSlugs = new Set(serverMeshes.map((m) => m.slug));
4738
4749
  const allSlugs = new Set([...localSlugs, ...serverSlugs]);
4739
4750
  if (allSlugs.size === 0) {
4740
- console.log(`
4741
- No meshes yet.
4742
- `);
4743
- console.log(" Create one: claudemesh mesh create <name>");
4744
- console.log(` Join one: claudemesh mesh add <invite-url>
4745
- `);
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();
4746
4755
  return;
4747
4756
  }
4748
- console.log(`
4749
- Your meshes:
4750
- `);
4757
+ render.section(`your meshes (${allSlugs.size})`);
4751
4758
  for (const slug of allSlugs) {
4752
4759
  const local = config.meshes.find((m) => m.slug === slug);
4753
4760
  const server = serverMeshes.find((m) => m.slug === slug);
4754
4761
  const name = server?.name ?? local?.name ?? slug;
4755
4762
  const role = server?.role ?? "member";
4756
4763
  const isOwner = server?.is_owner ?? false;
4757
- const roleLabel = isOwner ? "owner" : role;
4764
+ const roleLabel = isOwner ? clay("owner") : dim(role);
4758
4765
  const memberCount = server?.member_count;
4759
4766
  const activePeers = server?.active_peers ?? 0;
4760
4767
  const inLocal = localSlugs.has(slug);
@@ -4773,15 +4780,18 @@ async function runList() {
4773
4780
  }
4774
4781
  const memberInfo = memberCount ? dim(`${memberCount} member${memberCount !== 1 ? "s" : ""}`) : "";
4775
4782
  const parts = [roleLabel, memberInfo, status].filter(Boolean);
4776
- console.log(` ${icon} ${bold(name)} ${dim(slug)}`);
4777
- console.log(` ${parts.join(" · ")}`);
4783
+ process.stdout.write(` ${icon} ${bold(name)} ${dim(slug)}
4784
+ `);
4785
+ process.stdout.write(` ${parts.join(dim(" · "))}
4786
+ `);
4778
4787
  }
4779
- console.log("");
4788
+ process.stdout.write(`
4789
+ `);
4780
4790
  if (serverMeshes.some((m) => !localSlugs.has(m.slug))) {
4781
- 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`);
4782
4792
  }
4783
- console.log(dim(` Config: ${getConfigPath()}`));
4784
- console.log("");
4793
+ render.hint(`config: ${dim(getConfigPath())}`);
4794
+ render.blank();
4785
4795
  }
4786
4796
  var BROKER_HTTP4;
4787
4797
  var init_list2 = __esm(() => {
@@ -4790,6 +4800,7 @@ var init_list2 = __esm(() => {
4790
4800
  init_facade3();
4791
4801
  init_urls();
4792
4802
  init_styles();
4803
+ init_render();
4793
4804
  BROKER_HTTP4 = URLS.BROKER.replace("wss://", "https://").replace("ws://", "http://").replace("/ws", "");
4794
4805
  });
4795
4806
 
@@ -4816,11 +4827,12 @@ function getUserId(token) {
4816
4827
  return "";
4817
4828
  }
4818
4829
  }
4819
- async function isOwner(slug, userId) {
4830
+ async function isOwner(slug, auth) {
4820
4831
  try {
4821
4832
  const res = await request({
4822
- path: `/cli/meshes?user_id=${userId}`,
4823
- baseUrl: BROKER_HTTP5
4833
+ path: `/cli/meshes`,
4834
+ baseUrl: BROKER_HTTP5,
4835
+ token: auth.session_token
4824
4836
  });
4825
4837
  return res.meshes?.find((m) => m.slug === slug)?.is_owner ?? false;
4826
4838
  } catch {
@@ -4831,86 +4843,88 @@ async function deleteMesh(slug, opts = {}) {
4831
4843
  const config = readConfig();
4832
4844
  if (!slug) {
4833
4845
  if (config.meshes.length === 0) {
4834
- console.error(" No meshes to remove.");
4846
+ render.err("No meshes to remove.");
4835
4847
  return EXIT.NOT_FOUND;
4836
4848
  }
4837
- console.log(`
4838
- Select mesh to remove:
4839
- `);
4849
+ render.section("select mesh to remove");
4840
4850
  config.meshes.forEach((m, i) => {
4841
- 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
+ `);
4842
4853
  });
4843
- console.log("");
4844
- const choice = await prompt3(" Choice: ");
4854
+ render.blank();
4855
+ const choice = await prompt3(` ${dim("choice:")} `);
4845
4856
  const idx = parseInt(choice, 10) - 1;
4846
4857
  if (idx < 0 || idx >= config.meshes.length) {
4847
- console.log(" Cancelled.");
4858
+ render.info(dim("cancelled."));
4848
4859
  return EXIT.USER_CANCELLED;
4849
4860
  }
4850
4861
  slug = config.meshes[idx].slug;
4851
4862
  }
4852
4863
  const auth = getStoredToken();
4853
4864
  const userId = auth ? getUserId(auth.session_token) : "";
4854
- const ownerCheck = userId ? await isOwner(slug, userId) : false;
4865
+ const ownerCheck = auth ? await isOwner(slug, auth) : false;
4855
4866
  if (!opts.yes) {
4856
- console.log(`
4857
- ${bold(slug)}
4858
- `);
4867
+ render.section(slug);
4859
4868
  if (ownerCheck) {
4860
- console.log(` ${bold("1)")} Remove from this device only ${dim("(keep on server)")}`);
4861
- console.log(` ${bold("2)")} ${red("Delete everywhere")} ${dim("(removes for all members)")}`);
4862
- console.log(` ${bold("3)")} Cancel`);
4863
- console.log("");
4864
- 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";
4865
4877
  if (choice === "3") {
4866
- console.log(" Cancelled.");
4878
+ render.info(dim("cancelled."));
4867
4879
  return EXIT.USER_CANCELLED;
4868
4880
  }
4869
4881
  if (choice === "2") {
4870
- console.log(`
4871
- ${red("Warning:")} This will delete ${bold(slug)} for all members.`);
4872
- 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:`)} `);
4873
4885
  if (confirm.toLowerCase() !== slug.toLowerCase()) {
4874
- console.log(" Cancelled.");
4886
+ render.info(dim("cancelled."));
4875
4887
  return EXIT.USER_CANCELLED;
4876
4888
  }
4877
4889
  try {
4878
4890
  await request({
4879
4891
  path: `/cli/mesh/${slug}`,
4880
4892
  method: "DELETE",
4881
- body: { user_id: userId },
4882
- baseUrl: BROKER_HTTP5
4893
+ baseUrl: BROKER_HTTP5,
4894
+ token: auth?.session_token,
4895
+ body: { user_id: userId }
4883
4896
  });
4884
- console.log(` ${green(icons.check)} Deleted "${slug}" from server.`);
4897
+ render.ok(`deleted ${bold(slug)} from server.`);
4885
4898
  } catch (err) {
4886
- const msg = err instanceof Error ? err.message : String(err);
4887
- console.error(` ${icons.cross} Server delete failed: ${msg}`);
4899
+ render.err(`server delete failed: ${err instanceof Error ? err.message : String(err)}`);
4888
4900
  }
4889
4901
  leaveMesh(slug);
4890
- console.log(` ${green(icons.check)} Removed from local config.`);
4902
+ render.ok("removed from local config.");
4891
4903
  return EXIT.SUCCESS;
4892
4904
  }
4893
4905
  } else {
4894
- console.log(` ${bold("1)")} Remove from this device ${dim("(you can re-add later)")}`);
4895
- console.log(` ${bold("2)")} Cancel`);
4896
- if (!ownerCheck && userId) {
4897
- console.log(dim(`
4898
- ${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.");
4899
4913
  }
4900
- console.log("");
4901
- const choice = await prompt3(" Choice [1]: ") || "1";
4914
+ render.blank();
4915
+ const choice = await prompt3(` ${dim("choice [1]:")} `) || "1";
4902
4916
  if (choice === "2") {
4903
- console.log(" Cancelled.");
4917
+ render.info(dim("cancelled."));
4904
4918
  return EXIT.USER_CANCELLED;
4905
4919
  }
4906
4920
  }
4907
4921
  }
4908
4922
  const removed = leaveMesh(slug);
4909
4923
  if (removed) {
4910
- console.log(` ${green(icons.check)} Removed "${slug}" from this device.`);
4911
- 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>")}`);
4912
4926
  } else {
4913
- console.error(` Mesh "${slug}" not found in local config.`);
4927
+ render.err(`mesh "${slug}" not found in local config.`);
4914
4928
  }
4915
4929
  return EXIT.SUCCESS;
4916
4930
  }
@@ -4921,6 +4935,7 @@ var init_delete_mesh = __esm(() => {
4921
4935
  init_facade6();
4922
4936
  init_facade3();
4923
4937
  init_urls();
4938
+ init_render();
4924
4939
  init_styles();
4925
4940
  init_exit_codes();
4926
4941
  BROKER_HTTP5 = URLS.BROKER.replace("wss://", "https://").replace("ws://", "http://").replace("/ws", "");
@@ -6522,12 +6537,10 @@ __export(exports_state, {
6522
6537
  runStateGet: () => runStateGet
6523
6538
  });
6524
6539
  async function runStateGet(flags, key) {
6525
- const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
6526
- const dim2 = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
6527
6540
  await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
6528
6541
  const entry = await client.getState(key);
6529
6542
  if (!entry) {
6530
- console.log(dim2(`(not set)`));
6543
+ render.info(dim("(not set)"));
6531
6544
  return;
6532
6545
  }
6533
6546
  if (flags.json) {
@@ -6535,8 +6548,8 @@ async function runStateGet(flags, key) {
6535
6548
  return;
6536
6549
  }
6537
6550
  const val = typeof entry.value === "string" ? entry.value : JSON.stringify(entry.value);
6538
- console.log(val);
6539
- 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()}`));
6540
6553
  });
6541
6554
  }
6542
6555
  async function runStateSet(flags, key, value) {
@@ -6548,13 +6561,10 @@ async function runStateSet(flags, key, value) {
6548
6561
  }
6549
6562
  await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
6550
6563
  await client.setState(key, parsed);
6551
- console.log(`✓ ${key} = ${JSON.stringify(parsed)}`);
6564
+ render.ok(`${bold(key)} = ${JSON.stringify(parsed)}`);
6552
6565
  });
6553
6566
  }
6554
6567
  async function runStateList(flags) {
6555
- const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
6556
- const dim2 = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
6557
- const bold2 = (s) => useColor ? `\x1B[1m${s}\x1B[22m` : s;
6558
6568
  await withMesh({ meshSlug: flags.mesh ?? null }, async (client, mesh) => {
6559
6569
  const entries = await client.listState();
6560
6570
  if (flags.json) {
@@ -6562,18 +6572,23 @@ async function runStateList(flags) {
6562
6572
  return;
6563
6573
  }
6564
6574
  if (entries.length === 0) {
6565
- console.log(dim2(`No state on mesh "${mesh.slug}".`));
6575
+ render.info(dim(`No state on mesh "${mesh.slug}".`));
6566
6576
  return;
6567
6577
  }
6578
+ render.section(`state (${entries.length})`);
6568
6579
  for (const e of entries) {
6569
6580
  const val = typeof e.value === "string" ? e.value : JSON.stringify(e.value);
6570
- console.log(`${bold2(e.key)}: ${val}`);
6571
- 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
+ `);
6572
6585
  }
6573
6586
  });
6574
6587
  }
6575
6588
  var init_state = __esm(() => {
6576
6589
  init_connect();
6590
+ init_render();
6591
+ init_styles();
6577
6592
  });
6578
6593
 
6579
6594
  // src/commands/info.ts
@@ -6637,7 +6652,7 @@ __export(exports_remember, {
6637
6652
  async function remember(content, opts = {}) {
6638
6653
  const client = allClients()[0];
6639
6654
  if (!client) {
6640
- console.error("Not connected to any mesh.");
6655
+ render.err("Not connected to any mesh.");
6641
6656
  return EXIT.NETWORK_ERROR;
6642
6657
  }
6643
6658
  const tags = opts.tags?.split(",").map((t) => t.trim()).filter(Boolean);
@@ -6647,14 +6662,16 @@ async function remember(content, opts = {}) {
6647
6662
  return EXIT.SUCCESS;
6648
6663
  }
6649
6664
  if (id) {
6650
- console.log(`✓ Remembered (${id.slice(0, 8)})`);
6665
+ render.ok("remembered", dim(id.slice(0, 8)));
6651
6666
  return EXIT.SUCCESS;
6652
6667
  }
6653
- console.error(" Failed to store memory");
6668
+ render.err("failed to store memory");
6654
6669
  return EXIT.INTERNAL_ERROR;
6655
6670
  }
6656
6671
  var init_remember = __esm(() => {
6657
6672
  init_facade8();
6673
+ init_render();
6674
+ init_styles();
6658
6675
  init_exit_codes();
6659
6676
  });
6660
6677
 
@@ -6666,7 +6683,7 @@ __export(exports_recall, {
6666
6683
  async function recall(query, opts = {}) {
6667
6684
  const client = allClients()[0];
6668
6685
  if (!client) {
6669
- console.error("Not connected to any mesh.");
6686
+ render.err("Not connected to any mesh.");
6670
6687
  return EXIT.NETWORK_ERROR;
6671
6688
  }
6672
6689
  const memories = await client.recall(query);
@@ -6675,20 +6692,25 @@ async function recall(query, opts = {}) {
6675
6692
  return EXIT.SUCCESS;
6676
6693
  }
6677
6694
  if (memories.length === 0) {
6678
- console.log(dim("No memories found."));
6695
+ render.info(dim("no memories found."));
6679
6696
  return EXIT.SUCCESS;
6680
6697
  }
6698
+ render.section(`memories (${memories.length})`);
6681
6699
  for (const m of memories) {
6682
- const tags = m.tags.length ? dim(` [${m.tags.join(", ")}]`) : "";
6683
- console.log(`${bold(m.id.slice(0, 8))}${tags}`);
6684
- console.log(` ${m.content}`);
6685
- console.log(dim(` ${m.rememberedBy} · ${new Date(m.rememberedAt).toLocaleString()}`));
6686
- 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
+ `);
6687
6708
  }
6688
6709
  return EXIT.SUCCESS;
6689
6710
  }
6690
6711
  var init_recall = __esm(() => {
6691
6712
  init_facade8();
6713
+ init_render();
6692
6714
  init_styles();
6693
6715
  init_exit_codes();
6694
6716
  });
@@ -6735,9 +6757,6 @@ function parseDeliverAt(flags) {
6735
6757
  return null;
6736
6758
  }
6737
6759
  async function runRemind(flags, positional) {
6738
- const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
6739
- const dim2 = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
6740
- const bold2 = (s) => useColor ? `\x1B[1m${s}\x1B[22m` : s;
6741
6760
  const action = positional[0];
6742
6761
  if (action === "list") {
6743
6762
  await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
@@ -6747,15 +6766,18 @@ async function runRemind(flags, positional) {
6747
6766
  return;
6748
6767
  }
6749
6768
  if (scheduled.length === 0) {
6750
- console.log(dim2("No pending reminders."));
6769
+ render.info(dim("No pending reminders."));
6751
6770
  return;
6752
6771
  }
6772
+ render.section(`reminders (${scheduled.length})`);
6753
6773
  for (const m of scheduled) {
6754
6774
  const when = new Date(m.deliverAt).toLocaleString();
6755
- const to = m.to === client.getSessionPubkey() ? dim2("(self)") : m.to;
6756
- console.log(` ${bold2(m.id.slice(0, 8))} → ${to} at ${when}`);
6757
- console.log(` ${dim2(m.message.slice(0, 80))}`);
6758
- 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
+ `);
6759
6781
  }
6760
6782
  });
6761
6783
  return;
@@ -6763,15 +6785,15 @@ async function runRemind(flags, positional) {
6763
6785
  if (action === "cancel") {
6764
6786
  const id = positional[1];
6765
6787
  if (!id) {
6766
- console.error("Usage: claudemesh remind cancel <id>");
6788
+ render.err("Usage: claudemesh remind cancel <id>");
6767
6789
  process.exit(1);
6768
6790
  }
6769
6791
  await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
6770
6792
  const ok = await client.cancelScheduled(id);
6771
6793
  if (ok)
6772
- console.log(`✓ Cancelled ${id}`);
6794
+ render.ok(`cancelled ${bold(id.slice(0, 8))}`);
6773
6795
  else {
6774
- console.error(`✗ Not found or already fired: ${id}`);
6796
+ render.err(`not found or already fired: ${id}`);
6775
6797
  process.exit(1);
6776
6798
  }
6777
6799
  });
@@ -6779,17 +6801,17 @@ async function runRemind(flags, positional) {
6779
6801
  }
6780
6802
  const message = action ?? positional.join(" ");
6781
6803
  if (!message) {
6782
- console.error("Usage: claudemesh remind <message> --in <duration>");
6783
- console.error(" claudemesh remind <message> --at <time>");
6784
- console.error(' claudemesh remind <message> --cron "0 */2 * * *"');
6785
- console.error(" claudemesh remind list");
6786
- 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>"));
6787
6809
  process.exit(1);
6788
6810
  }
6789
6811
  const isCron = !!flags.cron;
6790
6812
  const deliverAt = isCron ? 0 : parseDeliverAt(flags);
6791
6813
  if (!isCron && deliverAt === null) {
6792
- 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>');
6793
6815
  process.exit(1);
6794
6816
  }
6795
6817
  await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
@@ -6801,7 +6823,7 @@ async function runRemind(flags, positional) {
6801
6823
  const peers = await client.listPeers();
6802
6824
  const match = peers.find((p) => p.displayName.toLowerCase() === flags.to.toLowerCase());
6803
6825
  if (!match) {
6804
- 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)"}`);
6805
6827
  process.exit(1);
6806
6828
  }
6807
6829
  targetSpec = match.pubkey;
@@ -6811,7 +6833,7 @@ async function runRemind(flags, positional) {
6811
6833
  }
6812
6834
  const result = await client.scheduleMessage(targetSpec, message, deliverAt ?? 0, false, flags.cron);
6813
6835
  if (!result) {
6814
- console.error("Broker did not acknowledge — check connection");
6836
+ render.err("Broker did not acknowledge — check connection");
6815
6837
  process.exit(1);
6816
6838
  }
6817
6839
  if (flags.json) {
@@ -6821,15 +6843,17 @@ async function runRemind(flags, positional) {
6821
6843
  const toLabel = !flags.to || flags.to === "self" ? "yourself" : flags.to;
6822
6844
  if (isCron) {
6823
6845
  const nextFire = new Date(result.deliverAt).toLocaleString();
6824
- 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}`);
6825
6847
  } else {
6826
6848
  const when = new Date(result.deliverAt).toLocaleString();
6827
- 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}`);
6828
6850
  }
6829
6851
  });
6830
6852
  }
6831
6853
  var init_remind = __esm(() => {
6832
6854
  init_connect();
6855
+ init_render();
6856
+ init_styles();
6833
6857
  });
6834
6858
 
6835
6859
  // src/commands/profile.ts
@@ -6969,20 +6993,21 @@ async function whoami(opts) {
6969
6993
  return EXIT.SUCCESS;
6970
6994
  }
6971
6995
  if (!result.signed_in) {
6972
- console.log(` Not signed in. Run \`claudemesh login\` to sign in.`);
6996
+ render.err("Not signed in", "Run `claudemesh login` to sign in.");
6973
6997
  return EXIT.AUTH_FAILED;
6974
6998
  }
6975
- console.log(`
6976
- Signed in as ${result.user.display_name} (${result.user.email})`);
6977
- console.log(` Token source: ${result.token_source} ${dim("(~/.claudemesh/auth.json)")}`);
6978
- if (result.meshes) {
6979
- console.log(` Meshes: ${result.meshes.owned} owned, ${result.meshes.guest} guest`);
6980
- }
6981
- 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();
6982
7006
  return EXIT.SUCCESS;
6983
7007
  }
6984
7008
  var init_whoami = __esm(() => {
6985
7009
  init_facade6();
7010
+ init_render();
6986
7011
  init_styles();
6987
7012
  init_exit_codes();
6988
7013
  });
@@ -7198,16 +7223,15 @@ function installStatusLine() {
7198
7223
  function runInstall(args = []) {
7199
7224
  const skipHooks = args.includes("--no-hooks");
7200
7225
  const wantStatusLine = args.includes("--status-line");
7201
- console.log("claudemesh install");
7202
- console.log("------------------");
7226
+ render.section("claudemesh install");
7203
7227
  const entry = resolveEntry();
7204
7228
  const isBundled = entry.endsWith("/dist/index.js") || entry.endsWith("\\dist\\index.js");
7205
7229
  if (!isBundled && !bunAvailable()) {
7206
- 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");
7207
7231
  process.exit(1);
7208
7232
  }
7209
7233
  if (!existsSync5(entry)) {
7210
- console.error(`✗ MCP entry not found at ${entry}`);
7234
+ render.err(`MCP entry not found at ${entry}`);
7211
7235
  process.exit(1);
7212
7236
  }
7213
7237
  const desired = buildMcpEntry(entry);
@@ -7216,56 +7240,53 @@ function runInstall(args = []) {
7216
7240
  const verifyServers = verify2.mcpServers ?? {};
7217
7241
  const stored = verifyServers[MCP_NAME];
7218
7242
  if (!stored || !entriesEqual(stored, desired)) {
7219
- console.error(`✗ post-write verification failed — ${CLAUDE_CONFIG} may be corrupt`);
7243
+ render.err("post-write verification failed", `${CLAUDE_CONFIG} may be corrupt`);
7220
7244
  process.exit(1);
7221
7245
  }
7222
- const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
7223
- const bold2 = (s) => useColor ? `\x1B[1m${s}\x1B[22m` : s;
7224
- const yellow2 = (s) => useColor ? `\x1B[33m${s}\x1B[39m` : s;
7225
- const dim2 = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
7226
- console.log(`✓ MCP server "${MCP_NAME}" ${action}`);
7227
- console.log(dim2(` config: ${CLAUDE_CONFIG}`));
7228
- 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
+ ]);
7229
7251
  try {
7230
7252
  const { added, unchanged } = installAllowedTools();
7231
7253
  if (added.length > 0) {
7232
- console.log(`✓ allowedTools: ${added.length} claudemesh tools pre-approved${unchanged > 0 ? `, ${unchanged} already present` : ""}`);
7233
- console.log(dim2(` This lets claudemesh tools run without --dangerously-skip-permissions.`));
7234
- 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."));
7235
7257
  } else {
7236
- console.log(`✓ allowedTools: all ${unchanged} claudemesh tools already pre-approved`);
7258
+ render.ok(`allowedTools: all ${unchanged} claudemesh tools already pre-approved`);
7237
7259
  }
7238
- console.log(dim2(` config: ${CLAUDE_SETTINGS}`));
7260
+ render.info(dim(` config: ${CLAUDE_SETTINGS}`));
7239
7261
  } catch (e) {
7240
- 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)}`);
7241
7263
  }
7242
7264
  if (!skipHooks) {
7243
7265
  try {
7244
7266
  const { added, unchanged } = installHooks();
7245
7267
  if (added > 0) {
7246
- console.log(`✓ Hooks registered (Stop + UserPromptSubmit) → ${added} added, ${unchanged} already present`);
7268
+ render.ok(`Hooks registered (Stop + UserPromptSubmit)`, `${added} added, ${unchanged} already present`);
7247
7269
  } else {
7248
- console.log(`✓ Hooks already registered (${unchanged} present)`);
7270
+ render.ok(`Hooks already registered`, `${unchanged} present`);
7249
7271
  }
7250
- console.log(dim2(` config: ${CLAUDE_SETTINGS}`));
7272
+ render.info(dim(` config: ${CLAUDE_SETTINGS}`));
7251
7273
  } catch (e) {
7252
- console.error(`⚠ hook registration failed: ${e instanceof Error ? e.message : String(e)}`);
7253
- 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.");
7254
7275
  }
7255
7276
  } else {
7256
- console.log(dim2("· Hooks skipped (--no-hooks)"));
7277
+ render.info(dim("· Hooks skipped (--no-hooks)"));
7257
7278
  }
7258
7279
  if (wantStatusLine) {
7259
7280
  try {
7260
7281
  const { installed } = installStatusLine();
7261
7282
  if (installed) {
7262
- console.log(`✓ Claude Code statusLine → \`claudemesh status-line\``);
7263
- 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>"));
7264
7285
  } else {
7265
- 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"));
7266
7287
  }
7267
7288
  } catch (e) {
7268
- 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)}`);
7269
7290
  }
7270
7291
  }
7271
7292
  let hasMeshes = false;
@@ -7273,57 +7294,58 @@ function runInstall(args = []) {
7273
7294
  const meshConfig = readConfig();
7274
7295
  hasMeshes = meshConfig.meshes.length > 0;
7275
7296
  } catch {}
7276
- console.log("");
7277
- 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.")}`);
7278
7299
  if (!hasMeshes) {
7279
- console.log("");
7280
- console.log(yellow2("No meshes joined.") + " To connect with peers:");
7281
- console.log(` ${bold2("claudemesh <invite-url>")}` + dim2(" — joins + launches in one step"));
7282
- 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")}`);
7283
7304
  } else {
7284
- console.log("");
7285
- 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")}`);
7286
7307
  }
7287
- console.log("");
7288
- console.log(dim2("Optional:"));
7289
- console.log(dim2(` claudemesh url-handler install # click-to-launch from email`));
7290
- console.log(dim2(` claudemesh install --status-line # live peer count in Claude Code`));
7291
- 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`));
7292
7313
  }
7293
7314
  function runUninstall() {
7294
- console.log("claudemesh uninstall");
7295
- console.log("--------------------");
7315
+ render.section("claudemesh uninstall");
7296
7316
  if (removeMcpServer()) {
7297
- console.log(`✓ MCP server "${MCP_NAME}" removed`);
7317
+ render.ok(`MCP server "${bold(MCP_NAME)}" removed`);
7298
7318
  } else {
7299
- console.log(`· MCP server "${MCP_NAME}" not present`);
7319
+ render.info(dim(`· MCP server "${MCP_NAME}" not present`));
7300
7320
  }
7301
7321
  try {
7302
7322
  const removed = uninstallAllowedTools();
7303
7323
  if (removed > 0) {
7304
- console.log(`✓ allowedTools: ${removed} claudemesh tools removed`);
7324
+ render.ok(`allowedTools: ${removed} claudemesh tools removed`);
7305
7325
  } else {
7306
- console.log("· No claudemesh allowedTools to remove");
7326
+ render.info(dim("· No claudemesh allowedTools to remove"));
7307
7327
  }
7308
7328
  } catch (e) {
7309
- 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)}`);
7310
7330
  }
7311
7331
  try {
7312
7332
  const removed = uninstallHooks();
7313
7333
  if (removed > 0) {
7314
- console.log(`✓ Hooks removed (${removed} entries)`);
7334
+ render.ok(`Hooks removed`, `${removed} entries`);
7315
7335
  } else {
7316
- console.log("· No claudemesh hooks to remove");
7336
+ render.info(dim("· No claudemesh hooks to remove"));
7317
7337
  }
7318
7338
  } catch (e) {
7319
- 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)}`);
7320
7340
  }
7321
- console.log("");
7322
- 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.");
7323
7343
  }
7324
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;
7325
7345
  var init_install = __esm(() => {
7326
7346
  init_facade();
7347
+ init_render();
7348
+ init_styles();
7327
7349
  CLAUDE_CONFIG = join4(homedir4(), ".claude.json");
7328
7350
  CLAUDE_SETTINGS = join4(homedir4(), ".claude", "settings.json");
7329
7351
  CLAUDEMESH_TOOLS = [
@@ -7392,7 +7414,7 @@ async function uninstall() {
7392
7414
  delete servers.claudemesh;
7393
7415
  writeFileSync6(PATHS.CLAUDE_JSON, JSON.stringify(config, null, 2) + `
7394
7416
  `, "utf-8");
7395
- console.log(` ${green(icons.check)} Removed MCP server from ~/.claude.json`);
7417
+ render.ok("removed MCP server", dim("~/.claude.json"));
7396
7418
  removed++;
7397
7419
  }
7398
7420
  } catch {}
@@ -7422,19 +7444,20 @@ async function uninstall() {
7422
7444
  if (removedHooks > 0) {
7423
7445
  writeFileSync6(PATHS.CLAUDE_SETTINGS, JSON.stringify(config, null, 2) + `
7424
7446
  `, "utf-8");
7425
- 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"));
7426
7448
  removed++;
7427
7449
  }
7428
7450
  }
7429
7451
  } catch {}
7430
7452
  }
7431
7453
  if (removed === 0) {
7432
- console.log(" Nothing to remove — claudemesh was not installed.");
7454
+ render.info(dim("Nothing to remove — claudemesh was not installed."));
7433
7455
  }
7434
7456
  return EXIT.SUCCESS;
7435
7457
  }
7436
7458
  var init_uninstall = __esm(() => {
7437
7459
  init_paths();
7460
+ init_render();
7438
7461
  init_styles();
7439
7462
  init_exit_codes();
7440
7463
  });
@@ -7664,7 +7687,7 @@ async function runDoctor() {
7664
7687
  const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
7665
7688
  const dim2 = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
7666
7689
  const green3 = (s) => useColor ? `\x1B[32m${s}\x1B[39m` : s;
7667
- const red3 = (s) => useColor ? `\x1B[31m${s}\x1B[39m` : s;
7690
+ const red2 = (s) => useColor ? `\x1B[31m${s}\x1B[39m` : s;
7668
7691
  console.log(`claudemesh doctor (v${VERSION})`);
7669
7692
  console.log("─".repeat(60));
7670
7693
  const checks = [
@@ -7678,7 +7701,7 @@ async function runDoctor() {
7678
7701
  await checkNpmLatest()
7679
7702
  ];
7680
7703
  for (const c of checks) {
7681
- const mark = c.pass ? green3("✓") : red3("✗");
7704
+ const mark = c.pass ? green3("✓") : red2("✗");
7682
7705
  const detail = c.detail ? dim2(` (${c.detail})`) : "";
7683
7706
  console.log(`${mark} ${c.name}${detail}`);
7684
7707
  if (!c.pass && c.fix) {
@@ -7691,7 +7714,7 @@ async function runDoctor() {
7691
7714
  console.log(green3("All checks passed."));
7692
7715
  process.exit(0);
7693
7716
  } else {
7694
- console.log(red3(`${failing.length} check(s) failed.`));
7717
+ console.log(red2(`${failing.length} check(s) failed.`));
7695
7718
  process.exit(1);
7696
7719
  }
7697
7720
  }
@@ -8269,7 +8292,7 @@ complete -c claudemesh -l join -d 'invite url'
8269
8292
  }
8270
8293
  async function runCompletions(shell) {
8271
8294
  if (!shell) {
8272
- console.error("Usage: claudemesh completions <bash|zsh|fish>");
8295
+ render.err("Usage: claudemesh completions <bash|zsh|fish>");
8273
8296
  return EXIT.INVALID_ARGS;
8274
8297
  }
8275
8298
  switch (shell.toLowerCase()) {
@@ -8283,13 +8306,14 @@ async function runCompletions(shell) {
8283
8306
  process.stdout.write(fish());
8284
8307
  return EXIT.SUCCESS;
8285
8308
  default:
8286
- console.error(`Unsupported shell: ${shell}. Use bash, zsh, or fish.`);
8309
+ render.err(`Unsupported shell: ${shell}`, "use bash, zsh, or fish.");
8287
8310
  return EXIT.INVALID_ARGS;
8288
8311
  }
8289
8312
  }
8290
8313
  var COMMANDS, FLAGS;
8291
8314
  var init_completions = __esm(() => {
8292
8315
  init_exit_codes();
8316
+ init_render();
8293
8317
  COMMANDS = [
8294
8318
  "create",
8295
8319
  "new",
@@ -8372,26 +8396,22 @@ function safetyNumber(myPubkey, peerPubkey) {
8372
8396
  return groups.join(" ");
8373
8397
  }
8374
8398
  async function runVerify(target, opts = {}) {
8375
- const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
8376
- const bold2 = (s) => useColor ? `\x1B[1m${s}\x1B[22m` : s;
8377
- const dim2 = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
8378
- const clay2 = (s) => useColor ? `\x1B[38;2;217;119;87m${s}\x1B[39m` : s;
8379
8399
  const config = readConfig();
8380
8400
  const meshSlug = opts.mesh ?? config.meshes[0]?.slug;
8381
8401
  if (!meshSlug) {
8382
- console.error(" No meshes joined. Run `claudemesh join <url>` first.");
8402
+ render.err("No meshes joined.", "Run `claudemesh join <url>` first.");
8383
8403
  return EXIT.NOT_FOUND;
8384
8404
  }
8385
8405
  const mesh = config.meshes.find((m) => m.slug === meshSlug);
8386
8406
  if (!mesh) {
8387
- console.error(` Mesh "${meshSlug}" not found locally.`);
8407
+ render.err(`Mesh "${meshSlug}" not found locally.`);
8388
8408
  return EXIT.NOT_FOUND;
8389
8409
  }
8390
8410
  return await withMesh({ meshSlug }, async (client) => {
8391
8411
  const peers = await client.listPeers();
8392
8412
  const targets = target ? peers.filter((p) => p.displayName === target || p.pubkey === target || p.pubkey.startsWith(target)) : peers;
8393
8413
  if (targets.length === 0) {
8394
- console.error(` No peer matching "${target ?? "(all)"}" on mesh ${meshSlug}.`);
8414
+ render.err(`No peer matching "${target ?? "(all)"}" on mesh ${meshSlug}.`);
8395
8415
  return EXIT.NOT_FOUND;
8396
8416
  }
8397
8417
  if (opts.json) {
@@ -8403,19 +8423,20 @@ async function runVerify(target, opts = {}) {
8403
8423
  })), null, 2));
8404
8424
  return EXIT.SUCCESS;
8405
8425
  }
8406
- console.log("");
8407
- console.log(` ${dim2("— safety numbers on")} ${bold2(meshSlug)}`);
8408
- console.log("");
8426
+ render.section(`safety numbers on ${meshSlug}`);
8409
8427
  for (const p of targets) {
8410
8428
  const sn = safetyNumber(mesh.pubkey, p.pubkey);
8411
- console.log(` ${bold2(p.displayName)}`);
8412
- console.log(` ${clay2(sn)}`);
8413
- console.log(` ${dim2(`pubkey ${p.pubkey.slice(0, 16)}…`)}`);
8414
- 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
+ `);
8415
8436
  }
8416
- console.log(dim2(" Compare these digits with your peer (phone, in person, not chat)."));
8417
- console.log(dim2(" If they match on both sides, the channel is not being intercepted."));
8418
- 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();
8419
8440
  return EXIT.SUCCESS;
8420
8441
  });
8421
8442
  }
@@ -8423,6 +8444,8 @@ var init_verify = __esm(() => {
8423
8444
  init_connect();
8424
8445
  init_facade();
8425
8446
  init_exit_codes();
8447
+ init_render();
8448
+ init_styles();
8426
8449
  });
8427
8450
 
8428
8451
  // src/commands/url-handler.ts
@@ -8484,10 +8507,9 @@ EOF
8484
8507
  chmodSync4(shimPath, 493);
8485
8508
  const lsreg = spawnSync5("/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister", ["-f", appDir], { encoding: "utf-8" });
8486
8509
  if (lsreg.status !== 0) {
8487
- 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.");
8488
8511
  }
8489
- console.log(` ✓ Registered claudemesh:// scheme on macOS`);
8490
- console.log(` app bundle: ${appDir}`);
8512
+ render.ok("registered claudemesh:// scheme on macOS", dim(appDir));
8491
8513
  return EXIT.SUCCESS;
8492
8514
  }
8493
8515
  function installLinux() {
@@ -8508,12 +8530,11 @@ NoDisplay=true
8508
8530
  writeFileSync7(desktopPath, desktop);
8509
8531
  const xdg1 = spawnSync5("xdg-mime", ["default", "claudemesh.desktop", "x-scheme-handler/claudemesh"], { encoding: "utf-8" });
8510
8532
  if (xdg1.status !== 0) {
8511
- console.log("xdg-mime not available — skipped mime default registration");
8533
+ render.warn("xdg-mime not available — skipped mime default registration");
8512
8534
  }
8513
8535
  const xdg2 = spawnSync5("update-desktop-database", [appsDir], { encoding: "utf-8" });
8514
8536
  xdg2.status;
8515
- console.log(` ✓ Registered claudemesh:// scheme on Linux`);
8516
- console.log(` desktop entry: ${desktopPath}`);
8537
+ render.ok("registered claudemesh:// scheme on Linux", dim(desktopPath));
8517
8538
  return EXIT.SUCCESS;
8518
8539
  }
8519
8540
  function installWindows() {
@@ -8533,29 +8554,29 @@ function installWindows() {
8533
8554
  `));
8534
8555
  const res = spawnSync5("reg.exe", ["import", regPath], { encoding: "utf-8" });
8535
8556
  if (res.status !== 0) {
8536
- console.log(` ⚠ reg.exe import failed. Manual: double-click ${regPath}`);
8557
+ render.warn("reg.exe import failed", `manual: double-click ${regPath}`);
8537
8558
  return EXIT.INTERNAL_ERROR;
8538
8559
  }
8539
- console.log(` ✓ Registered claudemesh:// scheme on Windows`);
8560
+ render.ok("registered claudemesh:// scheme on Windows");
8540
8561
  return EXIT.SUCCESS;
8541
8562
  }
8542
8563
  function uninstallDarwin() {
8543
8564
  const appDir = join6(homedir6(), "Library", "Application Support", "claudemesh", "ClaudemeshHandler.app");
8544
8565
  if (existsSync13(appDir))
8545
8566
  rmSync2(appDir, { recursive: true, force: true });
8546
- console.log(" Removed claudemesh:// handler on macOS");
8567
+ render.ok("removed claudemesh:// handler on macOS");
8547
8568
  return EXIT.SUCCESS;
8548
8569
  }
8549
8570
  function uninstallLinux() {
8550
8571
  const desktopPath = join6(homedir6(), ".local", "share", "applications", "claudemesh.desktop");
8551
8572
  if (existsSync13(desktopPath))
8552
8573
  rmSync2(desktopPath, { force: true });
8553
- console.log(" Removed claudemesh:// handler on Linux");
8574
+ render.ok("removed claudemesh:// handler on Linux");
8554
8575
  return EXIT.SUCCESS;
8555
8576
  }
8556
8577
  function uninstallWindows() {
8557
8578
  spawnSync5("reg.exe", ["delete", "HKCU\\Software\\Classes\\claudemesh", "/f"], { encoding: "utf-8" });
8558
- console.log(" Removed claudemesh:// handler on Windows");
8579
+ render.ok("removed claudemesh:// handler on Windows");
8559
8580
  return EXIT.SUCCESS;
8560
8581
  }
8561
8582
  async function runUrlHandler(action) {
@@ -8576,14 +8597,16 @@ async function runUrlHandler(action) {
8576
8597
  if (p === "win32")
8577
8598
  return uninstallWindows();
8578
8599
  } else {
8579
- console.error("Usage: claudemesh url-handler <install|uninstall>");
8600
+ render.err("Usage: claudemesh url-handler <install|uninstall>");
8580
8601
  return EXIT.INVALID_ARGS;
8581
8602
  }
8582
- console.error(`Unsupported platform: ${p}`);
8603
+ render.err(`Unsupported platform: ${p}`);
8583
8604
  return EXIT.INTERNAL_ERROR;
8584
8605
  }
8585
8606
  var init_url_handler = __esm(() => {
8586
8607
  init_exit_codes();
8608
+ init_render();
8609
+ init_styles();
8587
8610
  });
8588
8611
 
8589
8612
  // src/commands/status-line.ts
@@ -12301,9 +12324,8 @@ __export(exports_seed_test_mesh, {
12301
12324
  function runSeedTestMesh(args) {
12302
12325
  const [brokerUrl, meshId, memberId, pubkey, slug] = args;
12303
12326
  if (!brokerUrl || !meshId || !memberId || !pubkey || !slug) {
12304
- console.error("Usage: claudemesh seed-test-mesh <broker-ws-url> <mesh-id> <member-id> <pubkey> <slug>");
12305
- console.error("");
12306
- 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'));
12307
12329
  process.exit(1);
12308
12330
  }
12309
12331
  const config = readConfig();
@@ -12319,11 +12341,13 @@ function runSeedTestMesh(args) {
12319
12341
  joinedAt: new Date().toISOString()
12320
12342
  });
12321
12343
  writeConfig(config);
12322
- console.log(`Seeded mesh "${slug}" (${meshId}) into local config.`);
12323
- 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")}`);
12324
12346
  }
12325
12347
  var init_seed_test_mesh = __esm(() => {
12326
12348
  init_facade();
12349
+ init_render();
12350
+ init_styles();
12327
12351
  });
12328
12352
 
12329
12353
  // src/cli/argv.ts
@@ -12830,4 +12854,4 @@ main().catch((err) => {
12830
12854
  process.exit(EXIT.INTERNAL_ERROR);
12831
12855
  });
12832
12856
 
12833
- //# debugId=4199659439A09F6C64756E2164756E21
12857
+ //# debugId=80234995420998CA64756E2164756E21