claudemesh-cli 0.10.3 → 0.10.5

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.
Files changed (2) hide show
  1. package/dist/index.js +234 -70
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -40189,7 +40189,7 @@ var package_default;
40189
40189
  var init_package = __esm(() => {
40190
40190
  package_default = {
40191
40191
  name: "claudemesh-cli",
40192
- version: "0.10.3",
40192
+ version: "0.10.5",
40193
40193
  description: "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.",
40194
40194
  keywords: [
40195
40195
  "claude-code",
@@ -53720,18 +53720,170 @@ async function enrollWithBroker2(args) {
53720
53720
  // src/commands/join.ts
53721
53721
  init_keypair();
53722
53722
  init_config();
53723
+
53724
+ // src/lib/invite-v2.ts
53725
+ var import_libsodium_wrappers2 = __toESM(require_libsodium_wrappers(), 1);
53726
+ async function ensureSodium2() {
53727
+ await import_libsodium_wrappers2.default.ready;
53728
+ return import_libsodium_wrappers2.default;
53729
+ }
53730
+ async function generateX25519Keypair() {
53731
+ const s = await ensureSodium2();
53732
+ const kp = s.crypto_box_keypair();
53733
+ const publicKeyB64 = s.to_base64(kp.publicKey, s.base64_variants.URLSAFE_NO_PADDING);
53734
+ return { publicKeyB64, secretKey: kp.privateKey };
53735
+ }
53736
+ async function claimInviteV2(opts) {
53737
+ const s = await ensureSodium2();
53738
+ const { publicKeyB64, secretKey } = await generateX25519Keypair();
53739
+ const publicKeyBytes = s.from_base64(publicKeyB64, s.base64_variants.URLSAFE_NO_PADDING);
53740
+ const base = opts.appBaseUrl.replace(/\/$/, "");
53741
+ const code = encodeURIComponent(opts.code);
53742
+ const url = `${base}/api/public/invites/${code}/claim`;
53743
+ let res;
53744
+ try {
53745
+ res = await fetch(url, {
53746
+ method: "POST",
53747
+ headers: {
53748
+ "Content-Type": "application/json",
53749
+ Accept: "application/json"
53750
+ },
53751
+ body: JSON.stringify({ recipient_x25519_pubkey: publicKeyB64 }),
53752
+ signal: AbortSignal.timeout(15000)
53753
+ });
53754
+ } catch (e) {
53755
+ throw new Error(`claim request failed (network): ${e instanceof Error ? e.message : String(e)}`);
53756
+ }
53757
+ let parsed = null;
53758
+ try {
53759
+ parsed = await res.json();
53760
+ } catch {}
53761
+ if (!res.ok) {
53762
+ const err = parsed ?? {};
53763
+ const reason = err.error ?? err.code ?? err.message ?? `HTTP ${res.status}`;
53764
+ switch (res.status) {
53765
+ case 400:
53766
+ throw new Error(`invite claim rejected: ${reason}`);
53767
+ case 404:
53768
+ throw new Error(`invite not found: ${reason}`);
53769
+ case 410:
53770
+ throw new Error(`invite no longer usable: ${reason}`);
53771
+ default:
53772
+ throw new Error(`invite claim failed (${res.status}): ${reason}`);
53773
+ }
53774
+ }
53775
+ const body = parsed ?? {};
53776
+ if (!body.sealed_root_key || !body.mesh_id || !body.member_id || !body.owner_pubkey || !body.canonical_v2) {
53777
+ throw new Error(`invite claim response malformed: missing required field(s)`);
53778
+ }
53779
+ let rootKey;
53780
+ try {
53781
+ const sealed = s.from_base64(body.sealed_root_key, s.base64_variants.URLSAFE_NO_PADDING);
53782
+ const opened = s.crypto_box_seal_open(sealed, publicKeyBytes, secretKey);
53783
+ if (!opened)
53784
+ throw new Error("crypto_box_seal_open returned empty");
53785
+ rootKey = opened;
53786
+ } catch (e) {
53787
+ throw new Error(`failed to unseal root key (server sealed to wrong pubkey?): ${e instanceof Error ? e.message : String(e)}`);
53788
+ }
53789
+ if (rootKey.length !== 32) {
53790
+ throw new Error(`unsealed root key has wrong length: ${rootKey.length} (expected 32)`);
53791
+ }
53792
+ return {
53793
+ meshId: body.mesh_id,
53794
+ memberId: body.member_id,
53795
+ ownerPubkey: body.owner_pubkey,
53796
+ canonicalV2: body.canonical_v2,
53797
+ rootKey
53798
+ };
53799
+ }
53800
+ function parseV2InviteInput(input) {
53801
+ const trimmed = input.trim();
53802
+ const urlMatch = trimmed.match(/^https?:\/\/[^/]+(?:\/[a-z]{2})?\/i\/([A-Za-z0-9]+)\/?$/);
53803
+ if (urlMatch)
53804
+ return urlMatch[1];
53805
+ const schemelessMatch = trimmed.match(/^[^/]+(?:\/[a-z]{2})?\/i\/([A-Za-z0-9]+)\/?$/);
53806
+ if (schemelessMatch)
53807
+ return schemelessMatch[1];
53808
+ if (/^[A-Za-z0-9]{6,16}$/.test(trimmed))
53809
+ return trimmed;
53810
+ return null;
53811
+ }
53812
+
53813
+ // src/commands/join.ts
53723
53814
  init_env();
53815
+ var import_libsodium_wrappers3 = __toESM(require_libsodium_wrappers(), 1);
53724
53816
  import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "node:fs";
53725
53817
  import { join as join3, dirname as dirname3 } from "node:path";
53726
53818
  import { homedir as homedir3, hostname } from "node:os";
53819
+ function deriveAppBaseUrl() {
53820
+ const override = process.env.CLAUDEMESH_APP_URL;
53821
+ if (override)
53822
+ return override.replace(/\/$/, "");
53823
+ try {
53824
+ const u = new URL(env.CLAUDEMESH_BROKER_URL);
53825
+ const host = u.host.replace(/^ic\./, "");
53826
+ const scheme = u.protocol === "wss:" ? "https:" : "http:";
53827
+ return `${scheme}//${host}`;
53828
+ } catch {
53829
+ return "https://claudemesh.com";
53830
+ }
53831
+ }
53832
+ async function runJoinV2(code) {
53833
+ const appBaseUrl = deriveAppBaseUrl();
53834
+ console.log(`Claiming invite ${code} via ${appBaseUrl}…`);
53835
+ let claim;
53836
+ try {
53837
+ claim = await claimInviteV2({ appBaseUrl, code });
53838
+ } catch (e) {
53839
+ console.error(`claudemesh: ${e instanceof Error ? e.message : String(e)}`);
53840
+ process.exit(1);
53841
+ }
53842
+ const keypair = await generateKeypair2();
53843
+ const displayName = `${hostname()}-${process.pid}`;
53844
+ await import_libsodium_wrappers3.default.ready;
53845
+ const rootKeyB64 = import_libsodium_wrappers3.default.to_base64(claim.rootKey, import_libsodium_wrappers3.default.base64_variants.URLSAFE_NO_PADDING);
53846
+ const fallbackSlug = `mesh-${claim.meshId.slice(0, 8)}`;
53847
+ const config2 = loadConfig();
53848
+ config2.meshes = config2.meshes.filter((m) => m.meshId !== claim.meshId);
53849
+ config2.meshes.push({
53850
+ meshId: claim.meshId,
53851
+ memberId: claim.memberId,
53852
+ slug: fallbackSlug,
53853
+ name: fallbackSlug,
53854
+ pubkey: keypair.publicKey,
53855
+ secretKey: keypair.secretKey,
53856
+ brokerUrl: env.CLAUDEMESH_BROKER_URL,
53857
+ joinedAt: new Date().toISOString(),
53858
+ rootKey: rootKeyB64,
53859
+ inviteVersion: 2
53860
+ });
53861
+ saveConfig(config2);
53862
+ console.log("");
53863
+ console.log(`✓ Joined mesh ${claim.meshId} via v2 invite`);
53864
+ console.log(` member id: ${claim.memberId}`);
53865
+ console.log(` pubkey: ${keypair.publicKey.slice(0, 16)}…`);
53866
+ console.log(` broker: ${env.CLAUDEMESH_BROKER_URL}`);
53867
+ console.log(` config: ${getConfigPath()}`);
53868
+ console.log("");
53869
+ console.log("Restart Claude Code to pick up the new mesh.");
53870
+ }
53727
53871
  async function runJoin(args) {
53728
53872
  const link = args[0];
53729
53873
  if (!link) {
53730
- console.error("Usage: claudemesh join <invite-url-or-token>");
53874
+ console.error("Usage: claudemesh join <invite-url-or-code>");
53731
53875
  console.error("");
53732
- console.error("Example: claudemesh join https://claudemesh.com/join/eyJ2IjoxLC4uLn0");
53876
+ console.error("Examples:");
53877
+ console.error(" claudemesh join https://claudemesh.com/i/abc12345");
53878
+ console.error(" claudemesh join abc12345");
53879
+ console.error(" claudemesh join ic://join/eyJ2IjoxLC4uLn0 (v1 legacy)");
53733
53880
  process.exit(1);
53734
53881
  }
53882
+ const v2Code = parseV2InviteInput(link);
53883
+ if (v2Code) {
53884
+ await runJoinV2(v2Code);
53885
+ return;
53886
+ }
53735
53887
  let invite;
53736
53888
  try {
53737
53889
  invite = await parseInviteLink2(link);
@@ -55030,6 +55182,68 @@ async function runPeers(flags) {
55030
55182
  // src/commands/welcome.ts
55031
55183
  init_colors();
55032
55184
  init_spinner();
55185
+ async function browserAuthFlow(action) {
55186
+ const { generatePairingCode: generatePairingCode2, startCallbackListener: startCallbackListener2, openBrowser: openBrowser2 } = await Promise.resolve().then(() => (init_auth(), exports_auth));
55187
+ const code = generatePairingCode2();
55188
+ const listener = await startCallbackListener2();
55189
+ const baseUrl = action === "register" ? `https://claudemesh.com/register?callbackUrl=${encodeURIComponent(`/cli-auth?port=${listener.port}&code=${code}&action=sync`)}` : `https://claudemesh.com/cli-auth?port=${listener.port}&code=${code}&action=sync`;
55190
+ console.log(dim(action === "register" ? ` Opening browser to create your account...
55191
+ ` : ` Opening browser to sign in...
55192
+ `));
55193
+ const opened = await openBrowser2(baseUrl);
55194
+ if (!opened) {
55195
+ console.log(" Couldn't open browser. Visit:");
55196
+ }
55197
+ console.log(dim(` ${baseUrl}
55198
+ `));
55199
+ const manualPromise = new Promise((resolve2) => {
55200
+ const rl = createInterface3({ input: process.stdin, output: process.stdout });
55201
+ rl.question(" Paste sync token (or wait for browser): ", (answer) => {
55202
+ rl.close();
55203
+ if (answer.trim())
55204
+ resolve2(answer.trim());
55205
+ });
55206
+ });
55207
+ const timeoutPromise = new Promise((resolve2) => {
55208
+ setTimeout(() => resolve2(null), 900000);
55209
+ });
55210
+ const syncToken = await Promise.race([
55211
+ listener.token,
55212
+ manualPromise,
55213
+ timeoutPromise
55214
+ ]);
55215
+ listener.close();
55216
+ if (!syncToken) {
55217
+ console.error(`
55218
+ Timed out waiting for sign-in.`);
55219
+ process.exit(1);
55220
+ }
55221
+ const { generateKeypair: generateKeypair3 } = await Promise.resolve().then(() => (init_keypair(), exports_keypair));
55222
+ const keypair = await generateKeypair3();
55223
+ const displayName = `${hostname4()}-${process.pid}`;
55224
+ const { syncWithBroker: syncWithBroker2 } = await Promise.resolve().then(() => exports_sync_with_broker);
55225
+ const result = await syncWithBroker2(syncToken, keypair.publicKey, displayName);
55226
+ const config2 = loadConfig();
55227
+ const { saveConfig: saveConfig2 } = await Promise.resolve().then(() => (init_config(), exports_config));
55228
+ for (const m of result.meshes) {
55229
+ config2.meshes.push({
55230
+ meshId: m.mesh_id,
55231
+ memberId: m.member_id,
55232
+ slug: m.slug,
55233
+ name: m.slug,
55234
+ pubkey: keypair.publicKey,
55235
+ secretKey: keypair.secretKey,
55236
+ brokerUrl: m.broker_url,
55237
+ joinedAt: new Date().toISOString()
55238
+ });
55239
+ }
55240
+ config2.accountId = result.account_id;
55241
+ saveConfig2(config2);
55242
+ console.log(`
55243
+ ${green("✓")} Synced ${result.meshes.length} mesh(es): ${result.meshes.map((m) => m.slug).join(", ")}
55244
+ `);
55245
+ await runLaunch({}, []);
55246
+ }
55033
55247
  function detectState() {
55034
55248
  const claudeConfig = join6(homedir6(), ".claude.json");
55035
55249
  let mcpRegistered = false;
@@ -55110,84 +55324,27 @@ async function runWelcome() {
55110
55324
  }
55111
55325
  spinner.stop();
55112
55326
  const choice = await menuSelect({
55113
- title: "Connect to a mesh",
55327
+ title: "Get started",
55114
55328
  items: [
55115
- "Sign in via browser (create or join meshes)",
55329
+ "Create account (new to claudemesh)",
55330
+ "Sign in (existing account)",
55116
55331
  "Paste an invite URL",
55117
55332
  "Exit"
55118
55333
  ],
55119
55334
  row
55120
55335
  });
55121
- if (choice === 2) {
55122
- exitFullScreen();
55336
+ exitFullScreen();
55337
+ if (choice === 3)
55123
55338
  return;
55124
- }
55125
55339
  if (choice === 0) {
55126
- exitFullScreen();
55127
- console.log(dim(` Opening browser for sign-in...
55128
- `));
55129
- const { generatePairingCode: generatePairingCode2 } = await Promise.resolve().then(() => (init_auth(), exports_auth));
55130
- const { startCallbackListener: startCallbackListener2, openBrowser: openBrowser2 } = await Promise.resolve().then(() => (init_auth(), exports_auth));
55131
- const code = generatePairingCode2();
55132
- const listener = await startCallbackListener2();
55133
- const url = `https://claudemesh.com/cli-auth?port=${listener.port}&code=${code}&action=sync`;
55134
- const opened = await openBrowser2(url);
55135
- if (!opened) {
55136
- console.log(" Couldn't open browser. Visit:");
55137
- }
55138
- console.log(dim(` ${url}
55139
- `));
55140
- const manualPromise = new Promise((resolve2) => {
55141
- const rl = createInterface3({ input: process.stdin, output: process.stdout });
55142
- rl.question(" Paste sync token (or wait for browser): ", (answer) => {
55143
- rl.close();
55144
- if (answer.trim())
55145
- resolve2(answer.trim());
55146
- });
55147
- });
55148
- const timeoutPromise = new Promise((resolve2) => {
55149
- setTimeout(() => resolve2(null), 900000);
55150
- });
55151
- const syncToken = await Promise.race([
55152
- listener.token,
55153
- manualPromise,
55154
- timeoutPromise
55155
- ]);
55156
- listener.close();
55157
- if (!syncToken) {
55158
- console.error(`
55159
- Timed out waiting for sign-in.`);
55160
- process.exit(1);
55161
- }
55162
- const { generateKeypair: generateKeypair3 } = await Promise.resolve().then(() => (init_keypair(), exports_keypair));
55163
- const keypair = await generateKeypair3();
55164
- const displayName = `${hostname4()}-${process.pid}`;
55165
- const { syncWithBroker: syncWithBroker2 } = await Promise.resolve().then(() => exports_sync_with_broker);
55166
- const result = await syncWithBroker2(syncToken, keypair.publicKey, displayName);
55167
- const config2 = loadConfig();
55168
- const { saveConfig: saveConfig2 } = await Promise.resolve().then(() => (init_config(), exports_config));
55169
- for (const m of result.meshes) {
55170
- config2.meshes.push({
55171
- meshId: m.mesh_id,
55172
- memberId: m.member_id,
55173
- slug: m.slug,
55174
- name: m.slug,
55175
- pubkey: keypair.publicKey,
55176
- secretKey: keypair.secretKey,
55177
- brokerUrl: m.broker_url,
55178
- joinedAt: new Date().toISOString()
55179
- });
55180
- }
55181
- config2.accountId = result.account_id;
55182
- saveConfig2(config2);
55183
- console.log(`
55184
- ${green("✓")} Synced ${result.meshes.length} mesh(es): ${result.meshes.map((m) => m.slug).join(", ")}
55185
- `);
55186
- await runLaunch({}, []);
55340
+ await browserAuthFlow("register");
55187
55341
  return;
55188
55342
  }
55189
55343
  if (choice === 1) {
55190
- exitFullScreen();
55344
+ await browserAuthFlow("sync");
55345
+ return;
55346
+ }
55347
+ if (choice === 2) {
55191
55348
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
55192
55349
  const url = await new Promise((resolve2) => {
55193
55350
  rl.question(" Invite URL: ", (answer) => {
@@ -56279,4 +56436,11 @@ var main = defineCommand({
56279
56436
  await runWelcome();
56280
56437
  }
56281
56438
  });
56439
+ var KNOWN_SUBCOMMANDS = new Set(Object.keys(main.subCommands ?? {}));
56440
+ var ROOT_PASSTHROUGH_FLAGS = new Set(["--help", "-h", "--version", "-v"]);
56441
+ var argv = process.argv.slice(2);
56442
+ var first = argv[0];
56443
+ if (first && !ROOT_PASSTHROUGH_FLAGS.has(first) && !KNOWN_SUBCOMMANDS.has(first)) {
56444
+ process.argv.splice(2, 0, "launch");
56445
+ }
56282
56446
  runMain(main);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudemesh-cli",
3
- "version": "0.10.3",
3
+ "version": "0.10.5",
4
4
  "description": "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -48,10 +48,10 @@
48
48
  "prettier": "3.6.2",
49
49
  "typescript": "5.9.3",
50
50
  "vitest": "4.0.14",
51
- "@turbostarter/tsconfig": "0.1.0",
51
+ "@turbostarter/eslint-config": "0.1.0",
52
52
  "@turbostarter/prettier-config": "0.1.0",
53
- "@turbostarter/vitest-config": "0.1.0",
54
- "@turbostarter/eslint-config": "0.1.0"
53
+ "@turbostarter/tsconfig": "0.1.0",
54
+ "@turbostarter/vitest-config": "0.1.0"
55
55
  },
56
56
  "scripts": {
57
57
  "build": "bun build src/index.ts --target=node --outfile dist/index.js --banner \"#!/usr/bin/env node\" && chmod +x dist/index.js",