claudemesh-cli 1.0.0-alpha.28 → 1.0.0-alpha.29

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.
@@ -119,6 +119,22 @@ var init_timings = __esm(() => {
119
119
  };
120
120
  });
121
121
 
122
+ // src/utils/url.ts
123
+ function isInviteUrl(input) {
124
+ return /^https?:\/\/[^/]+\/(?:[a-z]{2}\/)?i\//.test(input) || /^https?:\/\/[^/]+\/(?:[a-z]{2}\/)?join\//.test(input) || /^ic:\/\//.test(input) || /^claudemesh:\/\//.test(input);
125
+ }
126
+ function normaliseInviteUrl(input, host = "claudemesh.com") {
127
+ const trimmed = input.trim();
128
+ if (trimmed.startsWith("claudemesh://")) {
129
+ const rest = trimmed.slice("claudemesh://".length).replace(/^\/+/, "");
130
+ const m = rest.match(/^(?:i|join)\/(.+)$/);
131
+ const tail = m ? m[1] : rest;
132
+ const kind = rest.startsWith("join/") ? "join" : "i";
133
+ return `https://${host}/${kind}/${tail}`;
134
+ }
135
+ return trimmed;
136
+ }
137
+
122
138
  // src/constants/paths.ts
123
139
  import { homedir } from "node:os";
124
140
  import { join } from "node:path";
@@ -3951,16 +3967,73 @@ var init_login = __esm(() => {
3951
3967
  init_urls();
3952
3968
  });
3953
3969
 
3954
- // src/commands/register.ts
3955
- var exports_register = {};
3956
- __export(exports_register, {
3957
- register: () => register2
3958
- });
3959
- async function register2() {
3960
- return login();
3961
- }
3962
- var init_register = __esm(() => {
3963
- init_login();
3970
+ // src/ui/render.ts
3971
+ var OUT, ERR, INDENT = " ", render;
3972
+ var init_render = __esm(() => {
3973
+ init_styles();
3974
+ OUT = process.stdout;
3975
+ ERR = process.stderr;
3976
+ render = {
3977
+ blank() {
3978
+ OUT.write(`
3979
+ `);
3980
+ },
3981
+ ok(msg, detail) {
3982
+ const d = detail ? ` ${dim("(" + detail + ")")}` : "";
3983
+ OUT.write(`${INDENT}${green(icons.check)} ${msg}${d}
3984
+ `);
3985
+ },
3986
+ warn(msg, hint) {
3987
+ OUT.write(`${INDENT}${yellow(icons.warn)} ${msg}
3988
+ `);
3989
+ if (hint)
3990
+ OUT.write(`${INDENT} ${dim(hint)}
3991
+ `);
3992
+ },
3993
+ err(msg, hint) {
3994
+ ERR.write(`${INDENT}${red(icons.cross)} ${msg}
3995
+ `);
3996
+ if (hint)
3997
+ ERR.write(`${INDENT} ${dim(hint)}
3998
+ `);
3999
+ },
4000
+ info(msg) {
4001
+ OUT.write(`${INDENT}${msg}
4002
+ `);
4003
+ },
4004
+ section(title) {
4005
+ OUT.write(`
4006
+ ${INDENT}${dim("—")} ${clay(title)}
4007
+
4008
+ `);
4009
+ },
4010
+ heading(title) {
4011
+ OUT.write(`${INDENT}${bold(title)}
4012
+ `);
4013
+ },
4014
+ kv(pairs, opts) {
4015
+ const pad = opts?.padTo ?? Math.max(...pairs.map(([k]) => k.length)) + 2;
4016
+ for (const [k, v] of pairs) {
4017
+ OUT.write(`${INDENT}${dim(k.padEnd(pad, " "))}${v}
4018
+ `);
4019
+ }
4020
+ },
4021
+ code(snippet) {
4022
+ for (const line of snippet.split(`
4023
+ `)) {
4024
+ OUT.write(`${INDENT} ${cyan(line)}
4025
+ `);
4026
+ }
4027
+ },
4028
+ link(url) {
4029
+ OUT.write(`${INDENT}${clay(url)}
4030
+ `);
4031
+ },
4032
+ hint(msg) {
4033
+ OUT.write(`${INDENT}${dim(icons.arrow + " " + msg)}
4034
+ `);
4035
+ }
4036
+ };
3964
4037
  });
3965
4038
 
3966
4039
  // src/commands/welcome.ts
@@ -3970,35 +4043,53 @@ __export(exports_welcome, {
3970
4043
  _stub: () => runWelcome
3971
4044
  });
3972
4045
  import { createInterface as createInterface5 } from "node:readline";
3973
- async function runWelcome() {
3974
- const config = readConfig();
3975
- if (config.meshes.length > 0)
3976
- return EXIT.SUCCESS;
3977
- renderWelcome();
4046
+ function prompt2(q) {
3978
4047
  const rl = createInterface5({ input: process.stdin, output: process.stdout });
3979
4048
  return new Promise((resolve) => {
3980
- rl.question(" Choice [1]: ", async (answer) => {
4049
+ rl.question(q, (a) => {
3981
4050
  rl.close();
3982
- const choice = (answer || "1").trim();
3983
- if (choice === "1")
3984
- resolve(await register2());
3985
- else if (choice === "2")
3986
- resolve(await login());
3987
- else if (choice === "3") {
3988
- console.log(`
3989
- Run: claudemesh join <invite-url>
3990
- `);
3991
- resolve(EXIT.SUCCESS);
3992
- } else
3993
- resolve(EXIT.USER_CANCELLED);
4051
+ resolve(a.trim());
3994
4052
  });
3995
4053
  });
3996
4054
  }
4055
+ async function runWelcome() {
4056
+ const config = readConfig();
4057
+ if (config.meshes.length > 0)
4058
+ return EXIT.SUCCESS;
4059
+ renderWelcome();
4060
+ render.info("Do you already have an invite link? (y/n) [n]");
4061
+ const hasInvite = (await prompt2(" > ")).toLowerCase().startsWith("y");
4062
+ if (hasInvite) {
4063
+ render.blank();
4064
+ render.info("Paste your invite link (claudemesh.com/i/... or claudemesh://...)");
4065
+ const raw = await prompt2(" > ");
4066
+ if (!raw || !isInviteUrl(raw)) {
4067
+ render.err("That doesn't look like a claudemesh invite URL.");
4068
+ render.hint("Check your email — the link starts with https://claudemesh.com/i/");
4069
+ return EXIT.INVALID_ARGS;
4070
+ }
4071
+ const normalised = normaliseInviteUrl(raw);
4072
+ render.blank();
4073
+ render.ok(`Joining via ${normalised}`);
4074
+ const { runLaunch: runLaunch2 } = await Promise.resolve().then(() => (init_launch(), exports_launch));
4075
+ await runLaunch2({
4076
+ join: normalised,
4077
+ name: process.env.USER ?? process.env.USERNAME ?? undefined,
4078
+ yes: false
4079
+ }, []);
4080
+ return EXIT.SUCCESS;
4081
+ }
4082
+ render.blank();
4083
+ render.info("Opening claudemesh.com so you can sign in and create your first mesh.");
4084
+ render.hint("After sign-in, paste the sync token back here when prompted.");
4085
+ render.blank();
4086
+ return await login();
4087
+ }
3997
4088
  var init_welcome2 = __esm(() => {
3998
4089
  init_facade();
3999
4090
  init_welcome();
4000
4091
  init_login();
4001
- init_register();
4092
+ init_render();
4002
4093
  init_exit_codes();
4003
4094
  });
4004
4095
 
@@ -4656,7 +4747,7 @@ __export(exports_delete_mesh, {
4656
4747
  deleteMesh: () => deleteMesh
4657
4748
  });
4658
4749
  import { createInterface as createInterface6 } from "node:readline";
4659
- function prompt2(question) {
4750
+ function prompt3(question) {
4660
4751
  const rl = createInterface6({ input: process.stdin, output: process.stdout });
4661
4752
  return new Promise((resolve) => {
4662
4753
  rl.question(question, (a) => {
@@ -4698,7 +4789,7 @@ async function deleteMesh(slug, opts = {}) {
4698
4789
  console.log(` ${bold(String(i + 1) + ")")} ${m.slug} ${dim("(" + m.name + ")")}`);
4699
4790
  });
4700
4791
  console.log("");
4701
- const choice = await prompt2(" Choice: ");
4792
+ const choice = await prompt3(" Choice: ");
4702
4793
  const idx = parseInt(choice, 10) - 1;
4703
4794
  if (idx < 0 || idx >= config.meshes.length) {
4704
4795
  console.log(" Cancelled.");
@@ -4718,7 +4809,7 @@ async function deleteMesh(slug, opts = {}) {
4718
4809
  console.log(` ${bold("2)")} ${red("Delete everywhere")} ${dim("(removes for all members)")}`);
4719
4810
  console.log(` ${bold("3)")} Cancel`);
4720
4811
  console.log("");
4721
- const choice = await prompt2(" Choice [1]: ") || "1";
4812
+ const choice = await prompt3(" Choice [1]: ") || "1";
4722
4813
  if (choice === "3") {
4723
4814
  console.log(" Cancelled.");
4724
4815
  return EXIT.USER_CANCELLED;
@@ -4726,7 +4817,7 @@ async function deleteMesh(slug, opts = {}) {
4726
4817
  if (choice === "2") {
4727
4818
  console.log(`
4728
4819
  ${red("Warning:")} This will delete ${bold(slug)} for all members.`);
4729
- const confirm = await prompt2(` Type "${slug}" to confirm: `);
4820
+ const confirm = await prompt3(` Type "${slug}" to confirm: `);
4730
4821
  if (confirm.toLowerCase() !== slug.toLowerCase()) {
4731
4822
  console.log(" Cancelled.");
4732
4823
  return EXIT.USER_CANCELLED;
@@ -4755,7 +4846,7 @@ async function deleteMesh(slug, opts = {}) {
4755
4846
  ${yellow(icons.warn)} Only the mesh owner can delete it from the server.`));
4756
4847
  }
4757
4848
  console.log("");
4758
- const choice = await prompt2(" Choice [1]: ") || "1";
4849
+ const choice = await prompt3(" Choice [1]: ") || "1";
4759
4850
  if (choice === "2") {
4760
4851
  console.log(" Cancelled.");
4761
4852
  return EXIT.USER_CANCELLED;
@@ -5888,7 +5979,7 @@ __export(exports_invite, {
5888
5979
  invite: () => invite
5889
5980
  });
5890
5981
  import { createInterface as createInterface7 } from "node:readline";
5891
- function prompt3(question) {
5982
+ function prompt4(question) {
5892
5983
  const rl = createInterface7({ input: process.stdin, output: process.stdout });
5893
5984
  return new Promise((resolve) => {
5894
5985
  rl.question(question, (a) => {
@@ -5920,7 +6011,7 @@ async function invite(email, opts = {}) {
5920
6011
  console.log(` ${bold(String(i + 1) + ")")} ${m.slug} ${dim("(" + m.name + ")")}`);
5921
6012
  });
5922
6013
  console.log("");
5923
- const choice = await prompt3(" Choice [1]: ") || "1";
6014
+ const choice = await prompt4(" Choice [1]: ") || "1";
5924
6015
  const idx = parseInt(choice, 10) - 1;
5925
6016
  meshSlug = config.meshes[idx >= 0 && idx < config.meshes.length ? idx : 0].slug;
5926
6017
  }
@@ -6585,6 +6676,18 @@ var init_profile = __esm(() => {
6585
6676
  init_facade();
6586
6677
  });
6587
6678
 
6679
+ // src/commands/register.ts
6680
+ var exports_register = {};
6681
+ __export(exports_register, {
6682
+ register: () => register2
6683
+ });
6684
+ async function register2() {
6685
+ return login();
6686
+ }
6687
+ var init_register = __esm(() => {
6688
+ init_login();
6689
+ });
6690
+
6588
6691
  // src/commands/logout.ts
6589
6692
  var exports_logout = {};
6590
6693
  __export(exports_logout, {
@@ -8294,9 +8397,9 @@ __export(exports_backup, {
8294
8397
  });
8295
8398
  import { readFileSync as readFileSync11, writeFileSync as writeFileSync8, existsSync as existsSync15 } from "node:fs";
8296
8399
  import { createInterface as createInterface10 } from "node:readline";
8297
- function readHidden(prompt4) {
8400
+ function readHidden(prompt5) {
8298
8401
  return new Promise((resolve2) => {
8299
- process.stdout.write(prompt4);
8402
+ process.stdout.write(prompt5);
8300
8403
  const rl = createInterface10({ input: process.stdin, output: process.stdout, terminal: true });
8301
8404
  const stdin = process.stdin;
8302
8405
  const wasRaw = Boolean(stdin.isRaw);
@@ -8410,6 +8513,261 @@ var init_backup = __esm(() => {
8410
8513
  MAGIC = Buffer.from("CMB1", "utf-8");
8411
8514
  });
8412
8515
 
8516
+ // src/commands/upgrade.ts
8517
+ var exports_upgrade = {};
8518
+ __export(exports_upgrade, {
8519
+ runUpgrade: () => runUpgrade
8520
+ });
8521
+ import { spawnSync as spawnSync6 } from "node:child_process";
8522
+ import { existsSync as existsSync16 } from "node:fs";
8523
+ import { dirname as dirname3, join as join8, resolve as resolve2 } from "node:path";
8524
+ async function latestAlpha() {
8525
+ try {
8526
+ const res = await fetch(URLS.NPM_REGISTRY, { signal: AbortSignal.timeout(8000) });
8527
+ if (!res.ok)
8528
+ return null;
8529
+ const body = await res.json();
8530
+ return body["dist-tags"]?.alpha ?? body["dist-tags"]?.latest ?? null;
8531
+ } catch {
8532
+ return null;
8533
+ }
8534
+ }
8535
+ function findNpm() {
8536
+ const portable = join8(process.env.HOME ?? "", ".claudemesh", "node", "bin", "npm");
8537
+ if (existsSync16(portable)) {
8538
+ return { npm: portable, prefix: join8(process.env.HOME ?? "", ".claudemesh") };
8539
+ }
8540
+ let cur = resolve2(process.argv[1] ?? ".");
8541
+ for (let i = 0;i < 6; i++) {
8542
+ cur = dirname3(cur);
8543
+ const candidate = join8(cur, "bin", "npm");
8544
+ if (existsSync16(candidate))
8545
+ return { npm: candidate };
8546
+ }
8547
+ return { npm: "npm" };
8548
+ }
8549
+ async function runUpgrade(opts = {}) {
8550
+ render.section("claudemesh upgrade");
8551
+ render.kv([
8552
+ ["installed", VERSION],
8553
+ ["checking", "npm registry…"]
8554
+ ]);
8555
+ const latest = await latestAlpha();
8556
+ if (!latest) {
8557
+ render.warn("Could not reach npm registry — skipped.");
8558
+ return EXIT.SUCCESS;
8559
+ }
8560
+ render.kv([["latest", latest]]);
8561
+ if (latest === VERSION) {
8562
+ render.blank();
8563
+ render.ok(`Already on latest (${latest}).`);
8564
+ return EXIT.SUCCESS;
8565
+ }
8566
+ if (opts.check) {
8567
+ render.blank();
8568
+ render.warn(`Update available: ${VERSION} → ${latest}`);
8569
+ render.hint("Run: claudemesh upgrade");
8570
+ return EXIT.SUCCESS;
8571
+ }
8572
+ const { npm, prefix } = findNpm();
8573
+ const args = ["install", "-g"];
8574
+ if (prefix)
8575
+ args.push("--prefix", prefix);
8576
+ args.push("claudemesh-cli@alpha");
8577
+ render.blank();
8578
+ render.info(`Updating ${VERSION} → ${latest}…`);
8579
+ render.hint(`${npm} ${args.join(" ")}`);
8580
+ render.blank();
8581
+ const res = spawnSync6(npm, args, { stdio: "inherit" });
8582
+ if (res.status !== 0) {
8583
+ render.err(`npm exited with status ${res.status}`);
8584
+ render.hint("Try: npm i -g claudemesh-cli@alpha");
8585
+ return EXIT.INTERNAL_ERROR;
8586
+ }
8587
+ render.blank();
8588
+ render.ok(`Upgraded to ${latest}.`);
8589
+ return EXIT.SUCCESS;
8590
+ }
8591
+ var init_upgrade = __esm(() => {
8592
+ init_urls();
8593
+ init_render();
8594
+ init_exit_codes();
8595
+ });
8596
+
8597
+ // src/commands/grants.ts
8598
+ var exports_grants = {};
8599
+ __export(exports_grants, {
8600
+ runRevoke: () => runRevoke,
8601
+ runGrants: () => runGrants,
8602
+ runGrant: () => runGrant,
8603
+ runBlock: () => runBlock,
8604
+ isAllowed: () => isAllowed
8605
+ });
8606
+ import { existsSync as existsSync17, mkdirSync as mkdirSync5, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "node:fs";
8607
+ import { homedir as homedir8 } from "node:os";
8608
+ import { join as join9 } from "node:path";
8609
+ function readGrants() {
8610
+ if (!existsSync17(GRANT_FILE))
8611
+ return {};
8612
+ try {
8613
+ return JSON.parse(readFileSync12(GRANT_FILE, "utf-8"));
8614
+ } catch {
8615
+ return {};
8616
+ }
8617
+ }
8618
+ function writeGrants(g) {
8619
+ const dir = join9(homedir8(), ".claudemesh");
8620
+ if (!existsSync17(dir))
8621
+ mkdirSync5(dir, { recursive: true });
8622
+ writeFileSync9(GRANT_FILE, JSON.stringify(g, null, 2), { mode: 384 });
8623
+ }
8624
+ function resolveCaps(input) {
8625
+ if (input.includes("all"))
8626
+ return [...ALL_CAPS];
8627
+ return input.filter((c) => ALL_CAPS.includes(c));
8628
+ }
8629
+ async function resolvePeer(meshSlug, name) {
8630
+ return await withMesh({ meshSlug }, async (client) => {
8631
+ const peers = await client.listPeers();
8632
+ const match = peers.find((p) => p.displayName === name || p.pubkey === name || p.pubkey.startsWith(name));
8633
+ return match ? { displayName: match.displayName, pubkey: match.pubkey } : null;
8634
+ });
8635
+ }
8636
+ function pickMesh2(slug) {
8637
+ const cfg = readConfig();
8638
+ if (slug)
8639
+ return cfg.meshes.find((m) => m.slug === slug) ? slug : null;
8640
+ return cfg.meshes[0]?.slug ?? null;
8641
+ }
8642
+ async function runGrant(peer, caps, opts = {}) {
8643
+ if (!peer || caps.length === 0) {
8644
+ render.err("Usage: claudemesh grant <peer> <capability...>");
8645
+ render.hint(`Capabilities: ${ALL_CAPS.join(", ")}, all`);
8646
+ return EXIT.INVALID_ARGS;
8647
+ }
8648
+ const mesh = pickMesh2(opts.mesh);
8649
+ if (!mesh) {
8650
+ render.err("No matching mesh — join one first.");
8651
+ return EXIT.NOT_FOUND;
8652
+ }
8653
+ const resolved = await resolvePeer(mesh, peer);
8654
+ if (!resolved) {
8655
+ render.err(`Peer "${peer}" not found on ${mesh}.`);
8656
+ return EXIT.NOT_FOUND;
8657
+ }
8658
+ const wanted = resolveCaps(caps);
8659
+ if (wanted.length === 0) {
8660
+ render.err(`Unknown capabilities: ${caps.join(", ")}`);
8661
+ return EXIT.INVALID_ARGS;
8662
+ }
8663
+ const store = readGrants();
8664
+ const meshGrants = store[mesh] ?? {};
8665
+ const existing = meshGrants[resolved.pubkey] ?? DEFAULT_CAPS.slice();
8666
+ const merged = Array.from(new Set([...existing, ...wanted]));
8667
+ meshGrants[resolved.pubkey] = merged;
8668
+ store[mesh] = meshGrants;
8669
+ writeGrants(store);
8670
+ render.ok(`Granted ${wanted.join(", ")} to ${resolved.displayName} on ${mesh}.`);
8671
+ render.kv([["now", merged.join(", ")]]);
8672
+ return EXIT.SUCCESS;
8673
+ }
8674
+ async function runRevoke(peer, caps, opts = {}) {
8675
+ if (!peer || caps.length === 0) {
8676
+ render.err("Usage: claudemesh revoke <peer> <capability...>");
8677
+ return EXIT.INVALID_ARGS;
8678
+ }
8679
+ const mesh = pickMesh2(opts.mesh);
8680
+ if (!mesh) {
8681
+ render.err("No matching mesh.");
8682
+ return EXIT.NOT_FOUND;
8683
+ }
8684
+ const resolved = await resolvePeer(mesh, peer);
8685
+ if (!resolved) {
8686
+ render.err(`Peer "${peer}" not found on ${mesh}.`);
8687
+ return EXIT.NOT_FOUND;
8688
+ }
8689
+ const wanted = caps.includes("all") ? ALL_CAPS.slice() : resolveCaps(caps);
8690
+ const store = readGrants();
8691
+ const meshGrants = store[mesh] ?? {};
8692
+ const existing = meshGrants[resolved.pubkey] ?? DEFAULT_CAPS.slice();
8693
+ const after = existing.filter((c) => !wanted.includes(c));
8694
+ meshGrants[resolved.pubkey] = after;
8695
+ store[mesh] = meshGrants;
8696
+ writeGrants(store);
8697
+ render.ok(`Revoked ${wanted.join(", ")} from ${resolved.displayName} on ${mesh}.`);
8698
+ render.kv([["now", after.length ? after.join(", ") : "(none)"]]);
8699
+ return EXIT.SUCCESS;
8700
+ }
8701
+ async function runBlock(peer, opts = {}) {
8702
+ if (!peer) {
8703
+ render.err("Usage: claudemesh block <peer>");
8704
+ return EXIT.INVALID_ARGS;
8705
+ }
8706
+ const mesh = pickMesh2(opts.mesh);
8707
+ if (!mesh) {
8708
+ render.err("No matching mesh.");
8709
+ return EXIT.NOT_FOUND;
8710
+ }
8711
+ const resolved = await resolvePeer(mesh, peer);
8712
+ if (!resolved) {
8713
+ render.err(`Peer "${peer}" not found on ${mesh}.`);
8714
+ return EXIT.NOT_FOUND;
8715
+ }
8716
+ const store = readGrants();
8717
+ const meshGrants = store[mesh] ?? {};
8718
+ meshGrants[resolved.pubkey] = [];
8719
+ store[mesh] = meshGrants;
8720
+ writeGrants(store);
8721
+ render.ok(`Blocked ${resolved.displayName} on ${mesh} (all capabilities revoked).`);
8722
+ render.hint(`Undo with: claudemesh grant ${resolved.displayName} all --mesh ${mesh}`);
8723
+ return EXIT.SUCCESS;
8724
+ }
8725
+ async function runGrants(opts = {}) {
8726
+ const mesh = pickMesh2(opts.mesh);
8727
+ if (!mesh) {
8728
+ render.err("No matching mesh.");
8729
+ return EXIT.NOT_FOUND;
8730
+ }
8731
+ const store = readGrants();
8732
+ const meshGrants = store[mesh] ?? {};
8733
+ if (opts.json) {
8734
+ console.log(JSON.stringify({ schema_version: "1.0", mesh, grants: meshGrants }, null, 2));
8735
+ return EXIT.SUCCESS;
8736
+ }
8737
+ render.section(`grants on ${mesh}`);
8738
+ const peerPubkeys = Object.keys(meshGrants);
8739
+ if (peerPubkeys.length === 0) {
8740
+ render.info("(no overrides — all peers use default caps: " + DEFAULT_CAPS.join(", ") + ")");
8741
+ return EXIT.SUCCESS;
8742
+ }
8743
+ await withMesh({ meshSlug: mesh }, async (client) => {
8744
+ const peers = await client.listPeers();
8745
+ const byPk = new Map(peers.map((p) => [p.pubkey, p.displayName]));
8746
+ for (const [pk, caps] of Object.entries(meshGrants)) {
8747
+ const name = byPk.get(pk) ?? `${pk.slice(0, 10)}…`;
8748
+ render.kv([[name, caps.length ? caps.join(", ") : "(blocked)"]]);
8749
+ }
8750
+ });
8751
+ return EXIT.SUCCESS;
8752
+ }
8753
+ function isAllowed(meshSlug, peerPubkey, cap) {
8754
+ const store = readGrants();
8755
+ const entry = store[meshSlug]?.[peerPubkey];
8756
+ if (entry === undefined)
8757
+ return DEFAULT_CAPS.includes(cap);
8758
+ return entry.includes(cap);
8759
+ }
8760
+ var ALL_CAPS, DEFAULT_CAPS, GRANT_FILE;
8761
+ var init_grants = __esm(() => {
8762
+ init_facade();
8763
+ init_connect();
8764
+ init_render();
8765
+ init_exit_codes();
8766
+ ALL_CAPS = ["read", "dm", "broadcast", "state-read", "state-write", "file-read"];
8767
+ DEFAULT_CAPS = ["read", "dm", "broadcast", "state-read"];
8768
+ GRANT_FILE = join9(homedir8(), ".claudemesh", "grants.json");
8769
+ });
8770
+
8413
8771
  // src/mcp/tools/definitions.ts
8414
8772
  var TOOLS;
8415
8773
  var init_definitions = __esm(() => {
@@ -9796,13 +10154,13 @@ ${peerLines.join(`
9796
10154
  }
9797
10155
  }
9798
10156
  try {
9799
- const { writeFileSync: writeFileSync9, mkdirSync: mkdirSync5, existsSync: existsSync16 } = await import("node:fs");
10157
+ const { writeFileSync: writeFileSync10, mkdirSync: mkdirSync6, existsSync: existsSync18 } = await import("node:fs");
9800
10158
  const { join: joinPath } = await import("node:path");
9801
- const { homedir: homedir8 } = await import("node:os");
9802
- const dir = joinPath(homedir8(), ".claudemesh");
9803
- if (!existsSync16(dir))
9804
- mkdirSync5(dir, { recursive: true });
9805
- writeFileSync9(joinPath(dir, "peer-cache.json"), JSON.stringify(statusCache));
10159
+ const { homedir: homedir9 } = await import("node:os");
10160
+ const dir = joinPath(homedir9(), ".claudemesh");
10161
+ if (!existsSync18(dir))
10162
+ mkdirSync6(dir, { recursive: true });
10163
+ writeFileSync10(joinPath(dir, "peer-cache.json"), JSON.stringify(statusCache));
9806
10164
  } catch {}
9807
10165
  return text(sections.join(`
9808
10166
 
@@ -10044,23 +10402,23 @@ ${lines.join(`
10044
10402
  const { path: filePath, name: fileName, tags, to: fileTo } = args ?? {};
10045
10403
  if (!filePath)
10046
10404
  return text("share_file: `path` required", true);
10047
- const { existsSync: existsSync16 } = await import("node:fs");
10048
- if (!existsSync16(filePath))
10405
+ const { existsSync: existsSync18 } = await import("node:fs");
10406
+ if (!existsSync18(filePath))
10049
10407
  return text(`share_file: file not found: ${filePath}`, true);
10050
10408
  const client = allClients()[0];
10051
10409
  if (!client)
10052
10410
  return text("share_file: not connected", true);
10053
10411
  if (fileTo) {
10054
10412
  const { encryptFile: encryptFile2, sealKeyForPeer: sealKeyForPeer2 } = await Promise.resolve().then(() => (init_file_crypto(), exports_file_crypto));
10055
- const { readFileSync: readFileSync12, writeFileSync: writeFileSync9, mkdtempSync: mkdtempSync2, unlinkSync: unlinkSync2, rmdirSync } = await import("node:fs");
10413
+ const { readFileSync: readFileSync13, writeFileSync: writeFileSync10, mkdtempSync: mkdtempSync2, unlinkSync: unlinkSync2, rmdirSync } = await import("node:fs");
10056
10414
  const { tmpdir: tmpdir2 } = await import("node:os");
10057
- const { join: join8, basename } = await import("node:path");
10415
+ const { join: join10, basename } = await import("node:path");
10058
10416
  const peers = await client.listPeers();
10059
10417
  const targetPeer = peers.find((p) => p.pubkey === fileTo || p.displayName === fileTo);
10060
10418
  if (!targetPeer) {
10061
10419
  return text(`share_file: peer not found: ${fileTo}`, true);
10062
10420
  }
10063
- const plaintext = readFileSync12(filePath);
10421
+ const plaintext = readFileSync13(filePath);
10064
10422
  const { ciphertext, nonce, key } = await encryptFile2(new Uint8Array(plaintext));
10065
10423
  const sealedForTarget = await sealKeyForPeer2(key, targetPeer.pubkey);
10066
10424
  const myPubkey = client.getSessionPubkey();
@@ -10077,9 +10435,9 @@ ${lines.join(`
10077
10435
  combined.set(ciphertext, nonceBytes.length);
10078
10436
  const rawName = fileName ?? basename(filePath);
10079
10437
  const baseName = basename(rawName).replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 255);
10080
- const tmpDir = mkdtempSync2(join8(tmpdir2(), "cm-"));
10081
- const tmpPath = join8(tmpDir, baseName);
10082
- writeFileSync9(tmpPath, combined);
10438
+ const tmpDir = mkdtempSync2(join10(tmpdir2(), "cm-"));
10439
+ const tmpPath = join10(tmpDir, baseName);
10440
+ writeFileSync10(tmpPath, combined);
10083
10441
  try {
10084
10442
  const fileId = await client.uploadFile(tmpPath, client.meshId, client.meshSlug, {
10085
10443
  name: baseName,
@@ -10154,10 +10512,10 @@ ${lines.join(`
10154
10512
  const plaintext = await decryptFile2(ciphertext, nonce, kf);
10155
10513
  if (!plaintext)
10156
10514
  return text(genericErr, true);
10157
- const { writeFileSync: writeFileSync10, mkdirSync: mkdirSync6 } = await import("node:fs");
10158
- const { dirname: dirname4 } = await import("node:path");
10159
- mkdirSync6(dirname4(save_to), { recursive: true });
10160
- writeFileSync10(save_to, plaintext);
10515
+ const { writeFileSync: writeFileSync11, mkdirSync: mkdirSync7 } = await import("node:fs");
10516
+ const { dirname: dirname5 } = await import("node:path");
10517
+ mkdirSync7(dirname5(save_to), { recursive: true });
10518
+ writeFileSync11(save_to, plaintext);
10161
10519
  return text(`Downloaded and decrypted: ${result.name} → ${save_to}`);
10162
10520
  }
10163
10521
  let res = await fetch(result.url, { signal: AbortSignal.timeout(1e4) }).catch(() => null);
@@ -10167,10 +10525,10 @@ ${lines.join(`
10167
10525
  }
10168
10526
  if (!res.ok)
10169
10527
  return text(`get_file: download failed (${res.status})`, true);
10170
- const { writeFileSync: writeFileSync9, mkdirSync: mkdirSync5 } = await import("node:fs");
10171
- const { dirname: dirname3 } = await import("node:path");
10172
- mkdirSync5(dirname3(save_to), { recursive: true });
10173
- writeFileSync9(save_to, Buffer.from(await res.arrayBuffer()));
10528
+ const { writeFileSync: writeFileSync10, mkdirSync: mkdirSync6 } = await import("node:fs");
10529
+ const { dirname: dirname4 } = await import("node:path");
10530
+ mkdirSync6(dirname4(save_to), { recursive: true });
10531
+ writeFileSync10(save_to, Buffer.from(await res.arrayBuffer()));
10174
10532
  return text(`Downloaded: ${result.name} → ${save_to}`);
10175
10533
  }
10176
10534
  case "list_files": {
@@ -10928,10 +11286,10 @@ ${lines.join(`
10928
11286
  const entryType = vType ?? "env";
10929
11287
  let plaintextBytes;
10930
11288
  if (entryType === "file") {
10931
- const { existsSync: existsSync16, readFileSync: readFileSync12 } = await import("node:fs");
10932
- if (!existsSync16(value))
11289
+ const { existsSync: existsSync18, readFileSync: readFileSync13 } = await import("node:fs");
11290
+ if (!existsSync18(value))
10933
11291
  return text(`vault_set: file not found: ${value}`, true);
10934
- plaintextBytes = new Uint8Array(readFileSync12(value));
11292
+ plaintextBytes = new Uint8Array(readFileSync13(value));
10935
11293
  } else {
10936
11294
  plaintextBytes = new TextEncoder().encode(value);
10937
11295
  }
@@ -11259,6 +11617,17 @@ ${lines.join(`
11259
11617
  }
11260
11618
  const fromPubkey = msg.senderPubkey || "";
11261
11619
  const fromName = fromPubkey ? await resolvePeerName(client, fromPubkey) : "unknown";
11620
+ if (fromPubkey) {
11621
+ try {
11622
+ const { isAllowed: isAllowed2 } = await Promise.resolve().then(() => (init_grants(), exports_grants));
11623
+ const kindCap = msg.kind === "broadcast" ? "broadcast" : "dm";
11624
+ if (!isAllowed2(client.meshSlug, fromPubkey, kindCap)) {
11625
+ process.stderr.write(`[claudemesh] dropped ${kindCap} from ${fromName} (not granted)
11626
+ `);
11627
+ return;
11628
+ }
11629
+ } catch {}
11630
+ }
11262
11631
  if (messageMode === "inbox") {
11263
11632
  try {
11264
11633
  await server.notification({
@@ -11734,22 +12103,6 @@ function renderVersion() {
11734
12103
  return " " + boldOrange("claudemesh") + " v" + VERSION;
11735
12104
  }
11736
12105
 
11737
- // src/utils/url.ts
11738
- function isInviteUrl(input) {
11739
- return /^https?:\/\/[^/]+\/(?:[a-z]{2}\/)?i\//.test(input) || /^https?:\/\/[^/]+\/(?:[a-z]{2}\/)?join\//.test(input) || /^ic:\/\//.test(input) || /^claudemesh:\/\//.test(input);
11740
- }
11741
- function normaliseInviteUrl(input, host = "claudemesh.com") {
11742
- const trimmed = input.trim();
11743
- if (trimmed.startsWith("claudemesh://")) {
11744
- const rest = trimmed.slice("claudemesh://".length).replace(/^\/+/, "");
11745
- const m = rest.match(/^(?:i|join)\/(.+)$/);
11746
- const tail = m ? m[1] : rest;
11747
- const kind = rest.startsWith("join/") ? "join" : "i";
11748
- return `https://${host}/${kind}/${tail}`;
11749
- }
11750
- return trimmed;
11751
- }
11752
-
11753
12106
  // src/entrypoints/cli.ts
11754
12107
  installSignalHandlers();
11755
12108
  installErrorHandlers();
@@ -11791,6 +12144,10 @@ Auth
11791
12144
 
11792
12145
  Security
11793
12146
  claudemesh verify [peer] show ed25519 safety numbers (SAS)
12147
+ claudemesh grant <peer> <cap> grant capability (dm, broadcast, state-read, all)
12148
+ claudemesh revoke <peer> <cap> revoke capability (or 'all')
12149
+ claudemesh block <peer> revoke all capabilities (silent drop)
12150
+ claudemesh grants list per-peer overrides for current mesh
11794
12151
  claudemesh backup [file] encrypt config → portable recovery file
11795
12152
  claudemesh restore <file> restore config from a backup file
11796
12153
 
@@ -11802,6 +12159,7 @@ Setup
11802
12159
  claudemesh sync refresh mesh list from dashboard
11803
12160
  claudemesh completions <shell> emit bash / zsh / fish completion script
11804
12161
  claudemesh url-handler install register claudemesh:// click-to-launch
12162
+ claudemesh upgrade self-update to latest alpha (rustup-style)
11805
12163
 
11806
12164
  Flags
11807
12165
  --version, -V show version
@@ -12038,6 +12396,32 @@ async function main() {
12038
12396
  process.exit(await runRestore2(positionals[0]));
12039
12397
  break;
12040
12398
  }
12399
+ case "upgrade":
12400
+ case "update": {
12401
+ const { runUpgrade: runUpgrade2 } = await Promise.resolve().then(() => (init_upgrade(), exports_upgrade));
12402
+ process.exit(await runUpgrade2({ check: !!flags.check, yes: !!flags.y || !!flags.yes }));
12403
+ break;
12404
+ }
12405
+ case "grant": {
12406
+ const { runGrant: runGrant2 } = await Promise.resolve().then(() => (init_grants(), exports_grants));
12407
+ process.exit(await runGrant2(positionals[0], positionals.slice(1), { mesh: flags.mesh }));
12408
+ break;
12409
+ }
12410
+ case "revoke": {
12411
+ const { runRevoke: runRevoke2 } = await Promise.resolve().then(() => (init_grants(), exports_grants));
12412
+ process.exit(await runRevoke2(positionals[0], positionals.slice(1), { mesh: flags.mesh }));
12413
+ break;
12414
+ }
12415
+ case "block": {
12416
+ const { runBlock: runBlock2 } = await Promise.resolve().then(() => (init_grants(), exports_grants));
12417
+ process.exit(await runBlock2(positionals[0], { mesh: flags.mesh }));
12418
+ break;
12419
+ }
12420
+ case "grants": {
12421
+ const { runGrants: runGrants2 } = await Promise.resolve().then(() => (init_grants(), exports_grants));
12422
+ process.exit(await runGrants2({ mesh: flags.mesh, json: !!flags.json }));
12423
+ break;
12424
+ }
12041
12425
  case "mcp": {
12042
12426
  const { runMcp: runMcp2 } = await Promise.resolve().then(() => (init_mcp(), exports_mcp));
12043
12427
  await runMcp2();
@@ -12065,4 +12449,4 @@ main().catch((err) => {
12065
12449
  process.exit(EXIT.INTERNAL_ERROR);
12066
12450
  });
12067
12451
 
12068
- //# debugId=35283B912CD01E3464756E2164756E21
12452
+ //# debugId=9DC7B5B7388FF04464756E2164756E21